Merge "Don't hide the PIN keyboard even when a physical keyboard is present." into jb-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index 4fab38b..9d7bb14 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2066,6 +2066,7 @@
     field public static final int DEFAULT = 1; // 0x1
     field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
     field public static final int FEEDBACK_AUDIBLE = 4; // 0x4
+    field public static final int FEEDBACK_BRAILLE = 32; // 0x20
     field public static final int FEEDBACK_GENERIC = 16; // 0x10
     field public static final int FEEDBACK_HAPTIC = 2; // 0x2
     field public static final int FEEDBACK_SPOKEN = 1; // 0x1
@@ -6326,6 +6327,7 @@
     field public static final int FLAG_FACTORY_TEST = 16; // 0x10
     field public static final int FLAG_HAS_CODE = 4; // 0x4
     field public static final int FLAG_INSTALLED = 8388608; // 0x800000
+    field public static final int FLAG_IS_DATA_ONLY = 16777216; // 0x1000000
     field public static final int FLAG_KILL_AFTER_RESTORE = 65536; // 0x10000
     field public static final int FLAG_LARGE_HEAP = 1048576; // 0x100000
     field public static final int FLAG_PERSISTENT = 8; // 0x8
@@ -15615,6 +15617,7 @@
     method public static final void flushPendingCommands();
     method public static final int getCallingPid();
     method public static final int getCallingUid();
+    method public static final android.os.UserHandle getCallingUserHandle();
     method public java.lang.String getInterfaceDescriptor();
     method public boolean isBinderAlive();
     method public static final void joinThreadPool();
@@ -25026,6 +25029,7 @@
     method public void setId(int);
     method public void setImportantForAccessibility(int);
     method public void setKeepScreenOn(boolean);
+    method public void setLayerPaint(android.graphics.Paint);
     method public void setLayerType(int, android.graphics.Paint);
     method public void setLayoutDirection(int);
     method public void setLayoutParams(android.view.ViewGroup.LayoutParams);
diff --git a/cmds/am/src/com/android/commands/am/Am.java b/cmds/am/src/com/android/commands/am/Am.java
index bcd4588..bb108c8 100644
--- a/cmds/am/src/com/android/commands/am/Am.java
+++ b/cmds/am/src/com/android/commands/am/Am.java
@@ -36,6 +36,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemProperties;
+import android.os.UserHandle;
 import android.util.AndroidException;
 import android.view.Display;
 import android.view.IWindowManager;
@@ -147,6 +148,18 @@
         }
     }
 
+    int parseUserArg(String arg) {
+        int userId;
+        if ("all".equals(arg)) {
+            userId = UserHandle.USER_ALL;
+        } else if ("current".equals(arg) || "cur".equals(arg)) {
+            userId = UserHandle.USER_CURRENT;
+        } else {
+            userId = Integer.parseInt(arg);
+        }
+        return userId;
+    }
+
     private Intent makeIntent() throws URISyntaxException {
         Intent intent = new Intent();
         Intent baseIntent = intent;
@@ -321,7 +334,7 @@
             } else if (opt.equals("--opengl-trace")) {
                 mStartFlags |= ActivityManager.START_FLAG_OPENGL_TRACES;
             } else if (opt.equals("--user")) {
-                mUserId = Integer.parseInt(nextArgRequired());
+                mUserId = parseUserArg(nextArgRequired());
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return null;
@@ -392,8 +405,12 @@
 
     private void runStartService() throws Exception {
         Intent intent = makeIntent();
+        if (mUserId == UserHandle.USER_ALL) {
+            System.err.println("Error: Can't start activity with user 'all'");
+            return;
+        }
         System.out.println("Starting service: " + intent);
-        ComponentName cn = mAm.startService(null, intent, intent.getType(), 0);
+        ComponentName cn = mAm.startService(null, intent, intent.getType(), mUserId);
         if (cn == null) {
             System.err.println("Error: Not found; no service started.");
         }
@@ -402,10 +419,15 @@
     private void runStart() throws Exception {
         Intent intent = makeIntent();
 
+        if (mUserId == UserHandle.USER_ALL) {
+            System.err.println("Error: Can't start service with user 'all'");
+            return;
+        }
+
         String mimeType = intent.getType();
         if (mimeType == null && intent.getData() != null
                 && "content".equals(intent.getData().getScheme())) {
-            mimeType = mAm.getProviderMimeType(intent.getData());
+            mimeType = mAm.getProviderMimeType(intent.getData(), mUserId);
         }
 
         do {
@@ -460,11 +482,11 @@
             int res;
             if (mWaitOption) {
                 result = mAm.startActivityAndWait(null, intent, mimeType,
-                            null, null, 0, mStartFlags, mProfileFile, fd, null);
+                            null, null, 0, mStartFlags, mProfileFile, fd, null, mUserId);
                 res = result.result;
             } else {
-                res = mAm.startActivity(null, intent, mimeType,
-                        null, null, 0, mStartFlags, mProfileFile, fd, null);
+                res = mAm.startActivityAsUser(null, intent, mimeType,
+                        null, null, 0, mStartFlags, mProfileFile, fd, null, mUserId);
             }
             PrintStream out = mWaitOption ? System.out : System.err;
             boolean launched = false;
@@ -573,6 +595,7 @@
         boolean wait = false;
         boolean rawMode = false;
         boolean no_window_animation = false;
+        int userId = 0;
         Bundle args = new Bundle();
         String argKey = null, argValue = null;
         IWindowManager wm = IWindowManager.Stub.asInterface(ServiceManager.getService("window"));
@@ -592,12 +615,19 @@
             } else if (opt.equals("--no_window_animation")
                     || opt.equals("--no-window-animation")) {
                 no_window_animation = true;
+            } else if (opt.equals("--user")) {
+                userId = parseUserArg(nextArgRequired());
             } else {
                 System.err.println("Error: Unknown option: " + opt);
                 return;
             }
         }
 
+        if (userId == UserHandle.USER_ALL) {
+            System.err.println("Error: Can't start instrumentation with user 'all'");
+            return;
+        }
+
         String cnArg = nextArgRequired();
         ComponentName cn = ComponentName.unflattenFromString(cnArg);
         if (cn == null) throw new IllegalArgumentException("Bad component name: " + cnArg);
@@ -614,7 +644,7 @@
             wm.setAnimationScale(1, 0.0f);
         }
 
-        if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher)) {
+        if (!mAm.startInstrumentation(cn, profileFile, 0, args, watcher, userId)) {
             throw new AndroidException("INSTRUMENTATION_FAILED: " + cn.flattenToString());
         }
 
@@ -1188,8 +1218,10 @@
             if (data != null) line = line + ", data=\"" + data + "\"";
             if (extras != null) line = line + ", extras: " + extras;
             System.out.println(line);
-            mFinished = true;
-            notifyAll();
+            synchronized (this) {
+              mFinished = true;
+              notifyAll();
+            }
         }
 
         public synchronized void waitForFinish() {
@@ -1338,6 +1370,7 @@
                 "       am kill-all\n" +
                 "       am broadcast <INTENT>\n" +
                 "       am instrument [-r] [-e <NAME> <VALUE>] [-p <FILE>] [-w]\n" +
+                "               [--user <USER_ID> | all | current]\n" +
                 "               [--no-window-animation] <COMPONENT>\n" +
                 "       am profile start <PROCESS> <FILE>\n" +
                 "       am profile stop [<PROCESS>]\n" +
@@ -1384,6 +1417,7 @@
                 "    -p <FILE>: write profiling data to <FILE>\n" +
                 "    -w: wait for instrumentation to finish before returning.  Required for\n" +
                 "        test runners.\n" +
+                "    --user [<USER_ID> | all | current]: Specify user instrumentation runs in.\n" +
                 "    --no-window-animation: turn off window animations will running.\n" +
                 "\n" +
                 "am profile: start and stop profiler on a process.\n" +
@@ -1431,6 +1465,7 @@
                 "    [--ela <EXTRA_KEY> <EXTRA_LONG_VALUE>[,<EXTRA_LONG_VALUE...]]\n" +
                 "    [--efa <EXTRA_KEY> <EXTRA_FLOAT_VALUE>[,<EXTRA_FLOAT_VALUE...]]\n" +
                 "    [-n <COMPONENT>] [-f <FLAGS>]\n" +
+                "    [--user [<USER_ID> | all | current]\n" +
                 "    [--grant-read-uri-permission] [--grant-write-uri-permission]\n" +
                 "    [--debug-log-resolution] [--exclude-stopped-packages]\n" +
                 "    [--include-stopped-packages]\n" +
diff --git a/cmds/installd/commands.c b/cmds/installd/commands.c
index 9e83a67..697d8ec 100644
--- a/cmds/installd/commands.c
+++ b/cmds/installd/commands.c
@@ -420,7 +420,7 @@
         return -1;
     }
     if (chmod(pkgpath, S_IRUSR|S_IWUSR|S_IRGRP) < 0) {
-        ALOGE("failed to chmod '%s': %s\n", pkgpath, strerror(errno));
+        ALOGE("protect(): failed to chmod '%s': %s\n", pkgpath, strerror(errno));
         return -1;
     }
 
@@ -1014,13 +1014,13 @@
 
     if (stat(dataDir, &s) < 0) return -1;
 
-    if (chown(dataDir, 0, 0) < 0) {
+    if (chown(dataDir, AID_INSTALL, AID_INSTALL) < 0) {
         ALOGE("failed to chown '%s': %s\n", dataDir, strerror(errno));
         return -1;
     }
 
     if (chmod(dataDir, 0700) < 0) {
-        ALOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        ALOGE("linklib() 1: failed to chmod '%s': %s\n", dataDir, strerror(errno));
         rc = -1;
         goto out;
     }
@@ -1058,7 +1058,7 @@
 
 out:
     if (chmod(dataDir, s.st_mode) < 0) {
-        ALOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        ALOGE("linklib() 2: failed to chmod '%s': %s\n", dataDir, strerror(errno));
         rc = -errno;
     }
 
@@ -1091,13 +1091,13 @@
         return -1;
     }
 
-    if (chown(dataDir, 0, 0) < 0) {
+    if (chown(dataDir, AID_INSTALL, AID_INSTALL) < 0) {
         ALOGE("failed to chown '%s': %s\n", dataDir, strerror(errno));
         return -1;
     }
 
     if (chmod(dataDir, 0700) < 0) {
-        ALOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        ALOGE("unlinklib() 1: failed to chmod '%s': %s\n", dataDir, strerror(errno));
         rc = -1;
         goto out;
     }
@@ -1140,7 +1140,7 @@
 
 out:
     if (chmod(dataDir, s.st_mode) < 0) {
-        ALOGE("failed to chmod '%s': %s\n", dataDir, strerror(errno));
+        ALOGE("unlinklib() 2: failed to chmod '%s': %s\n", dataDir, strerror(errno));
         rc = -1;
     }
 
diff --git a/cmds/pm/src/com/android/commands/pm/Pm.java b/cmds/pm/src/com/android/commands/pm/Pm.java
index 36bf38a..8570f27 100644
--- a/cmds/pm/src/com/android/commands/pm/Pm.java
+++ b/cmds/pm/src/com/android/commands/pm/Pm.java
@@ -20,7 +20,6 @@
 
 import android.app.ActivityManagerNative;
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.ContainerEncryptionParams;
 import android.content.pm.FeatureInfo;
@@ -40,12 +39,9 @@
 import android.content.res.AssetManager;
 import android.content.res.Resources;
 import android.net.Uri;
-import android.os.Binder;
 import android.os.IUserManager;
-import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.UserManager;
 
 import java.io.File;
 import java.lang.reflect.Field;
@@ -74,7 +70,6 @@
 
     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);
@@ -1054,11 +1049,16 @@
     }
 
     private void runUninstall() {
-        int unInstallFlags = 0;
+        int unInstallFlags = PackageManager.DELETE_ALL_USERS;
 
-        String opt = nextOption();
-        if (opt != null && opt.equals("-k")) {
-            unInstallFlags = PackageManager.DELETE_KEEP_DATA;
+        String opt;
+        while ((opt=nextOption()) != null) {
+            if (opt.equals("-k")) {
+                unInstallFlags |= PackageManager.DELETE_KEEP_DATA;
+            } else {
+                System.err.println("Error: Unknown option: " + opt);
+                return;
+            }
         }
 
         String pkg = nextArg();
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 10ea0fe..75a4f83 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -85,6 +85,11 @@
     public static final int FEEDBACK_GENERIC = 0x0000010;
 
     /**
+     * Denotes braille feedback.
+     */
+    public static final int FEEDBACK_BRAILLE = 0x0000020;
+
+    /**
      * Mask for all feedback types.
      *
      * @see #FEEDBACK_SPOKEN
@@ -92,6 +97,7 @@
      * @see #FEEDBACK_AUDIBLE
      * @see #FEEDBACK_VISUAL
      * @see #FEEDBACK_GENERIC
+     * @see #FEEDBACK_BRAILLE
      */
     public static final int FEEDBACK_ALL_MASK = 0xFFFFFFFF;
 
@@ -186,6 +192,7 @@
      * @see #FEEDBACK_HAPTIC
      * @see #FEEDBACK_SPOKEN
      * @see #FEEDBACK_VISUAL
+     * @see #FEEDBACK_BRAILLE
      */
     public int feedbackType;
 
@@ -591,6 +598,12 @@
                     }
                     builder.append("FEEDBACK_VISUAL");
                     break;
+                case FEEDBACK_BRAILLE:
+                    if (builder.length() > 1) {
+                        builder.append(", ");
+                    }
+                    builder.append("FEEDBACK_BRAILLE");
+                    break;
             }
         }
         builder.append("]");
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index cd22aad..bb3c56a 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -1957,4 +1957,21 @@
             return false;
         }
     }
+
+    /**
+     * Return whether the given user is actively running.  This means that
+     * the user is in the "started" state, not "stopped" -- it is currently
+     * allowed to run code through scheduled alarms, receiving broadcasts,
+     * etc.  A started user may be either the current foreground user or a
+     * background user; the result here does not distinguish between the two.
+     * @param userid the user's id. Zero indicates the default user.
+     * @hide
+     */
+    public boolean isUserRunning(int userid) {
+        try {
+            return ActivityManagerNative.getDefault().isUserRunning(userid);
+        } catch (RemoteException e) {
+            return false;
+        }
+    }
 }
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java
index eed9254..e5dd7b1 100644
--- a/core/java/android/app/ActivityManagerNative.java
+++ b/core/java/android/app/ActivityManagerNative.java
@@ -177,9 +177,10 @@
                     ? data.readFileDescriptor() : null;
             Bundle options = data.readInt() != 0
                     ? Bundle.CREATOR.createFromParcel(data) : null;
+            int userId = data.readInt();
             WaitResult result = startActivityAndWait(app, intent, resolvedType,
                     resultTo, resultWho, requestCode, startFlags,
-                    profileFile, profileFd, options);
+                    profileFile, profileFd, options, userId);
             reply.writeNoException();
             result.writeToParcel(reply, 0);
             return true;
@@ -811,7 +812,8 @@
             Bundle arguments = data.readBundle();
             IBinder b = data.readStrongBinder();
             IInstrumentationWatcher w = IInstrumentationWatcher.Stub.asInterface(b);
-            boolean res = startInstrumentation(className, profileFile, fl, arguments, w);
+            int userId = data.readInt();
+            boolean res = startInstrumentation(className, profileFile, fl, arguments, w, userId);
             reply.writeNoException();
             reply.writeInt(res ? 1 : 0);
             return true;
@@ -1323,11 +1325,11 @@
             return true;
         }
         
-        case KILL_APPLICATION_WITH_UID_TRANSACTION: {
+        case KILL_APPLICATION_WITH_APPID_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             String pkg = data.readString();
-            int uid = data.readInt();
-            killApplicationWithUid(pkg, uid);
+            int appid = data.readInt();
+            killApplicationWithAppId(pkg, appid);
             reply.writeNoException();
             return true;
         }
@@ -1424,7 +1426,8 @@
         case GET_PROVIDER_MIME_TYPE_TRANSACTION: {
             data.enforceInterface(IActivityManager.descriptor);
             Uri uri = Uri.CREATOR.createFromParcel(data);
-            String type = getProviderMimeType(uri);
+            int userId = data.readInt();
+            String type = getProviderMimeType(uri, userId);
             reply.writeNoException();
             reply.writeString(type);
             return true;
@@ -1573,6 +1576,15 @@
             return true;
         }
 
+        case IS_USER_RUNNING_TRANSACTION: {
+            data.enforceInterface(IActivityManager.descriptor);
+            int userid = data.readInt();
+            boolean result = isUserRunning(userid);
+            reply.writeNoException();
+            reply.writeInt(result ? 1 : 0);
+            return true;
+        }
+
         case REMOVE_SUB_TASK_TRANSACTION:
         {
             data.enforceInterface(IActivityManager.descriptor);
@@ -1827,7 +1839,7 @@
     public WaitResult startActivityAndWait(IApplicationThread caller, Intent intent,
             String resolvedType, IBinder resultTo, String resultWho,
             int requestCode, int startFlags, String profileFile,
-            ParcelFileDescriptor profileFd, Bundle options) throws RemoteException {
+            ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
@@ -1851,6 +1863,7 @@
         } else {
             data.writeInt(0);
         }
+        data.writeInt(userId);
         mRemote.transact(START_ACTIVITY_AND_WAIT_TRANSACTION, data, reply, 0);
         reply.readException();
         WaitResult result = WaitResult.CREATOR.createFromParcel(reply);
@@ -2719,7 +2732,7 @@
     }
 
     public boolean startInstrumentation(ComponentName className, String profileFile,
-            int flags, Bundle arguments, IInstrumentationWatcher watcher)
+            int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
             throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
@@ -2729,6 +2742,7 @@
         data.writeInt(flags);
         data.writeBundle(arguments);
         data.writeStrongBinder(watcher != null ? watcher.asBinder() : null);
+        data.writeInt(userId);
         mRemote.transact(START_INSTRUMENTATION_TRANSACTION, data, reply, 0);
         reply.readException();
         boolean res = reply.readInt() != 0;
@@ -3366,13 +3380,13 @@
         data.recycle();
     }
     
-    public void killApplicationWithUid(String pkg, int uid) throws RemoteException {
+    public void killApplicationWithAppId(String pkg, int appid) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         data.writeString(pkg);
-        data.writeInt(uid);
-        mRemote.transact(KILL_APPLICATION_WITH_UID_TRANSACTION, data, reply, 0);
+        data.writeInt(appid);
+        mRemote.transact(KILL_APPLICATION_WITH_APPID_TRANSACTION, data, reply, 0);
         reply.readException();
         data.recycle();
         reply.recycle();
@@ -3507,12 +3521,12 @@
         reply.recycle();
     }
 
-    public String getProviderMimeType(Uri uri)
-            throws RemoteException {
+    public String getProviderMimeType(Uri uri, int userId) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
         data.writeInterfaceToken(IActivityManager.descriptor);
         uri.writeToParcel(data, 0);
+        data.writeInt(userId);
         mRemote.transact(GET_PROVIDER_MIME_TYPE_TRANSACTION, data, reply, 0);
         reply.readException();
         String res = reply.readString();
@@ -3747,6 +3761,19 @@
         return userInfo;
     }
 
+    public boolean isUserRunning(int userid) throws RemoteException {
+        Parcel data = Parcel.obtain();
+        Parcel reply = Parcel.obtain();
+        data.writeInterfaceToken(IActivityManager.descriptor);
+        data.writeInt(userid);
+        mRemote.transact(IS_USER_RUNNING_TRANSACTION, data, reply, 0);
+        reply.readException();
+        boolean result = reply.readInt() != 0;
+        reply.recycle();
+        data.recycle();
+        return result;
+    }
+
     public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException {
         Parcel data = Parcel.obtain();
         Parcel reply = Parcel.obtain();
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 65ea6a0..3498919 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1428,7 +1428,7 @@
                 arguments.setAllowFds(false);
             }
             return ActivityManagerNative.getDefault().startInstrumentation(
-                    className, profileFile, 0, arguments, null);
+                    className, profileFile, 0, arguments, null, UserHandle.myUserId());
         } catch (RemoteException e) {
             // System has crashed, nothing we can do.
         }
diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java
index 7a633ed..9cb3621 100644
--- a/core/java/android/app/IActivityManager.java
+++ b/core/java/android/app/IActivityManager.java
@@ -62,7 +62,7 @@
     public WaitResult startActivityAndWait(IApplicationThread caller,
             Intent intent, String resolvedType, IBinder resultTo, String resultWho,
             int requestCode, int flags, String profileFile,
-            ParcelFileDescriptor profileFd, Bundle options) throws RemoteException;
+            ParcelFileDescriptor profileFd, Bundle options, int userId) throws RemoteException;
     public int startActivityWithConfig(IApplicationThread caller,
             Intent intent, String resolvedType, IBinder resultTo, String resultWho,
             int requestCode, int startFlags, Configuration newConfig,
@@ -160,7 +160,7 @@
     public void killApplicationProcess(String processName, int uid) throws RemoteException;
     
     public boolean startInstrumentation(ComponentName className, String profileFile,
-            int flags, Bundle arguments, IInstrumentationWatcher watcher)
+            int flags, Bundle arguments, IInstrumentationWatcher watcher, int userId)
             throws RemoteException;
     public void finishInstrumentation(IApplicationThread target,
             int resultCode, Bundle results) throws RemoteException;
@@ -275,7 +275,7 @@
     public void stopAppSwitches() throws RemoteException;
     public void resumeAppSwitches() throws RemoteException;
     
-    public void killApplicationWithUid(String pkg, int uid) throws RemoteException;
+    public void killApplicationWithAppId(String pkg, int appid) throws RemoteException;
     
     public void closeSystemDialogs(String reason) throws RemoteException;
     
@@ -296,7 +296,7 @@
     public void crashApplication(int uid, int initialPid, String packageName,
             String message) throws RemoteException;
 
-    public String getProviderMimeType(Uri uri) throws RemoteException;
+    public String getProviderMimeType(Uri uri, int userId) throws RemoteException;
     
     public IBinder newUriPermissionOwner(String name) throws RemoteException;
     public void grantUriPermissionFromOwner(IBinder owner, int fromUid, String targetPkg,
@@ -328,6 +328,7 @@
     public boolean switchUser(int userid) throws RemoteException;
     public int stopUser(int userid, IStopUserCallback callback) throws RemoteException;
     public UserInfo getCurrentUser() throws RemoteException;
+    public boolean isUserRunning(int userid) throws RemoteException;
 
     public boolean removeSubTask(int taskId, int subTaskIndex) throws RemoteException;
 
@@ -548,7 +549,7 @@
     int GET_UID_FOR_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92;
     int HANDLE_INCOMING_USER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+93;
 
-    int KILL_APPLICATION_WITH_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95;
+    int KILL_APPLICATION_WITH_APPID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95;
     int CLOSE_SYSTEM_DIALOGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+96;
     int GET_PROCESS_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+97;
     int KILL_APPLICATION_PROCESS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+98;
@@ -574,7 +575,7 @@
     int CHECK_GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+118;
     int DUMP_HEAP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+119;
     int START_ACTIVITIES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+120;
-
+    int IS_USER_RUNNING_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+121;
     int ACTIVITY_SLEPT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+122;
     int GET_FRONT_ACTIVITY_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+123;
     int SET_FRONT_ACTIVITY_SCREEN_COMPAT_MODE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+124;
diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java
index 0a5a26a9..ece8841 100644
--- a/core/java/android/content/ContentResolver.java
+++ b/core/java/android/content/ContentResolver.java
@@ -39,6 +39,7 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.EventLog;
 import android.util.Log;
@@ -230,7 +231,8 @@
         }
 
         try {
-            String type = ActivityManagerNative.getDefault().getProviderMimeType(url);
+            String type = ActivityManagerNative.getDefault().getProviderMimeType(
+                    url, UserHandle.myUserId());
             return type;
         } catch (RemoteException e) {
             // Arbitrary and not worth documenting, as Activity
diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java
index 1a82d58..a0283d3 100644
--- a/core/java/android/content/pm/ApplicationInfo.java
+++ b/core/java/android/content/pm/ApplicationInfo.java
@@ -308,6 +308,13 @@
     public static final int FLAG_INSTALLED = 1<<23;
 
     /**
+     * Value for {@link #flags}: true if the application only has its
+     * data installed; the application package itself does not currently
+     * exist on the device.
+     */
+    public static final int FLAG_IS_DATA_ONLY = 1<<24;
+
+    /**
      * Value for {@link #flags}: Set to true if the application has been
      * installed using the forward lock option.
      *
diff --git a/core/java/android/content/pm/InstrumentationInfo.java b/core/java/android/content/pm/InstrumentationInfo.java
index ea47e8e..a977e41 100644
--- a/core/java/android/content/pm/InstrumentationInfo.java
+++ b/core/java/android/content/pm/InstrumentationInfo.java
@@ -18,10 +18,6 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.text.TextUtils;
-
-import java.text.Collator;
-import java.util.Comparator;
 
 /**
  * Information you can retrieve about a particular piece of test
diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java
index 0b77842..7164713 100644
--- a/core/java/android/content/res/Configuration.java
+++ b/core/java/android/content/res/Configuration.java
@@ -536,8 +536,8 @@
         int layoutDir = (screenLayout&SCREENLAYOUT_LAYOUTDIR_MASK);
         switch (layoutDir) {
             case SCREENLAYOUT_LAYOUTDIR_UNDEFINED: sb.append(" ?layoutDir"); break;
-            case SCREENLAYOUT_LAYOUTDIR_LTR: sb.append(" ltr"); break;
-            case SCREENLAYOUT_LAYOUTDIR_RTL: sb.append(" rtl"); break;
+            case SCREENLAYOUT_LAYOUTDIR_LTR: sb.append(" ldltr"); break;
+            case SCREENLAYOUT_LAYOUTDIR_RTL: sb.append(" ldrtl"); break;
             default: sb.append(" layoutDir=");
                 sb.append(layoutDir >> SCREENLAYOUT_LAYOUTDIR_SHIFT); break;
         }
diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java
index 42a6bdc..b316f23 100755
--- a/core/java/android/content/res/Resources.java
+++ b/core/java/android/content/res/Resources.java
@@ -1897,12 +1897,14 @@
             }
         }
 
-        final long key = (((long) value.assetCookie) << 32) | value.data;
         boolean isColorDrawable = false;
         if (value.type >= TypedValue.TYPE_FIRST_COLOR_INT &&
                 value.type <= TypedValue.TYPE_LAST_COLOR_INT) {
             isColorDrawable = true;
         }
+        final long key = isColorDrawable ? value.data :
+                (((long) value.assetCookie) << 32) | value.data;
+
         Drawable dr = getCachedDrawable(isColorDrawable ? mColorDrawableCache : mDrawableCache, key);
 
         if (dr != null) {
diff --git a/core/java/android/os/Binder.java b/core/java/android/os/Binder.java
index ea14098..16b4835 100644
--- a/core/java/android/os/Binder.java
+++ b/core/java/android/os/Binder.java
@@ -64,7 +64,7 @@
     public static final native int getCallingPid();
     
     /**
-     * Return the ID of the user assigned to the process that sent you the
+     * Return the Linux uid assigned to the process that sent you the
      * current transaction that is being processed.  This uid can be used with
      * higher-level system services to determine its identity and check
      * permissions.  If the current thread is not currently executing an
@@ -73,6 +73,18 @@
     public static final native int getCallingUid();
 
     /**
+     * Return the UserHandle assigned to the process that sent you the
+     * current transaction that is being processed.  This is the user
+     * of the caller.  It is distinct from {@link #getCallingUid()} in that a
+     * particular user will have multiple distinct apps running under it each
+     * with their own uid.  If the current thread is not currently executing an
+     * incoming transaction, then its own UserHandle is returned.
+     */
+    public static final UserHandle getCallingUserHandle() {
+        return new UserHandle(UserHandle.getUserId(getCallingUid()));
+    }
+
+    /**
      * Reset the identity of the incoming IPC on the current thread.  This can
      * be useful if, while handling an incoming call, you will be calling
      * on interfaces of other objects that may be local to your process and
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 79c8f3b..b5983d1 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -26,16 +26,16 @@
  */
 public class StorageVolume implements Parcelable {
 
-    //private static final String TAG = "StorageVolume";
+    private int mStorageId;
 
     private final String mPath;
     private final int mDescriptionId;
+    private final boolean mPrimary;
     private final boolean mRemovable;
     private final boolean mEmulated;
     private final int mMtpReserveSpace;
     private final boolean mAllowMassStorage;
-    private int mStorageId;
-    // maximum file size for the storage, or zero for no limit
+    /** Maximum file size for the storage, or zero for no limit */
     private final long mMaxFileSize;
 
     // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
@@ -43,10 +43,11 @@
     // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
     public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
 
-    public StorageVolume(String path, int descriptionId, boolean removable,
+    public StorageVolume(String path, int descriptionId, boolean primary, boolean removable,
             boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize) {
         mPath = path;
         mDescriptionId = descriptionId;
+        mPrimary = primary;
         mRemovable = removable;
         mEmulated = emulated;
         mMtpReserveSpace = mtpReserveSpace;
@@ -54,18 +55,16 @@
         mMaxFileSize = maxFileSize;
     }
 
-    // for parcelling only
-    private StorageVolume(String path, int descriptionId, boolean removable,
-            boolean emulated, int mtpReserveSpace, int storageId,
-            boolean allowMassStorage, long maxFileSize) {
-        mPath = path;
-        mDescriptionId = descriptionId;
-        mRemovable = removable;
-        mEmulated = emulated;
-        mMtpReserveSpace = mtpReserveSpace;
-        mAllowMassStorage = allowMassStorage;
-        mStorageId = storageId;
-        mMaxFileSize = maxFileSize;
+    private StorageVolume(Parcel in) {
+        mStorageId = in.readInt();
+        mPath = in.readString();
+        mDescriptionId = in.readInt();
+        mPrimary = in.readByte() != 0;
+        mRemovable = in.readByte() != 0;
+        mEmulated = in.readByte() != 0;
+        mMtpReserveSpace = in.readInt();
+        mAllowMassStorage = in.readByte() != 0;
+        mMaxFileSize = in.readLong();
     }
 
     /**
@@ -90,6 +89,10 @@
         return mDescriptionId;
     }
 
+    public boolean isPrimary() {
+        return mPrimary;
+    }
+
     /**
      * Returns true if the volume is removable.
      *
@@ -183,37 +186,31 @@
                 + mRemovable + ", mStorageId=" + mStorageId + "]";
     }
 
-    public static final Parcelable.Creator<StorageVolume> CREATOR =
-        new Parcelable.Creator<StorageVolume>() {
+    public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
+        @Override
         public StorageVolume createFromParcel(Parcel in) {
-            String path = in.readString();
-            int descriptionId = in.readInt();
-            int removable = in.readInt();
-            int emulated = in.readInt();
-            int storageId = in.readInt();
-            int mtpReserveSpace = in.readInt();
-            int allowMassStorage = in.readInt();
-            long maxFileSize = in.readLong();
-            return new StorageVolume(path, descriptionId,
-                    removable == 1, emulated == 1, mtpReserveSpace,
-                    storageId, allowMassStorage == 1, maxFileSize);
+            return new StorageVolume(in);
         }
 
+        @Override
         public StorageVolume[] newArray(int size) {
             return new StorageVolume[size];
         }
     };
 
+    @Override
     public int describeContents() {
         return 0;
     }
 
+    @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeInt(mStorageId);
         parcel.writeString(mPath);
         parcel.writeInt(mDescriptionId);
+        parcel.writeInt(mPrimary ? 1 : 0);
         parcel.writeInt(mRemovable ? 1 : 0);
         parcel.writeInt(mEmulated ? 1 : 0);
-        parcel.writeInt(mStorageId);
         parcel.writeInt(mMtpReserveSpace);
         parcel.writeInt(mAllowMassStorage ? 1 : 0);
         parcel.writeLong(mMaxFileSize);
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index d7ae441..841a076 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -878,6 +878,8 @@
         private static final HashSet<String> MOVED_TO_GLOBAL;
         static {
             MOVED_TO_GLOBAL = new HashSet<String>();
+            // these were originally in system but migrated to secure in the past,
+            // so are duplicated in the Secure.* namespace
             MOVED_TO_GLOBAL.add(Global.ADB_ENABLED);
             MOVED_TO_GLOBAL.add(Global.BLUETOOTH_ON);
             MOVED_TO_GLOBAL.add(Global.DATA_ROAMING);
@@ -885,6 +887,7 @@
             MOVED_TO_GLOBAL.add(Global.INSTALL_NON_MARKET_APPS);
             MOVED_TO_GLOBAL.add(Global.USB_MASS_STORAGE_ENABLED);
 
+            // these are moving directly from system to global
             MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_ON);
             MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_RADIOS);
             MOVED_TO_GLOBAL.add(Settings.Global.AIRPLANE_MODE_TOGGLEABLE_RADIOS);
@@ -4779,17 +4782,24 @@
         /**
          * Whether the package manager should send package verification broadcasts for verifiers to
          * review apps prior to installation.
-         *
+         * @deprecated moved to Settings.Global
          * 1 = request apps to be verified prior to installation, if a verifier exists.
          * 0 = do not verify apps before installation
          * {@hide}
          */
+        @Deprecated
         public static final String PACKAGE_VERIFIER_ENABLE = "package_verifier_enable";
 
-        /** Timeout for package verification. {@hide} */
+        /** Timeout for package verification.
+         * @deprecated moved to Settings.Global
+         * {@hide} */
+        @Deprecated
         public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout";
 
-        /** Default response code for package verification. {@hide} */
+        /** Default response code for package verification.
+         * @deprecated moved to Settings.Global
+         * {@hide} */
+        @Deprecated
         public static final String PACKAGE_VERIFIER_DEFAULT_RESPONSE = "verifier_default_response";
 
         /** {@hide} */
@@ -5269,6 +5279,23 @@
        public static final String NTP_TIMEOUT = "ntp_timeout";
 
        /**
+        * Whether the package manager should send package verification broadcasts for verifiers to
+        * review apps prior to installation.
+        * 1 = request apps to be verified prior to installation, if a verifier exists.
+        * 0 = do not verify apps before installation
+        * {@hide}
+        */
+       public static final String PACKAGE_VERIFIER_ENABLE = "package_verifier_enable";
+
+       /** Timeout for package verification.
+        * {@hide} */
+       public static final String PACKAGE_VERIFIER_TIMEOUT = "verifier_timeout";
+
+       /** Default response code for package verification.
+        * {@hide} */
+       public static final String PACKAGE_VERIFIER_DEFAULT_RESPONSE = "verifier_default_response";
+
+       /**
         * The interval in milliseconds at which to check packet counts on the
         * mobile data interface when screen is on, to detect possible data
         * connection problems.
diff --git a/core/java/android/text/format/Time.java b/core/java/android/text/format/Time.java
index e9b0d32..b0b18da 100644
--- a/core/java/android/text/format/Time.java
+++ b/core/java/android/text/format/Time.java
@@ -437,6 +437,9 @@
      * @throws android.util.TimeFormatException if s cannot be parsed.
      */
     public boolean parse(String s) {
+        if (s == null) {
+            throw new NullPointerException("time string is null");
+        }
         if (nativeParse(s)) {
             timezone = TIMEZONE_UTC;
             return true;
diff --git a/core/java/android/util/Pair.java b/core/java/android/util/Pair.java
index bf25306..6027d08 100644
--- a/core/java/android/util/Pair.java
+++ b/core/java/android/util/Pair.java
@@ -16,6 +16,8 @@
 
 package android.util;
 
+import libcore.util.Objects;
+
 /**
  * Container to ease passing around a tuple of two objects. This object provides a sensible
  * implementation of equals(), returning true if equals() is true on each of the contained
@@ -26,8 +28,8 @@
     public final S second;
 
     /**
-     * Constructor for a Pair. If either are null then equals() and hashCode() will throw
-     * a NullPointerException.
+     * Constructor for a Pair.
+     *
      * @param first the first object in the Pair
      * @param second the second object in the pair
      */
@@ -37,31 +39,30 @@
     }
 
     /**
-     * Checks the two objects for equality by delegating to their respective equals() methods.
-     * @param o the Pair to which this one is to be checked for equality
-     * @return true if the underlying objects of the Pair are both considered equals()
+     * Checks the two objects for equality by delegating to their respective
+     * {@link Object#equals(Object)} methods.
+     *
+     * @param o the {@link Pair} to which this one is to be checked for equality
+     * @return true if the underlying objects of the Pair are both considered
+     *         equal
      */
+    @Override
     public boolean equals(Object o) {
-        if (o == this) return true;
-        if (!(o instanceof Pair)) return false;
-        final Pair<F, S> other;
-        try {
-            other = (Pair<F, S>) o;
-        } catch (ClassCastException e) {
+        if (!(o instanceof Pair)) {
             return false;
         }
-        return first.equals(other.first) && second.equals(other.second);
+        Pair<?, ?> p = (Pair<?, ?>) o;
+        return Objects.equal(p.first, first) && Objects.equal(p.second, second);
     }
 
     /**
      * Compute a hash code using the hash codes of the underlying objects
+     *
      * @return a hashcode of the Pair
      */
+    @Override
     public int hashCode() {
-        int result = 17;
-        result = 31 * result + first.hashCode();
-        result = 31 * result + second.hashCode();
-        return result;
+        return (first == null ? 0 : first.hashCode()) ^ (second == null ? 0 : second.hashCode());
     }
 
     /**
diff --git a/core/java/android/view/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java
index f4ab133..869cd00 100644
--- a/core/java/android/view/GLES20Canvas.java
+++ b/core/java/android/view/GLES20Canvas.java
@@ -152,6 +152,8 @@
     static native int nCreateLayer(int width, int height, boolean isOpaque, int[] layerInfo);
     static native void nResizeLayer(int layerId, int width, int height, int[] layerInfo);
     static native void nSetOpaqueLayer(int layerId, boolean isOpaque);
+    static native void nSetLayerPaint(int layerId, int nativePaint);
+    static native void nSetLayerColorFilter(int layerId, int nativeColorFilter);
     static native void nUpdateTextureLayer(int layerId, int width, int height, boolean opaque,
             SurfaceTexture surface);
     static native void nSetTextureLayerTransform(int layerId, int matrix);
@@ -394,13 +396,8 @@
     
     void drawHardwareLayer(HardwareLayer layer, float x, float y, Paint paint) {
         final GLES20Layer glLayer = (GLES20Layer) layer;
-        int modifier = paint != null ? setupColorFilter(paint) : MODIFIER_NONE;
-        try {
-            final int nativePaint = paint == null ? 0 : paint.mNativePaint;
-            nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint);
-        } finally {
-            if (modifier != MODIFIER_NONE) nResetModifiers(mRenderer, modifier);
-        }
+        final int nativePaint = paint == null ? 0 : paint.mNativePaint;
+        nDrawLayer(mRenderer, glLayer.getLayer(), x, y, nativePaint);
     }
 
     private static native void nDrawLayer(int renderer, int layer, float x, float y, int paint);
diff --git a/core/java/android/view/GLES20Layer.java b/core/java/android/view/GLES20Layer.java
index a0ae379..a462ed6 100644
--- a/core/java/android/view/GLES20Layer.java
+++ b/core/java/android/view/GLES20Layer.java
@@ -18,6 +18,7 @@
 package android.view;
 
 import android.graphics.Bitmap;
+import android.graphics.Paint;
 
 /**
  * An OpenGL ES 2.0 implementation of {@link HardwareLayer}.
@@ -43,6 +44,15 @@
     }
 
     @Override
+    void setLayerPaint(Paint paint) {
+        if (paint != null) {
+            GLES20Canvas.nSetLayerPaint(mLayer, paint.mNativePaint);
+            GLES20Canvas.nSetLayerColorFilter(mLayer, paint.getColorFilter() != null ?
+                    paint.getColorFilter().nativeColorFilter : 0);
+        }
+    }
+
+    @Override
     boolean copyInto(Bitmap bitmap) {
         return GLES20Canvas.nCopyLayer(mLayer, bitmap.mNativeBitmap);
     }
diff --git a/core/java/android/view/HardwareLayer.java b/core/java/android/view/HardwareLayer.java
index 06c6e7c..6e763b2 100644
--- a/core/java/android/view/HardwareLayer.java
+++ b/core/java/android/view/HardwareLayer.java
@@ -19,6 +19,7 @@
 import android.graphics.Bitmap;
 import android.graphics.Canvas;
 import android.graphics.Matrix;
+import android.graphics.Paint;
 import android.graphics.Rect;
 
 /**
@@ -62,6 +63,14 @@
     }
 
     /**
+     * Update the paint used when drawing this layer.
+     *
+     * @param paint The paint used when the layer is drawn into the destination canvas.
+     * @see View#setLayerPaint(android.graphics.Paint)
+     */
+    void setLayerPaint(Paint paint) {}
+
+    /**
      * Returns the minimum width of the layer.
      * 
      * @return The minimum desired width of the hardware layer 
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index db05e10..8f4626f 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -259,7 +259,7 @@
     private static native IBinder nativeGetBuiltInDisplay(int physicalDisplayId);
     private static native IBinder nativeCreateDisplay(String name);
     private static native void nativeSetDisplaySurface(
-            IBinder displayToken, SurfaceTexture surfaceTexture);
+            IBinder displayToken, Surface surface);
     private static native void nativeSetDisplayLayerStack(
             IBinder displayToken, int layerStack);
     private static native void nativeSetDisplayProjection(
@@ -597,11 +597,11 @@
     }
 
     /** @hide */
-    public static void setDisplaySurface(IBinder displayToken, SurfaceTexture surfaceTexture) {
+    public static void setDisplaySurface(IBinder displayToken, Surface surface) {
         if (displayToken == null) {
             throw new IllegalArgumentException("displayToken must not be null");
         }
-        nativeSetDisplaySurface(displayToken, surfaceTexture);
+        nativeSetDisplaySurface(displayToken, surface);
     }
 
     /** @hide */
diff --git a/core/java/android/view/TextureView.java b/core/java/android/view/TextureView.java
index fe14c88..7e335f0 100644
--- a/core/java/android/view/TextureView.java
+++ b/core/java/android/view/TextureView.java
@@ -367,6 +367,7 @@
             if (mListener != null && !mUpdateSurface) {
                 mListener.onSurfaceTextureAvailable(mSurface, getWidth(), getHeight());
             }
+            mLayer.setLayerPaint(mLayerPaint);
         }
 
         if (mUpdateSurface) {
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index db6a77e..236adab 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -11364,6 +11364,7 @@
         resolveLayoutParams();
         resolveTextDirection();
         resolveTextAlignment();
+        resolveDrawables();
     }
 
     /**
@@ -11931,13 +11932,13 @@
      * {@link #setAlpha(float)}, the alpha value of the layer's paint is replaced by
      * this view's alpha value. Calling {@link #setAlpha(float)} is therefore
      * equivalent to setting a hardware layer on this view and providing a paint with
-     * the desired alpha value.<p>
+     * the desired alpha value.</p>
      *
      * <p>Refer to the documentation of {@link #LAYER_TYPE_NONE disabled},
      * {@link #LAYER_TYPE_SOFTWARE software} and {@link #LAYER_TYPE_HARDWARE hardware}
      * for more information on when and how to use layers.</p>
      *
-     * @param layerType The ype of layer to use with this view, must be one of
+     * @param layerType The type of layer to use with this view, must be one of
      *        {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
      *        {@link #LAYER_TYPE_HARDWARE}
      * @param paint The paint used to compose the layer. This argument is optional
@@ -11989,6 +11990,50 @@
     }
 
     /**
+     * Updates the {@link Paint} object used with the current layer (used only if the current
+     * layer type is not set to {@link #LAYER_TYPE_NONE}). Changed properties of the Paint
+     * provided to {@link #setLayerType(int, android.graphics.Paint)} will be used the next time
+     * the View is redrawn, but {@link #setLayerPaint(android.graphics.Paint)} must be called to
+     * ensure that the view gets redrawn immediately.
+     *
+     * <p>A layer is associated with an optional {@link android.graphics.Paint}
+     * instance that controls how the layer is composed on screen. The following
+     * properties of the paint are taken into account when composing the layer:</p>
+     * <ul>
+     * <li>{@link android.graphics.Paint#getAlpha() Translucency (alpha)}</li>
+     * <li>{@link android.graphics.Paint#getXfermode() Blending mode}</li>
+     * <li>{@link android.graphics.Paint#getColorFilter() Color filter}</li>
+     * </ul>
+     *
+     * <p>If this view has an alpha value set to < 1.0 by calling
+     * {@link #setAlpha(float)}, the alpha value of the layer's paint is replaced by
+     * this view's alpha value. Calling {@link #setAlpha(float)} is therefore
+     * equivalent to setting a hardware layer on this view and providing a paint with
+     * the desired alpha value.</p>
+     *
+     * @param paint The paint used to compose the layer. This argument is optional
+     *        and can be null. It is ignored when the layer type is
+     *        {@link #LAYER_TYPE_NONE}
+     *
+     * @see #setLayerType(int, android.graphics.Paint)
+     */
+    public void setLayerPaint(Paint paint) {
+        int layerType = getLayerType();
+        if (layerType != LAYER_TYPE_NONE) {
+            mLayerPaint = paint == null ? new Paint() : paint;
+            if (layerType == LAYER_TYPE_HARDWARE) {
+                HardwareLayer layer = getHardwareLayer();
+                if (layer != null) {
+                    layer.setLayerPaint(paint);
+                }
+                invalidateViewProperty(false, false);
+            } else {
+                invalidate();
+            }
+        }
+    }
+
+    /**
      * Indicates whether this view has a static layer. A view with layer type
      * {@link #LAYER_TYPE_NONE} is a static layer. Other types of layers are
      * dynamic.
@@ -12100,6 +12145,7 @@
             if (!mHardwareLayer.isValid()) {
                 return null;
             }
+            mHardwareLayer.setLayerPaint(mLayerPaint);
 
             mHardwareLayer.redraw(getHardwareLayerDisplayList(mHardwareLayer), mLocalDirtyRect);
             mLocalDirtyRect.setEmpty();
@@ -14117,7 +14163,7 @@
     @RemotableViewMethod
     public void setBackgroundColor(int color) {
         if (mBackground instanceof ColorDrawable) {
-            ((ColorDrawable) mBackground).setColor(color);
+            ((ColorDrawable) mBackground.mutate()).setColor(color);
             computeOpaqueFlags();
         } else {
             setBackground(new ColorDrawable(color));
diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java
index 76ec374..9b93805 100644
--- a/core/java/android/webkit/HTML5VideoFullScreen.java
+++ b/core/java/android/webkit/HTML5VideoFullScreen.java
@@ -93,6 +93,7 @@
     private boolean mPlayingWhenDestroyed = false;
     SurfaceHolder.Callback mSHCallback = new SurfaceHolder.Callback()
     {
+        @Override
         public void surfaceChanged(SurfaceHolder holder, int format,
                                     int w, int h)
         {
@@ -106,6 +107,7 @@
             }
         }
 
+        @Override
         public void surfaceCreated(SurfaceHolder holder)
         {
             mSurfaceHolder = holder;
@@ -114,6 +116,7 @@
             prepareForFullScreen();
         }
 
+        @Override
         public void surfaceDestroyed(SurfaceHolder holder)
         {
             mPlayingWhenDestroyed = mPlayer.isPlaying();
@@ -233,12 +236,14 @@
 
     }
 
+    @Override
     public boolean fullScreenExited() {
         return (mLayout == null);
     }
 
     private final WebChromeClient.CustomViewCallback mCallback =
         new WebChromeClient.CustomViewCallback() {
+            @Override
             public void onCustomViewHidden() {
                 // It listens to SurfaceHolder.Callback.SurfaceDestroyed event
                 // which happens when the video view is detached from its parent
@@ -274,7 +279,7 @@
         mVideoSurfaceView.setFocusable(true);
         mVideoSurfaceView.setFocusableInTouchMode(true);
         mVideoSurfaceView.requestFocus();
-
+        mVideoSurfaceView.setOnKeyListener(mProxy);
         // Create a FrameLayout that will contain the VideoView and the
         // progress view (if any).
         mLayout = new FrameLayout(mProxy.getContext());
@@ -306,6 +311,7 @@
      * @return true when we are in full screen mode, even the surface not fully
      * created.
      */
+    @Override
     public boolean isFullScreenMode() {
         return true;
     }
@@ -344,6 +350,7 @@
     // Other listeners functions:
     private MediaPlayer.OnBufferingUpdateListener mBufferingUpdateListener =
         new MediaPlayer.OnBufferingUpdateListener() {
+        @Override
         public void onBufferingUpdate(MediaPlayer mp, int percent) {
             mCurrentBufferPercentage = percent;
         }
diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java
index 96240ef..2ab2ab9 100644
--- a/core/java/android/webkit/HTML5VideoInline.java
+++ b/core/java/android/webkit/HTML5VideoInline.java
@@ -19,7 +19,6 @@
 import android.Manifest.permission;
 import android.content.pm.PackageManager;
 import android.graphics.SurfaceTexture;
-import android.media.MediaPlayer;
 import android.webkit.HTML5VideoView;
 import android.webkit.HTML5VideoViewProxy;
 import android.view.Surface;
diff --git a/core/java/android/webkit/HTML5VideoView.java b/core/java/android/webkit/HTML5VideoView.java
index 8f9f8eb..0e8a5db 100644
--- a/core/java/android/webkit/HTML5VideoView.java
+++ b/core/java/android/webkit/HTML5VideoView.java
@@ -16,11 +16,8 @@
 
 package android.webkit;
 
-import android.graphics.SurfaceTexture;
 import android.media.MediaPlayer;
 import android.net.Uri;
-import android.util.Log;
-import android.view.SurfaceView;
 import android.webkit.HTML5VideoViewProxy;
 import java.io.IOException;
 import java.util.HashMap;
diff --git a/core/java/android/webkit/HTML5VideoViewProxy.java b/core/java/android/webkit/HTML5VideoViewProxy.java
index 701ef35..a3d62ae 100644
--- a/core/java/android/webkit/HTML5VideoViewProxy.java
+++ b/core/java/android/webkit/HTML5VideoViewProxy.java
@@ -31,6 +31,8 @@
 import android.os.Looper;
 import android.os.Message;
 import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
 
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -47,7 +49,8 @@
                           MediaPlayer.OnCompletionListener,
                           MediaPlayer.OnErrorListener,
                           MediaPlayer.OnInfoListener,
-                          SurfaceTexture.OnFrameAvailableListener {
+                          SurfaceTexture.OnFrameAvailableListener,
+                          View.OnKeyListener {
     // Logging tag.
     private static final String LOGTAG = "HTML5VideoViewProxy";
 
@@ -94,9 +97,6 @@
         private static HTML5VideoView mHTML5VideoView;
 
         private static boolean isVideoSelfEnded = false;
-        // By using the baseLayer and the current video Layer ID, we can
-        // identify the exact layer on the UI thread to use the SurfaceTexture.
-        private static int mBaseLayer = 0;
 
         private static void setPlayerBuffering(boolean playerBuffering) {
             mHTML5VideoView.setPlayerBuffering(playerBuffering);
@@ -106,7 +106,6 @@
         // When we found the Video layer, then we set the Surface Texture to it.
         // Otherwise, we may want to delete the Surface Texture to save memory.
         public static void setBaseLayer(int layer) {
-            mBaseLayer = layer;
             // Don't do this for full screen mode.
             if (mHTML5VideoView != null
                 && !mHTML5VideoView.isFullScreenMode()
@@ -303,6 +302,7 @@
 
     // A bunch event listeners for our VideoView
     // MediaPlayer.OnPreparedListener
+    @Override
     public void onPrepared(MediaPlayer mp) {
         VideoPlayer.onPrepared();
         Message msg = Message.obtain(mWebCoreHandler, PREPARED);
@@ -315,6 +315,7 @@
     }
 
     // MediaPlayer.OnCompletionListener;
+    @Override
     public void onCompletion(MediaPlayer mp) {
         // The video ended by itself, so we need to
         // send a message to the UI thread to dismiss
@@ -324,6 +325,7 @@
     }
 
     // MediaPlayer.OnErrorListener
+    @Override
     public boolean onError(MediaPlayer mp, int what, int extra) {
         sendMessage(obtainMessage(ERROR));
         return false;
@@ -489,6 +491,7 @@
             releaseQueue();
         }
         // EventHandler methods. Executed on the network thread.
+        @Override
         public void status(int major_version,
                 int minor_version,
                 int code,
@@ -496,10 +499,12 @@
             mStatusCode = code;
         }
 
+        @Override
         public void headers(Headers headers) {
             mHeaders = headers;
         }
 
+        @Override
         public void data(byte[] data, int len) {
             if (mPosterBytes == null) {
                 mPosterBytes = new ByteArrayOutputStream();
@@ -507,6 +512,7 @@
             mPosterBytes.write(data, 0, len);
         }
 
+        @Override
         public void endData() {
             if (mStatusCode == 200) {
                 if (mPosterBytes.size() > 0) {
@@ -524,6 +530,7 @@
                 }
                 if (mUrl != null) {
                     mHandler.post(new Runnable() {
+                       @Override
                        public void run() {
                            if (mRequestHandle != null) {
                                mRequestHandle.setupRedirect(mUrl.toString(), mStatusCode,
@@ -535,14 +542,17 @@
             }
         }
 
+        @Override
         public void certificate(SslCertificate certificate) {
             // Don't care.
         }
 
+        @Override
         public void error(int id, String description) {
             cleanup();
         }
 
+        @Override
         public boolean handleSslErrorRequest(SslError error) {
             // Don't care. If this happens, data() will never be called so
             // mPosterBytes will never be created, so no need to call cleanup.
@@ -794,4 +804,17 @@
         }
         return false;
     }
+
+    @Override
+    public boolean onKey(View v, int keyCode, KeyEvent event) {
+        if (keyCode == KeyEvent.KEYCODE_BACK) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                return true;
+            } else if (event.getAction() == KeyEvent.ACTION_UP && !event.isCanceled()) {
+                VideoPlayer.exitFullScreenVideo(this, mWebView);
+                return true;
+            }
+        }
+        return false;
+    }
 }
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index e217e4f..34f78c6 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -107,6 +107,9 @@
         }
         if (thumb != null) {
             thumb.setCallback(this);
+            if (canResolveLayoutDirection()) {
+                thumb.setLayoutDirection(getResolvedLayoutDirection());
+            }
 
             // Assuming the thumb drawable is symmetric, set the thumb offset
             // such that the thumb will hang halfway off either edge of the
@@ -303,7 +306,16 @@
         // Canvas will be translated, so 0,0 is where we start drawing
         thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound);
     }
-    
+
+    @Override
+    public void onResolveDrawables(int layoutDirection) {
+        super.onResolveDrawables(layoutDirection);
+
+        if (mThumb != null) {
+            mThumb.setLayoutDirection(layoutDirection);
+        }
+    }
+
     @Override
     protected synchronized void onDraw(Canvas canvas) {
         super.onDraw(canvas);
@@ -409,15 +421,25 @@
         int x = (int)event.getX();
         float scale;
         float progress = 0;
-        if (x < mPaddingLeft) {
-            scale = 0.0f;
-        } else if (x > width - mPaddingRight) {
-            scale = 1.0f;
+        if (isLayoutRtl()) {
+            if (x > width - mPaddingRight) {
+                scale = 0.0f;
+            } else if (x < mPaddingLeft) {
+                scale = 1.0f;
+            } else {
+                scale = (float)(available - x + mPaddingLeft) / (float)available;
+                progress = mTouchProgressOffset;
+            }
         } else {
-            scale = (float)(x - mPaddingLeft) / (float)available;
-            progress = mTouchProgressOffset;
+            if (x < mPaddingLeft) {
+                scale = 0.0f;
+            } else if (x > width - mPaddingRight) {
+                scale = 1.0f;
+            } else {
+                scale = (float)(x - mPaddingLeft) / (float)available;
+                progress = mTouchProgressOffset;
+            }
         }
-        
         final int max = getMax();
         progress += scale * max;
         
diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java
index e011c13..938979a 100644
--- a/core/java/android/widget/ListView.java
+++ b/core/java/android/widget/ListView.java
@@ -29,6 +29,7 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.util.AttributeSet;
+import android.util.MathUtils;
 import android.util.SparseBooleanArray;
 import android.view.FocusFinder;
 import android.view.KeyEvent;
@@ -1490,6 +1491,10 @@
 
             View focusLayoutRestoreView = null;
 
+            AccessibilityNodeInfo accessibilityFocusLayoutRestoreNode = null;
+            View accessibilityFocusLayoutRestoreView = null;
+            int accessibilityFocusPosition = INVALID_POSITION;
+
             // Remember stuff we will need down below
             switch (mLayoutMode) {
             case LAYOUT_SET_SELECTION:
@@ -1584,6 +1589,25 @@
                 requestFocus();
             }
 
+            // Remember which child, if any, had accessibility focus.
+            final View accessFocusedView = getViewRootImpl().getAccessibilityFocusedHost();
+            if (accessFocusedView != null) {
+                final View accessFocusedChild = findAccessibilityFocusedChild(accessFocusedView);
+                if (accessFocusedChild != null) {
+                    if (!dataChanged || isDirectChildHeaderOrFooter(accessFocusedChild)) {
+                        // If the views won't be changing, try to maintain focus
+                        // on the current view host and (if applicable) its
+                        // virtual view.
+                        accessibilityFocusLayoutRestoreView = accessFocusedView;
+                        accessibilityFocusLayoutRestoreNode = getViewRootImpl()
+                                .getAccessibilityFocusedVirtualView();
+                    } else {
+                        // Otherwise, try to maintain focus at the same position.
+                        accessibilityFocusPosition = getPositionForView(accessFocusedChild);
+                    }
+                }
+            }
+
             // Clear out old views
             detachAllViewsFromParent();
             recycleBin.removeSkippedScrap();
@@ -1682,6 +1706,22 @@
                 }
             }
 
+            // Attempt to restore accessibility focus.
+            if (accessibilityFocusLayoutRestoreNode != null) {
+                accessibilityFocusLayoutRestoreNode.performAction(
+                        AccessibilityNodeInfo.ACTION_ACCESSIBILITY_FOCUS);
+            } else if (accessibilityFocusLayoutRestoreView != null) {
+                accessibilityFocusLayoutRestoreView.requestAccessibilityFocus();
+            } else if (accessibilityFocusPosition != INVALID_POSITION) {
+                // Bound the position within the visible children.
+                final int position = MathUtils.constrain(
+                        (accessibilityFocusPosition - mFirstPosition), 0, (getChildCount() - 1));
+                final View restoreView = getChildAt(position);
+                if (restoreView != null) {
+                    restoreView.requestAccessibilityFocus();
+                }
+            }
+
             // tell focus view we are done mucking with it, if it is still in
             // our view hierarchy.
             if (focusLayoutRestoreView != null
@@ -1713,6 +1753,22 @@
     }
 
     /**
+     * @param focusedView the view that has accessibility focus.
+     * @return the direct child that contains accessibility focus.
+     */
+    private View findAccessibilityFocusedChild(View focusedView) {
+        ViewParent viewParent = focusedView.getParent();
+        while ((viewParent instanceof View) && (viewParent != this)) {
+            focusedView = (View) viewParent;
+            viewParent = viewParent.getParent();
+        }
+        if (!(viewParent instanceof View)) {
+            return null;
+        }
+        return focusedView;
+    }
+
+    /**
      * @param child a direct child of this list.
      * @return Whether child is a header or footer view.
      */
diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java
index eabcb80..dbc777e 100644
--- a/core/java/android/widget/NumberPicker.java
+++ b/core/java/android/widget/NumberPicker.java
@@ -2305,22 +2305,26 @@
         }
 
         private void sendAccessibilityEventForVirtualText(int eventType) {
-            AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
-            mInputText.onInitializeAccessibilityEvent(event);
-            mInputText.onPopulateAccessibilityEvent(event);
-            event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
-            requestSendAccessibilityEvent(NumberPicker.this, event);
+            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+                AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+                mInputText.onInitializeAccessibilityEvent(event);
+                mInputText.onPopulateAccessibilityEvent(event);
+                event.setSource(NumberPicker.this, VIRTUAL_VIEW_ID_INPUT);
+                requestSendAccessibilityEvent(NumberPicker.this, event);
+            }
         }
 
         private void sendAccessibilityEventForVirtualButton(int virtualViewId, int eventType,
                 String text) {
-            AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
-            event.setClassName(Button.class.getName());
-            event.setPackageName(mContext.getPackageName());
-            event.getText().add(text);
-            event.setEnabled(NumberPicker.this.isEnabled());
-            event.setSource(NumberPicker.this, virtualViewId);
-            requestSendAccessibilityEvent(NumberPicker.this, event);
+            if (AccessibilityManager.getInstance(mContext).isEnabled()) {
+                AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
+                event.setClassName(Button.class.getName());
+                event.setPackageName(mContext.getPackageName());
+                event.getText().add(text);
+                event.setEnabled(NumberPicker.this.isEnabled());
+                event.setSource(NumberPicker.this, virtualViewId);
+                requestSendAccessibilityEvent(NumberPicker.this, event);
+            }
         }
 
         private void findAccessibilityNodeInfosByTextInChild(String searchedLowerCase,
diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java
index 860e583..b6d0995 100644
--- a/core/java/android/widget/ProgressBar.java
+++ b/core/java/android/widget/ProgressBar.java
@@ -478,8 +478,8 @@
             d.setCallback(this);
         }
         mIndeterminateDrawable = d;
-        if (mIndeterminateDrawable != null) {
-            mIndeterminateDrawable.setLayoutDirection(getLayoutDirection());
+        if (mIndeterminateDrawable != null && canResolveLayoutDirection()) {
+            mIndeterminateDrawable.setLayoutDirection(getResolvedLayoutDirection());
         }
         if (mIndeterminate) {
             mCurrentDrawable = d;
@@ -520,7 +520,9 @@
 
         if (d != null) {
             d.setCallback(this);
-            d.setLayoutDirection(getLayoutDirection());
+            if (canResolveLayoutDirection()) {
+                d.setLayoutDirection(getResolvedLayoutDirection());
+            }
 
             // Make sure the ProgressBar is always tall enough
             int drawableHeight = d.getMinimumHeight();
@@ -564,6 +566,20 @@
     }
 
     @Override
+    public void onResolveDrawables(int layoutDirection) {
+        final Drawable d = mCurrentDrawable;
+        if (d != null) {
+            d.setLayoutDirection(layoutDirection);
+        }
+        if (mIndeterminateDrawable != null) {
+            mIndeterminateDrawable.setLayoutDirection(layoutDirection);
+        }
+        if (mProgressDrawable != null) {
+            mProgressDrawable.setLayoutDirection(layoutDirection);
+        }
+    }
+
+    @Override
     public void postInvalidate() {
         if (!mNoInvalidate) {
             super.postInvalidate();
@@ -652,6 +668,9 @@
 
             if (d instanceof LayerDrawable) {
                 progressDrawable = ((LayerDrawable) d).findDrawableByLayerId(id);
+                if (progressDrawable != null && canResolveLayoutDirection()) {
+                    progressDrawable.setLayoutDirection(getResolvedLayoutDirection());
+                }
             }
 
             final int level = (int) (scale * MAX_LEVEL);
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 74aea76..dd2ff35 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -4463,9 +4463,6 @@
 
         mTemporaryDetach = false;
 
-        // Resolve drawables as the layout direction has been resolved
-        resolveDrawables();
-
         if (mEditor != null) mEditor.onAttachedToWindow();
     }
 
diff --git a/core/java/com/android/internal/util/DumpUtils.java b/core/java/com/android/internal/util/DumpUtils.java
new file mode 100644
index 0000000..7b8c582
--- /dev/null
+++ b/core/java/com/android/internal/util/DumpUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.os.Handler;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+/**
+ * Helper functions for dumping the state of system services.
+ */
+public final class DumpUtils {
+    private DumpUtils() {
+    }
+
+    /**
+     * Helper for dumping state owned by a handler thread.
+     *
+     * Because the caller might be holding an important lock that the handler is
+     * trying to acquire, we use a short timeout to avoid deadlocks.  The process
+     * is inelegant but this function is only used for debugging purposes.
+     */
+    public static void dumpAsync(Handler handler, final Dump dump, PrintWriter pw, long timeout) {
+        final StringWriter sw = new StringWriter();
+        if (handler.runWithScissors(new Runnable() {
+            @Override
+            public void run() {
+                PrintWriter lpw = new PrintWriter(sw);
+                dump.dump(lpw);
+                lpw.close();
+            }
+        }, timeout)) {
+            pw.print(sw.toString());
+        } else {
+            pw.println("... timed out");
+        }
+    }
+
+    public interface Dump {
+        void dump(PrintWriter pw);
+    }
+}
diff --git a/core/java/com/android/internal/util/IndentingPrintWriter.java b/core/java/com/android/internal/util/IndentingPrintWriter.java
index 699e9b3..dd5918b 100644
--- a/core/java/com/android/internal/util/IndentingPrintWriter.java
+++ b/core/java/com/android/internal/util/IndentingPrintWriter.java
@@ -28,7 +28,7 @@
     private final String mIndent;
 
     private StringBuilder mBuilder = new StringBuilder();
-    private String mCurrent = new String();
+    private char[] mCurrent;
     private boolean mEmptyLine = true;
 
     public IndentingPrintWriter(Writer writer, String indent) {
@@ -38,12 +38,12 @@
 
     public void increaseIndent() {
         mBuilder.append(mIndent);
-        mCurrent = mBuilder.toString();
+        mCurrent = null;
     }
 
     public void decreaseIndent() {
         mBuilder.delete(0, mIndent.length());
-        mCurrent = mBuilder.toString();
+        mCurrent = null;
     }
 
     public void printPair(String key, Object value) {
@@ -51,17 +51,35 @@
     }
 
     @Override
-    public void println() {
-        super.println();
-        mEmptyLine = true;
+    public void write(char[] buf, int offset, int count) {
+        final int bufferEnd = offset + count;
+        int lineStart = offset;
+        int lineEnd = offset;
+        while (lineEnd < bufferEnd) {
+            char ch = buf[lineEnd++];
+            if (ch == '\n') {
+                writeIndent();
+                super.write(buf, lineStart, lineEnd - lineStart);
+                lineStart = lineEnd;
+                mEmptyLine = true;
+            }
+        }
+
+        if (lineStart != lineEnd) {
+            writeIndent();
+            super.write(buf, lineStart, lineEnd - lineStart);
+        }
     }
 
-    @Override
-    public void write(char[] buf, int offset, int count) {
+    private void writeIndent() {
         if (mEmptyLine) {
             mEmptyLine = false;
-            super.print(mCurrent);
+            if (mBuilder.length() != 0) {
+                if (mCurrent == null) {
+                    mCurrent = mBuilder.toString().toCharArray();
+                }
+                super.write(mCurrent, 0, mCurrent.length);
+            }
         }
-        super.write(buf, offset, count);
     }
 }
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index 9d45479..5f6042d 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -126,6 +126,7 @@
 	android_media_AudioSystem.cpp \
 	android_media_AudioTrack.cpp \
 	android_media_JetPlayer.cpp \
+	android_media_RemoteDisplay.cpp \
 	android_media_ToneGenerator.cpp \
 	android_hardware_Camera.cpp \
 	android_hardware_SensorManager.cpp \
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 55563a8..27684d7 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -160,6 +160,7 @@
 extern int register_android_app_backup_FullBackup(JNIEnv *env);
 extern int register_android_app_ActivityThread(JNIEnv *env);
 extern int register_android_app_NativeActivity(JNIEnv *env);
+extern int register_android_media_RemoteDisplay(JNIEnv *env);
 extern int register_android_view_InputChannel(JNIEnv* env);
 extern int register_android_view_InputDevice(JNIEnv* env);
 extern int register_android_view_InputEventReceiver(JNIEnv* env);
@@ -1161,6 +1162,7 @@
     REG_JNI(register_android_media_AudioSystem),
     REG_JNI(register_android_media_AudioTrack),
     REG_JNI(register_android_media_JetPlayer),
+    REG_JNI(register_android_media_RemoteDisplay),
     REG_JNI(register_android_media_ToneGenerator),
 
     REG_JNI(register_android_opengl_classes),
diff --git a/core/jni/android_media_RemoteDisplay.cpp b/core/jni/android_media_RemoteDisplay.cpp
new file mode 100644
index 0000000..5d24f61
--- /dev/null
+++ b/core/jni/android_media_RemoteDisplay.cpp
@@ -0,0 +1,182 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "RemoteDisplay"
+
+#include "jni.h"
+#include "JNIHelp.h"
+
+#include "android_os_Parcel.h"
+#include "android_util_Binder.h"
+
+#include <android_runtime/AndroidRuntime.h>
+#include <android_runtime/android_view_Surface.h>
+
+#include <binder/IServiceManager.h>
+
+#include <gui/ISurfaceTexture.h>
+
+#include <media/IMediaPlayerService.h>
+#include <media/IRemoteDisplay.h>
+#include <media/IRemoteDisplayClient.h>
+
+#include <utils/Log.h>
+
+#include <ScopedUtfChars.h>
+
+namespace android {
+
+static struct {
+    jmethodID notifyDisplayConnected;
+    jmethodID notifyDisplayDisconnected;
+    jmethodID notifyDisplayError;
+} gRemoteDisplayClassInfo;
+
+// ----------------------------------------------------------------------------
+
+class NativeRemoteDisplayClient : public BnRemoteDisplayClient {
+public:
+    NativeRemoteDisplayClient(JNIEnv* env, jobject remoteDisplayObj) :
+            mRemoteDisplayObjGlobal(env->NewGlobalRef(remoteDisplayObj)) {
+    }
+
+protected:
+    ~NativeRemoteDisplayClient() {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+        env->DeleteGlobalRef(mRemoteDisplayObjGlobal);
+    }
+
+public:
+    virtual void onDisplayConnected(const sp<ISurfaceTexture>& surfaceTexture,
+            uint32_t width, uint32_t height, uint32_t flags) {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+        jobject surfaceObj = android_view_Surface_createFromISurfaceTexture(env, surfaceTexture);
+        if (surfaceObj == NULL) {
+            ALOGE("Could not create Surface from surface texture %p provided by media server.",
+                    surfaceTexture.get());
+            return;
+        }
+
+        env->CallVoidMethod(mRemoteDisplayObjGlobal,
+                gRemoteDisplayClassInfo.notifyDisplayConnected,
+                surfaceObj, width, height, flags);
+        env->DeleteLocalRef(surfaceObj);
+        checkAndClearExceptionFromCallback(env, "notifyDisplayConnected");
+    }
+
+    virtual void onDisplayDisconnected() {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+        env->CallVoidMethod(mRemoteDisplayObjGlobal,
+                gRemoteDisplayClassInfo.notifyDisplayDisconnected);
+        checkAndClearExceptionFromCallback(env, "notifyDisplayDisconnected");
+    }
+
+    virtual void onDisplayError(int32_t error) {
+        JNIEnv* env = AndroidRuntime::getJNIEnv();
+
+        env->CallVoidMethod(mRemoteDisplayObjGlobal,
+                gRemoteDisplayClassInfo.notifyDisplayError, error);
+        checkAndClearExceptionFromCallback(env, "notifyDisplayError");
+    }
+
+private:
+    jobject mRemoteDisplayObjGlobal;
+
+    static void checkAndClearExceptionFromCallback(JNIEnv* env, const char* methodName) {
+        if (env->ExceptionCheck()) {
+            ALOGE("An exception was thrown by callback '%s'.", methodName);
+            LOGE_EX(env);
+            env->ExceptionClear();
+        }
+    }
+};
+
+class NativeRemoteDisplay {
+public:
+    NativeRemoteDisplay(const sp<IRemoteDisplay>& display,
+            const sp<NativeRemoteDisplayClient>& client) :
+            mDisplay(display), mClient(client) {
+    }
+
+    ~NativeRemoteDisplay() {
+        mDisplay->dispose();
+    }
+
+private:
+    sp<IRemoteDisplay> mDisplay;
+    sp<NativeRemoteDisplayClient> mClient;
+};
+
+
+// ----------------------------------------------------------------------------
+
+static jint nativeListen(JNIEnv* env, jobject remoteDisplayObj, jstring ifaceStr) {
+    ScopedUtfChars iface(env, ifaceStr);
+
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(
+            sm->getService(String16("media.player")));
+    if (service == NULL) {
+        ALOGE("Could not obtain IMediaPlayerService from service manager");
+        return 0;
+    }
+
+    sp<NativeRemoteDisplayClient> client(new NativeRemoteDisplayClient(env, remoteDisplayObj));
+    sp<IRemoteDisplay> display = service->listenForRemoteDisplay(
+            client, String8(iface.c_str()));
+    if (display == NULL) {
+        ALOGE("Media player service rejected request to listen for remote display '%s'.",
+                iface.c_str());
+        return 0;
+    }
+
+    NativeRemoteDisplay* wrapper = new NativeRemoteDisplay(display, client);
+    return reinterpret_cast<jint>(wrapper);
+}
+
+static void nativeDispose(JNIEnv* env, jobject remoteDisplayObj, jint ptr) {
+    NativeRemoteDisplay* wrapper = reinterpret_cast<NativeRemoteDisplay*>(ptr);
+    delete wrapper;
+}
+
+// ----------------------------------------------------------------------------
+
+static JNINativeMethod gMethods[] = {
+    {"nativeListen", "(Ljava/lang/String;)I",
+            (void*)nativeListen },
+    {"nativeDispose", "(I)V",
+            (void*)nativeDispose },
+};
+
+int register_android_media_RemoteDisplay(JNIEnv* env)
+{
+    int err = AndroidRuntime::registerNativeMethods(env, "android/media/RemoteDisplay",
+            gMethods, NELEM(gMethods));
+
+    jclass clazz = env->FindClass("android/media/RemoteDisplay");
+    gRemoteDisplayClassInfo.notifyDisplayConnected =
+            env->GetMethodID(clazz, "notifyDisplayConnected",
+                    "(Landroid/view/Surface;III)V");
+    gRemoteDisplayClassInfo.notifyDisplayDisconnected =
+            env->GetMethodID(clazz, "notifyDisplayDisconnected", "()V");
+    gRemoteDisplayClassInfo.notifyDisplayError =
+            env->GetMethodID(clazz, "notifyDisplayError", "(I)V");
+    return err;
+}
+
+};
diff --git a/core/jni/android_text_format_Time.cpp b/core/jni/android_text_format_Time.cpp
index 776733c..bb09421 100644
--- a/core/jni/android_text_format_Time.cpp
+++ b/core/jni/android_text_format_Time.cpp
@@ -23,6 +23,7 @@
 #include "jni.h"
 #include "utils/misc.h"
 #include "android_runtime/AndroidRuntime.h"
+#include "ScopedStringChars.h"
 #include "TimeUtils.h"
 #include <nativehelper/JNIHelp.h>
 #include <cutils/tztime.h>
@@ -71,11 +72,10 @@
     t->t.tm_gmtoff = env->GetLongField(o, g_gmtoffField);
     bool allDay = env->GetBooleanField(o, g_allDayField);
     if (allDay &&
-	((t->t.tm_sec !=0) || (t->t.tm_min != 0) || (t->t.tm_hour != 0))) {
-        char msg[100];
-	sprintf(msg, "allDay is true but sec, min, hour are not 0.");
-	jniThrowException(env, "java/lang/IllegalArgumentException", msg);
-	return false;
+       ((t->t.tm_sec !=0) || (t->t.tm_min != 0) || (t->t.tm_hour != 0))) {
+        jniThrowException(env, "java/lang/IllegalArgumentException",
+                          "allDay is true but sec, min, hour are not 0.");
+        return false;
     }
     return true;
 }
@@ -308,7 +308,7 @@
 static jstring android_text_format_Time_toString(JNIEnv* env, jobject This)
 {
     Time t;
-    if (!java2time(env, &t, This)) return env->NewStringUTF("");;
+    if (!java2time(env, &t, This)) return env->NewStringUTF("");
     ACQUIRE_TIMEZONE(This, t)
 
     String8 r = t.toString();
@@ -360,32 +360,30 @@
 // ============================================================================
 // Just do this here because it's not worth recreating the strings
 
-static int get_char(JNIEnv* env, const jchar *s, int spos, int mul,
-                    bool *thrown)
+static int get_char(JNIEnv* env, const ScopedStringChars& s, int spos, int mul,
+                    bool* thrown)
 {
     jchar c = s[spos];
     if (c >= '0' && c <= '9') {
         return (c - '0') * mul;
     } else {
         if (!*thrown) {
-            char msg[100];
-            sprintf(msg, "Parse error at pos=%d", spos);
-            jniThrowException(env, "android/util/TimeFormatException", msg);
+            jniThrowExceptionFmt(env, "android/util/TimeFormatException",
+                                 "Parse error at pos=%d", spos);
             *thrown = true;
         }
         return 0;
     }
 }
 
-static bool check_char(JNIEnv* env, const jchar *s, int spos, jchar expected)
+static bool check_char(JNIEnv* env, const ScopedStringChars& s, int spos, jchar expected)
 {
     jchar c = s[spos];
     if (c != expected) {
-        char msg[100];
-	sprintf(msg, "Unexpected character 0x%02x at pos=%d.  Expected %c.", c, spos,
-		expected);
-	jniThrowException(env, "android/util/TimeFormatException", msg);
-	return false;
+        jniThrowExceptionFmt(env, "android/util/TimeFormatException",
+                             "Unexpected character 0x%02x at pos=%d.  Expected %c.",
+                             c, spos, expected);
+        return false;
     }
     return true;
 }
@@ -394,20 +392,19 @@
 static jboolean android_text_format_Time_parse(JNIEnv* env, jobject This, jstring strObj)
 {
     jsize len = env->GetStringLength(strObj);
-    const jchar *s = env->GetStringChars(strObj, NULL);
-
-    bool thrown = false;
-    int n;
-    jboolean inUtc = false;
-
     if (len < 8) {
-        char msg[100];
-        sprintf(msg, "String too short -- expected at least 8 characters.");
-	jniThrowException(env, "android/util/TimeFormatException", msg);
-	return false;
+        jniThrowException(env, "android/util/TimeFormatException",
+                          "String too short -- expected at least 8 characters.");
+        return false;
     }
 
+    jboolean inUtc = false;
+
+    ScopedStringChars s(env, strObj);
+
     // year
+    int n;
+    bool thrown = false;
     n = get_char(env, s, 0, 1000, &thrown);
     n += get_char(env, s, 1, 100, &thrown);
     n += get_char(env, s, 2, 10, &thrown);
@@ -454,7 +451,7 @@
         if (len > 15) {
             // Z
             if (!check_char(env, s, 15, 'Z')) return false;
-	    inUtc = true;
+            inUtc = true;
         }
     } else {
         env->SetBooleanField(This, g_allDayField, JNI_TRUE);
@@ -467,8 +464,7 @@
     env->SetIntField(This, g_ydayField, 0);
     env->SetIntField(This, g_isdstField, -1);
     env->SetLongField(This, g_gmtoffField, 0);
-    
-    env->ReleaseStringChars(strObj, s);
+
     return inUtc;
 }
 
@@ -477,19 +473,19 @@
                                            jstring strObj)
 {
     jsize len = env->GetStringLength(strObj);
-    const jchar *s = env->GetStringChars(strObj, NULL);
-
-    bool thrown = false;
-    int n;
-    jboolean inUtc = false;
-
     if (len < 10) {
         jniThrowException(env, "android/util/TimeFormatException",
-                "Time input is too short; must be at least 10 characters");
+                          "String too short --- expected at least 10 characters.");
         return false;
     }
 
+    jboolean inUtc = false;
+
+    ScopedStringChars s(env, strObj);
+
     // year
+    int n;
+    bool thrown = false;
     n = get_char(env, s, 0, 1000, &thrown);    
     n += get_char(env, s, 1, 100, &thrown);
     n += get_char(env, s, 2, 10, &thrown);
@@ -520,28 +516,28 @@
         // T
         if (!check_char(env, s, 10, 'T')) return false;
 
-	env->SetBooleanField(This, g_allDayField, JNI_FALSE);
+        env->SetBooleanField(This, g_allDayField, JNI_FALSE);
         // hour
         n = get_char(env, s, 11, 10, &thrown);
         n += get_char(env, s, 12, 1, &thrown);
         if (thrown) return false;
-	int hour = n;
+        int hour = n;
         // env->SetIntField(This, g_hourField, n);
-	
-	// :
-	if (!check_char(env, s, 13, ':')) return false;
 
-	// minute
+        // :
+        if (!check_char(env, s, 13, ':')) return false;
+
+        // minute
         n = get_char(env, s, 14, 10, &thrown);
         n += get_char(env, s, 15, 1, &thrown);
         if (thrown) return false;
-	int minute = n;
+        int minute = n;
         // env->SetIntField(This, g_minField, n);
 
-	// :
-	if (!check_char(env, s, 16, ':')) return false;
+        // :
+        if (!check_char(env, s, 16, ':')) return false;
 
-	// second
+        // second
         n = get_char(env, s, 17, 10, &thrown);
         n += get_char(env, s, 18, 1, &thrown);
         if (thrown) return false;
@@ -561,64 +557,63 @@
         if (len > tz_index) {
             char c = s[tz_index];
 
-	    // NOTE: the offset is meant to be subtracted to get from local time
-	    // to UTC.  we therefore use 1 for '-' and -1 for '+'.
-	    switch (c) {
-	    case 'Z':
-	        // Zulu time -- UTC
-	        offset = 0;
-		break;
-	    case '-': 
+            // NOTE: the offset is meant to be subtracted to get from local time
+            // to UTC.  we therefore use 1 for '-' and -1 for '+'.
+            switch (c) {
+            case 'Z':
+                // Zulu time -- UTC
+                offset = 0;
+                break;
+            case '-': 
                 offset = 1;
-	        break;
-	    case '+': 
+                break;
+            case '+': 
                 offset = -1;
-	        break;
-	    default:
-	        char msg[100];
-	        sprintf(msg, "Unexpected character 0x%02x at position %d.  Expected + or -",
-			c, tz_index);
-	        jniThrowException(env, "android/util/TimeFormatException", msg);
-	        return false;
-	    }
+                break;
+            default:
+                jniThrowExceptionFmt(env, "android/util/TimeFormatException",
+                                     "Unexpected character 0x%02x at position %d.  Expected + or -",
+                                     c, tz_index);
+                return false;
+            }
             inUtc = true;
 
-	    if (offset != 0) {
-	        if (len < tz_index + 6) {
-	            char msg[100];
-	            sprintf(msg, "Unexpected length; should be %d characters", tz_index + 6);
-	            jniThrowException(env, "android/util/TimeFormatException", msg);
-	            return false;
-	        }
+            if (offset != 0) {
+                if (len < tz_index + 6) {
+                    jniThrowExceptionFmt(env, "android/util/TimeFormatException",
+                                         "Unexpected length; should be %d characters",
+                                         tz_index + 6);
+                    return false;
+                }
 
-	        // hour
-	        n = get_char(env, s, tz_index + 1, 10, &thrown);
-		n += get_char(env, s, tz_index + 2, 1, &thrown);
-		if (thrown) return false;
-		n *= offset;
-		hour += n;
+                // hour
+                n = get_char(env, s, tz_index + 1, 10, &thrown);
+                n += get_char(env, s, tz_index + 2, 1, &thrown);
+                if (thrown) return false;
+                n *= offset;
+                hour += n;
 
-		// :
-		if (!check_char(env, s, tz_index + 3, ':')) return false;
-	    
-		// minute
-		n = get_char(env, s, tz_index + 4, 10, &thrown);
-		n += get_char(env, s, tz_index + 5, 1, &thrown);
-		if (thrown) return false;
-		n *= offset;
-		minute += n;
-	    }
-	}
-	env->SetIntField(This, g_hourField, hour);
+                // :
+                if (!check_char(env, s, tz_index + 3, ':')) return false;
+            
+                // minute
+                n = get_char(env, s, tz_index + 4, 10, &thrown);
+                n += get_char(env, s, tz_index + 5, 1, &thrown);
+                if (thrown) return false;
+                n *= offset;
+                minute += n;
+            }
+        }
+        env->SetIntField(This, g_hourField, hour);
         env->SetIntField(This, g_minField, minute);
 
-	if (offset != 0) {
-	    // we need to normalize after applying the hour and minute offsets
-	    android_text_format_Time_normalize(env, This, false /* use isdst */);
-	    // The timezone is set to UTC in the calling Java code.
-	}
+        if (offset != 0) {
+            // we need to normalize after applying the hour and minute offsets
+            android_text_format_Time_normalize(env, This, false /* use isdst */);
+            // The timezone is set to UTC in the calling Java code.
+        }
     } else {
-	env->SetBooleanField(This, g_allDayField, JNI_TRUE);
+        env->SetBooleanField(This, g_allDayField, JNI_TRUE);
         env->SetIntField(This, g_hourField, 0);
         env->SetIntField(This, g_minField, 0);
         env->SetIntField(This, g_secField, 0);
@@ -628,8 +623,7 @@
     env->SetIntField(This, g_ydayField, 0);
     env->SetIntField(This, g_isdstField, -1);
     env->SetLongField(This, g_gmtoffField, 0);
-    
-    env->ReleaseStringChars(strObj, s);
+
     return inUtc;
 }
 
diff --git a/core/jni/android_view_GLES20Canvas.cpp b/core/jni/android_view_GLES20Canvas.cpp
index 3538fef..7dbf9ec 100644
--- a/core/jni/android_view_GLES20Canvas.cpp
+++ b/core/jni/android_view_GLES20Canvas.cpp
@@ -776,6 +776,20 @@
     env->ReleaseIntArrayElements(layerInfo, storage, 0);
 }
 
+static void android_view_GLES20Canvas_setLayerPaint(JNIEnv* env, jobject clazz,
+        Layer* layer, SkPaint* paint) {
+    if (layer) {
+        layer->setPaint(paint);
+    }
+}
+
+static void android_view_GLES20Canvas_setLayerColorFilter(JNIEnv* env, jobject clazz,
+        Layer* layer, SkiaColorFilter* colorFilter) {
+    if (layer) {
+        layer->setColorFilter(colorFilter);
+    }
+}
+
 static void android_view_GLES20Canvas_setOpaqueLayer(JNIEnv* env, jobject clazz,
         Layer* layer, jboolean isOpaque) {
     if (layer) {
@@ -979,6 +993,8 @@
     { "nCreateLayerRenderer",    "(I)I",       (void*) android_view_GLES20Canvas_createLayerRenderer },
     { "nCreateLayer",            "(IIZ[I)I",   (void*) android_view_GLES20Canvas_createLayer },
     { "nResizeLayer",            "(III[I)V" ,  (void*) android_view_GLES20Canvas_resizeLayer },
+    { "nSetLayerPaint",          "(II)V",      (void*) android_view_GLES20Canvas_setLayerPaint },
+    { "nSetLayerColorFilter",    "(II)V",      (void*) android_view_GLES20Canvas_setLayerColorFilter },
     { "nSetOpaqueLayer",         "(IZ)V",      (void*) android_view_GLES20Canvas_setOpaqueLayer },
     { "nCreateTextureLayer",     "(Z[I)I",     (void*) android_view_GLES20Canvas_createTextureLayer },
     { "nUpdateTextureLayer",     "(IIIZLandroid/graphics/SurfaceTexture;)V",
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index 4fbfab6..90e85e6 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -66,6 +66,7 @@
     jfieldID mGenerationId;
     jfieldID mCanvas;
     jfieldID mCanvasSaveCount;
+    jmethodID ctor;
 } gSurfaceClassInfo;
 
 static struct {
@@ -231,6 +232,42 @@
     }
 }
 
+static sp<ISurfaceTexture> getISurfaceTexture(JNIEnv* env, jobject surfaceObj) {
+    if (surfaceObj) {
+        sp<Surface> surface(getSurface(env, surfaceObj));
+        if (surface != NULL) {
+            return surface->getSurfaceTexture();
+        }
+    }
+    return NULL;
+}
+
+jobject android_view_Surface_createFromISurfaceTexture(JNIEnv* env,
+        const sp<ISurfaceTexture>& surfaceTexture) {
+    if (surfaceTexture == NULL) {
+        return NULL;
+    }
+
+    sp<Surface> surface(new Surface(surfaceTexture));
+    if (surface == NULL) {
+        return NULL;
+    }
+
+    jobject surfaceObj = env->NewObject(gSurfaceClassInfo.clazz, gSurfaceClassInfo.ctor);
+    if (surfaceObj == NULL) {
+        if (env->ExceptionCheck()) {
+            ALOGE("Could not create instance of Surface from ISurfaceTexture.");
+            LOGE_EX(env);
+            env->ExceptionClear();
+        }
+        return NULL;
+    }
+
+    setSurface(env, surfaceObj, surface);
+    return surfaceObj;
+}
+
+
 // ----------------------------------------------------------------------------
 
 static void nativeCreate(JNIEnv* env, jobject surfaceObj, jobject sessionObj,
@@ -619,24 +656,12 @@
 }
 
 static void nativeSetDisplaySurface(JNIEnv* env, jclass clazz,
-        jobject tokenObj, jobject surfaceTextureObj) {
+        jobject tokenObj, jobject surfaceObj) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return;
 
-    if (!surfaceTextureObj) {
-        SurfaceComposerClient::setDisplaySurface(token, NULL);
-        return;
-    }
-
-    sp<SurfaceTexture> st(SurfaceTexture_getSurfaceTexture(env, surfaceTextureObj));
-    if (st == NULL) {
-        jniThrowException(env, "java/lang/IllegalArgumentException",
-                "SurfaceTexture has already been released");
-        return;
-    }
-
-    sp<ISurfaceTexture> bq = st->getBufferQueue();
-    SurfaceComposerClient::setDisplaySurface(token, bq);
+    sp<ISurfaceTexture> surfaceTexture(getISurfaceTexture(env, surfaceObj));
+    SurfaceComposerClient::setDisplaySurface(token, surfaceTexture);
 }
 
 static void nativeSetDisplayLayerStack(JNIEnv* env, jclass clazz,
@@ -648,23 +673,23 @@
 }
 
 static void nativeSetDisplayProjection(JNIEnv* env, jclass clazz,
-        jobject tokenObj, jint orientation, jobject rect1Obj, jobject rect2Obj) {
+        jobject tokenObj, jint orientation, jobject layerStackRectObj, jobject displayRectObj) {
     sp<IBinder> token(ibinderForJavaObject(env, tokenObj));
     if (token == NULL) return;
 
-    Rect rect1;
-    rect1.left = env->GetIntField(rect1Obj, gRectClassInfo.left);
-    rect1.top = env->GetIntField(rect1Obj, gRectClassInfo.top);
-    rect1.right = env->GetIntField(rect1Obj, gRectClassInfo.right);
-    rect1.bottom = env->GetIntField(rect1Obj, gRectClassInfo.bottom);
+    Rect layerStackRect;
+    layerStackRect.left = env->GetIntField(layerStackRectObj, gRectClassInfo.left);
+    layerStackRect.top = env->GetIntField(layerStackRectObj, gRectClassInfo.top);
+    layerStackRect.right = env->GetIntField(layerStackRectObj, gRectClassInfo.right);
+    layerStackRect.bottom = env->GetIntField(layerStackRectObj, gRectClassInfo.bottom);
 
-    Rect rect2;
-    rect2.left = env->GetIntField(rect2Obj, gRectClassInfo.left);
-    rect2.top = env->GetIntField(rect2Obj, gRectClassInfo.top);
-    rect2.right = env->GetIntField(rect2Obj, gRectClassInfo.right);
-    rect2.bottom = env->GetIntField(rect2Obj, gRectClassInfo.bottom);
+    Rect displayRect;
+    displayRect.left = env->GetIntField(displayRectObj, gRectClassInfo.left);
+    displayRect.top = env->GetIntField(displayRectObj, gRectClassInfo.top);
+    displayRect.right = env->GetIntField(displayRectObj, gRectClassInfo.right);
+    displayRect.bottom = env->GetIntField(displayRectObj, gRectClassInfo.bottom);
 
-    SurfaceComposerClient::setDisplayProjection(token, orientation, rect1, rect2);
+    SurfaceComposerClient::setDisplayProjection(token, orientation, layerStackRect, displayRect);
 }
 
 static jboolean nativeGetDisplayInfo(JNIEnv* env, jclass clazz,
@@ -800,7 +825,7 @@
             (void*)nativeGetBuiltInDisplay },
     {"nativeCreateDisplay", "(Ljava/lang/String;)Landroid/os/IBinder;",
             (void*)nativeCreateDisplay },
-    {"nativeSetDisplaySurface", "(Landroid/os/IBinder;Landroid/graphics/SurfaceTexture;)V",
+    {"nativeSetDisplaySurface", "(Landroid/os/IBinder;Landroid/view/Surface;)V",
             (void*)nativeSetDisplaySurface },
     {"nativeSetDisplayLayerStack", "(Landroid/os/IBinder;I)V",
             (void*)nativeSetDisplayLayerStack },
@@ -835,6 +860,7 @@
             env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvas", "Landroid/graphics/Canvas;");
     gSurfaceClassInfo.mCanvasSaveCount =
             env->GetFieldID(gSurfaceClassInfo.clazz, "mCanvasSaveCount", "I");
+    gSurfaceClassInfo.ctor = env->GetMethodID(gSurfaceClassInfo.clazz, "<init>", "()V");
 
     clazz = env->FindClass("android/graphics/Canvas");
     gCanvasClassInfo.mNativeCanvas = env->GetFieldID(clazz, "mNativeCanvas", "I");
diff --git a/core/res/res/drawable/progress_horizontal_holo_dark.xml b/core/res/res/drawable/progress_horizontal_holo_dark.xml
index ff270b3..bc1ecf3 100644
--- a/core/res/res/drawable/progress_horizontal_holo_dark.xml
+++ b/core/res/res/drawable/progress_horizontal_holo_dark.xml
@@ -21,11 +21,13 @@
 
     <item android:id="@android:id/secondaryProgress">
         <scale android:scaleWidth="100%"
+               android:scaleGravity="start"
                android:drawable="@android:drawable/progress_secondary_holo_dark" />
     </item>
 
     <item android:id="@android:id/progress">
         <scale android:scaleWidth="100%"
+               android:scaleGravity="start"
                android:drawable="@android:drawable/progress_primary_holo_dark" />
     </item>
 
diff --git a/core/res/res/drawable/progress_horizontal_holo_light.xml b/core/res/res/drawable/progress_horizontal_holo_light.xml
index 4935185..ee9b629 100644
--- a/core/res/res/drawable/progress_horizontal_holo_light.xml
+++ b/core/res/res/drawable/progress_horizontal_holo_light.xml
@@ -21,11 +21,13 @@
 
     <item android:id="@android:id/secondaryProgress">
         <scale android:scaleWidth="100%"
+               android:scaleGravity="start"
                android:drawable="@android:drawable/progress_secondary_holo_light" />
     </item>
 
     <item android:id="@android:id/progress">
         <scale android:scaleWidth="100%"
+               android:scaleGravity="start"
                android:drawable="@android:drawable/progress_primary_holo_light" />
     </item>
 
diff --git a/core/res/res/drawable/scrubber_progress_horizontal_holo_dark.xml b/core/res/res/drawable/scrubber_progress_horizontal_holo_dark.xml
index b117bb8..4d83191 100644
--- a/core/res/res/drawable/scrubber_progress_horizontal_holo_dark.xml
+++ b/core/res/res/drawable/scrubber_progress_horizontal_holo_dark.xml
@@ -19,10 +19,12 @@
             android:drawable="@android:drawable/scrubber_track_holo_dark" />
     <item android:id="@android:id/secondaryProgress">
         <scale android:scaleWidth="100%"
-                android:drawable="@android:drawable/scrubber_secondary_holo" />
+               android:scaleGravity="start"
+               android:drawable="@android:drawable/scrubber_secondary_holo" />
     </item>
     <item android:id="@android:id/progress">
         <scale android:scaleWidth="100%"
-                android:drawable="@android:drawable/scrubber_primary_holo" />
+               android:scaleGravity="start"
+               android:drawable="@android:drawable/scrubber_primary_holo" />
     </item>
 </layer-list>
diff --git a/core/res/res/drawable/scrubber_progress_horizontal_holo_light.xml b/core/res/res/drawable/scrubber_progress_horizontal_holo_light.xml
index 6cd08ea..a3461fa 100644
--- a/core/res/res/drawable/scrubber_progress_horizontal_holo_light.xml
+++ b/core/res/res/drawable/scrubber_progress_horizontal_holo_light.xml
@@ -19,10 +19,12 @@
             android:drawable="@android:drawable/scrubber_track_holo_light" />
     <item android:id="@android:id/secondaryProgress">
         <scale android:scaleWidth="100%"
-                android:drawable="@android:drawable/scrubber_secondary_holo" />
+               android:scaleGravity="start"
+               android:drawable="@android:drawable/scrubber_secondary_holo" />
     </item>
     <item android:id="@android:id/progress">
         <scale android:scaleWidth="100%"
-                android:drawable="@android:drawable/scrubber_primary_holo" />
+               android:scaleGravity="start"
+               android:drawable="@android:drawable/scrubber_primary_holo" />
     </item>
 </layer-list>
diff --git a/core/res/res/layout-land/keyguard_host_view.xml b/core/res/res/layout-land/keyguard_host_view.xml
index b404155..0028a54 100644
--- a/core/res/res/layout-land/keyguard_host_view.xml
+++ b/core/res/res/layout-land/keyguard_host_view.xml
@@ -45,13 +45,8 @@
         android:layout_weight="1"
         android:gravity="center">
 
+        <!-- SelectorView is always used, so add it here. The rest are loaded dynamically -->
         <include layout="@layout/keyguard_selector_view"/>
-        <include layout="@layout/keyguard_account_view"/>
-        <include layout="@layout/keyguard_pattern_view"/>
-        <include layout="@layout/keyguard_password_view"/>
-        <include layout="@layout/keyguard_sim_pin_view"/>
-        <include layout="@layout/keyguard_sim_puk_view"/>
-        <include layout="@layout/keyguard_face_unlock_view"/>
 
     </ViewFlipper>
 
diff --git a/core/res/res/layout-port/keyguard_host_view.xml b/core/res/res/layout-port/keyguard_host_view.xml
index 5dc2225..5e467d1 100644
--- a/core/res/res/layout-port/keyguard_host_view.xml
+++ b/core/res/res/layout-port/keyguard_host_view.xml
@@ -33,13 +33,8 @@
         android:layout_height="match_parent"
         android:gravity="center">
 
+        <!-- SelectorView is always used, so add it here. The rest are loaded dynamically -->
         <include layout="@layout/keyguard_selector_view"/>
-        <include layout="@layout/keyguard_account_view"/>
-        <include layout="@layout/keyguard_pattern_view"/>
-        <include layout="@layout/keyguard_password_view"/>
-        <include layout="@layout/keyguard_sim_pin_view"/>
-        <include layout="@layout/keyguard_sim_puk_view"/>
-        <include layout="@layout/keyguard_face_unlock_view"/>
 
     </ViewFlipper>
 
diff --git a/core/res/res/layout-sw600dp-land/keyguard_host_view.xml b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
index e77f584..5b6bb2f 100644
--- a/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
+++ b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
@@ -52,13 +52,8 @@
             android:layout_weight="1"
             android:gravity="center">
 
+            <!-- SelectorView is always used, so add it here. The rest are loaded dynamically -->
             <include layout="@layout/keyguard_selector_view"/>
-            <include layout="@layout/keyguard_account_view"/>
-            <include layout="@layout/keyguard_pattern_view"/>
-            <include layout="@layout/keyguard_password_view"/>
-            <include layout="@layout/keyguard_sim_pin_view"/>
-            <include layout="@layout/keyguard_sim_puk_view"/>
-            <include layout="@layout/keyguard_face_unlock_view"/>
 
         </ViewFlipper>
 
diff --git a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
index 082f481..397b881 100644
--- a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
+++ b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
@@ -47,13 +47,8 @@
         android:layout_weight="0.6"
         android:layout_gravity="center">
 
+        <!-- SelectorView is always used, so add it here. The rest are loaded dynamically -->
         <include layout="@layout/keyguard_selector_view"/>
-        <include layout="@layout/keyguard_account_view"/>
-        <include layout="@layout/keyguard_pattern_view"/>
-        <include layout="@layout/keyguard_password_view"/>
-        <include layout="@layout/keyguard_sim_pin_view"/>
-        <include layout="@layout/keyguard_sim_puk_view"/>
-        <include layout="@layout/keyguard_face_unlock_view"/>
 
     </ViewFlipper>
 
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 9601ad4..e6fd538 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -4079,6 +4079,10 @@
                  The clip will be based on the horizontal gravity: a left gravity will clip the right
                  edge, a right gravity will clip the left edge, and neither will clip both edges. -->
             <flag name="clip_horizontal" value="0x08" />
+            <!-- Push object to the beginning of its container, not changing its size. -->
+            <flag name="start" value="0x00800003" />
+            <!-- Push object to the end of its container, not changing its size. -->
+            <flag name="end" value="0x00800005" />
         </attr>
         <!-- Reference to a drawable resource to draw with the specified scale. -->
         <attr name="drawable" />
diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml
index 4db8cd1..9a8e712 100644
--- a/core/res/res/values/public.xml
+++ b/core/res/res/values/public.xml
@@ -486,6 +486,7 @@
   <java-symbol type="string" name="display_manager_hdmi_display_name" />
   <java-symbol type="string" name="display_manager_overlay_display_name" />
   <java-symbol type="string" name="display_manager_overlay_display_title" />
+  <java-symbol type="string" name="display_manager_wifi_display_name" />
   <java-symbol type="string" name="double_tap_toast" />
   <java-symbol type="string" name="elapsed_time_short_format_h_mm_ss" />
   <java-symbol type="string" name="elapsed_time_short_format_mm_ss" />
@@ -1366,6 +1367,13 @@
   <java-symbol type="layout" name="keyguard_screen_tab_unlock_land" />
   <java-symbol type="layout" name="keyguard_screen_unlock_landscape" />
   <java-symbol type="layout" name="keyguard_screen_unlock_portrait" />
+  <java-symbol type="layout" name="keyguard_selector_view" />
+  <java-symbol type="layout" name="keyguard_pattern_view" />
+  <java-symbol type="layout" name="keyguard_password_view" />
+  <java-symbol type="layout" name="keyguard_face_unlock_view" />
+  <java-symbol type="layout" name="keyguard_sim_pin_view" />
+  <java-symbol type="layout" name="keyguard_sim_puk_view" />
+  <java-symbol type="layout" name="keyguard_account_view" />
   <java-symbol type="layout" name="recent_apps_dialog" />
   <java-symbol type="layout" name="screen_action_bar" />
   <java-symbol type="layout" name="screen_action_bar_overlay" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 9f254b6..1e7e9fb 100755
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3678,6 +3678,9 @@
     <!-- Title text to show within the overlay.  [CHAR LIMIT=50] -->
     <string name="display_manager_overlay_display_title"><xliff:g id="name">%1$s</xliff:g>: <xliff:g id="width">%2$d</xliff:g>x<xliff:g id="height">%3$d</xliff:g>, <xliff:g id="dpi">%4$d</xliff:g> dpi</string>
 
+    <!-- Name of a wifi display.  [CHAR LIMIT=50] -->
+    <string name="display_manager_wifi_display_name">Wifi display: <xliff:g id="device">%1$s</xliff:g></string>
+
     <!-- Keyguard strings -->
     <!-- Label shown on emergency call button in keyguard -->
     <string name="kg_emergency_call_label">Emergency call</string>
diff --git a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
index a1fd14e..607150a 100755
--- a/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
+++ b/core/tests/coretests/src/android/content/pm/PackageManagerTests.java
@@ -395,6 +395,14 @@
                     assertTrue("The native library path (" + info.nativeLibraryDir
                             + ") should start with " + SECURE_CONTAINERS_PREFIX,
                             info.nativeLibraryDir.startsWith(SECURE_CONTAINERS_PREFIX));
+                    try {
+                        String compatLib = new File(info.dataDir + "/lib").getCanonicalPath();
+                        assertEquals("The compatibility lib directory should be a symbolic link to "
+                                + info.nativeLibraryDir,
+                                info.nativeLibraryDir, compatLib);
+                    } catch (IOException e) {
+                        fail("compat check: Can't read " + info.dataDir + "/lib");
+                    }
                 } else {
                     assertFalse((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) != 0);
                     assertEquals(srcPath, appInstallPath);
diff --git a/docs/downloads/training/BitmapFun.zip b/docs/downloads/training/BitmapFun.zip
index e7e71f9..e48bfd3 100644
--- a/docs/downloads/training/BitmapFun.zip
+++ b/docs/downloads/training/BitmapFun.zip
Binary files differ
diff --git a/docs/html/about/versions/jelly-bean.jd b/docs/html/about/versions/jelly-bean.jd
index db56fa4..485a1bb 100644
--- a/docs/html/about/versions/jelly-bean.jd
+++ b/docs/html/about/versions/jelly-bean.jd
@@ -1,29 +1,6 @@
 page.title=Android 4.1 for Developers
 @jd:body
 
-
-<!--<style type="text/css">
-#jd-content {
-  max-width:1024px;
-}
-#jd-content div.screenshot {
-  float:left;
-  clear:left;
-  padding:15px 30px 15px 0;
-}
-
-</style>
-
-<p></p>
-
-
-<div style="float:right;width:230px;padding:0px 0px 60px 34px;margin-top:-40px">
-<div>
-<img src="{@docRoot}images/android-jellybean-sm.png" xheight="402" width="280">
-</div>
-<p class="image-caption">Find out more about the Jelly Bean features for users at <a href="http://www.android.com">android.com</a></p>
-</div>-->
-
 <div style="float:right;width:320px;padding:0px 0px 0px 34px;clear:both">
 <div>
 <img src="{@docRoot}images/jb-android-4.1.png" height="426" width="390">
@@ -35,23 +12,9 @@
 improvements throughout the platform and added great new features
 for users and developers. This document provides a glimpse of what's new for developers.
 
-<p>See the <a href="{@docRoot}about/versions/android-4.1.html">Android 4.1 APIs</a> document for a detailed look at the new developer APIs,</p>
+<p>See the <a href="{@docRoot}about/versions/android-4.1.html">Android 4.1 APIs</a> document for a detailed look at the new developer APIs.</p>
 
-<!--
-<ul>
-  <li><a href="#performance">Fast, Smooth, Responsive</a></li>
-  <li><a href="#accessibility">Enhanced Accessibility</a></li>
-  <li><a href="#intl">Support for International Users</a></li>
-  <li><a href="#ui">Capabilities for Creating Beautiful UI</a></li>
-  <li><a href="#input">New Input Types and Capabilities</a></li>
-  <li><a href="#graphics">Animation and Graphics</a></li>
-  <li><a href="#connectivity">New Types of Connectivity</a></li>
-  <li><a href="#media">Media Capabilities</a></li>
-  <li><a href="#google">Google APIs and services</a></li>
-  </ul>
--->
-
-<p>Find out more about the Jelly Bean features for users at <a href="http://www.android.com/whatsnew">www.android.com</a></p>
+<p>Find out more about the Jelly Bean features for users at <a href="http://www.android.com/whatsnew">www.android.com</a>.</p>
 
 
 <h2 id="performance">Faster, Smoother, More Responsive</h2>
diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd
index a46f9a7..5a4e03a 100644
--- a/docs/html/guide/topics/appwidgets/index.jd
+++ b/docs/html/guide/topics/appwidgets/index.jd
@@ -307,6 +307,7 @@
   <li>{@link android.widget.FrameLayout}</li>
   <li>{@link android.widget.LinearLayout}</li>
   <li>{@link android.widget.RelativeLayout}</li>
+  <li>{@link android.widget.GridLayout}</li>
 </ul>
 
 <p>And the following widget classes:</p>
@@ -327,6 +328,9 @@
 
 <p>Descendants of these classes are not supported.</p>
 
+<p>RemoteViews also supports {@link android.view.ViewStub}, which is an invisible, zero-sized View you can use 
+to lazily inflate layout resources at runtime.</p>
+
 
 <h3 id="AddingMargins">Adding margins to App Widgets</h3>
 
@@ -410,6 +414,25 @@
 done.
     (See <a href="#Configuring">Creating an App Widget Configuration
 Activity</a> below.)</dd> 
+
+<dt>
+  {@link android.appwidget.AppWidgetProvider#onAppWidgetOptionsChanged onAppWidgetOptionsChanged()} 
+</dt>
+<dd>
+This is called when the widget is first placed and any time the widget is resized. You can use this callback to show or hide content based on the widget's size ranges. You get the size ranges by calling {@link android.appwidget.AppWidgetManager#getAppWidgetOptions getAppWidgetOptions()}, which returns a  {@link android.os.Bundle} that includes the following:<br /><br />
+<ul>
+  <li>{@link android.appwidget.AppWidgetManager#OPTION_APPWIDGET_MIN_WIDTH}&mdash;Contains 
+the lower bound on the current width, in dp units, of a widget instance.</li>
+  <li>{@link android.appwidget.AppWidgetManager#OPTION_APPWIDGET_MIN_HEIGHT}&mdash;Contains 
+the lower bound on the current height, in dp units, of a widget instance.</li>
+  <li>{@link android.appwidget.AppWidgetManager#OPTION_APPWIDGET_MAX_WIDTH}&mdash;Contains
+ the upper bound on the current width, in dp units, of a widget instance.</li>
+  <li>{@link android.appwidget.AppWidgetManager#OPTION_APPWIDGET_MAX_HEIGHT}&mdash;Contains 
+the upper bound on the current width, in dp units, of a widget instance.</li>
+</ul>
+
+This callback was introduced in API Level 16 (Android 4.1). If you implement this callback, make sure that your app doesn't depend on it since it won't be called on older devices.
+</dd>
   <dt>{@link android.appwidget.AppWidgetProvider#onDeleted(Context,int[])}</dt>
     <dd>This is called every time an App Widget is deleted from the App Widget
 host.</dd>
@@ -533,12 +556,13 @@
 to receive the App Widget broadcasts directly, you can implement your own 
 {@link android.content.BroadcastReceiver} or override the 
 {@link android.appwidget.AppWidgetProvider#onReceive(Context,Intent)} callback. 
-The four Intents you need to care about are:</p>
+The Intents you need to care about are as follows:</p>
 <ul>
   <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_UPDATE}</li>
   <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_DELETED}</li>
   <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_ENABLED}</li>
   <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_DISABLED}</li>
+  <li>{@link android.appwidget.AppWidgetManager#ACTION_APPWIDGET_OPTIONS_CHANGED}</li>
 </ul>
 
 
diff --git a/docs/html/guide/topics/resources/providing-resources.jd b/docs/html/guide/topics/resources/providing-resources.jd
index b0d5d6f..b2c95b9 100644
--- a/docs/html/guide/topics/resources/providing-resources.jd
+++ b/docs/html/guide/topics/resources/providing-resources.jd
@@ -329,6 +329,31 @@
 indicates the current locale.</p>
       </td>
     </tr>
+    <tr id="LayoutDirectionQualifier">
+      <td>Layout Direction</td>
+      <td>Examples:<br/>
+        <code>ldrtl</code><br/>
+        <code>ldltr</code><br/>
+      </td>
+      <td><p>The layout direction of your application. {@code ldrtl} means "layout-direction-right-to-left".
+      {@code ldltr} means "layout-direction-left-to-right" and is the default implicit value.
+      </p>
+      <p>This can apply to any resource like layouts or values or drawables.
+      </p>
+      <p>For example, if you want to provide some specific layout for the Arabic language and some
+      generic layout for any other "right-to-left" language (like Persian or Hebrew) then you would have:
+      </p>
+<pre class="classic no-pretty-print">
+res/
+    layout/   <span style="color:black">
+        main.xml  </span>(This is the default layout)
+    layout-ar/  <span style="color:black">
+        main.xml  </span>(This is the specific layout for Arabic)
+    layout-ldrtl/  <span style="color:black">
+        main.xml  </span>(This applies to any "right-to-left" language, except for Arabic, because the ar language qualifier has a higher precedence.)
+</pre>
+      </td>
+    </tr>
     <tr id="SmallestScreenWidthQualifier">
       <td>smallestWidth</td>
       <td><code>sw&lt;N&gt;dp</code><br/><br/>
diff --git a/docs/html/training/displaying-bitmaps/cache-bitmap.jd b/docs/html/training/displaying-bitmaps/cache-bitmap.jd
index 94abe21..2a333cc 100644
--- a/docs/html/training/displaying-bitmaps/cache-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/cache-bitmap.jd
@@ -96,7 +96,7 @@
 <p>Here’s an example of setting up a {@link android.util.LruCache} for bitmaps:</p>
 
 <pre>
-private LruCache<String, Bitmap> mMemoryCache;
+private LruCache&lt;String, Bitmap&gt; mMemoryCache;
 
 &#64;Override
 protected void onCreate(Bundle savedInstanceState) {
@@ -109,7 +109,7 @@
     // Use 1/8th of the available memory for this memory cache.
     final int cacheSize = 1024 * 1024 * memClass / 8;
 
-    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
+    mMemoryCache = new LruCache&lt;String, Bitmap&gt;(cacheSize) {
         &#64;Override
         protected int sizeOf(String key, Bitmap bitmap) {
             // The cache size will be measured in bytes rather than number of items.
@@ -159,7 +159,7 @@
 updated to add entries to the memory cache:</p>
 
 <pre>
-class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
+class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
     ...
     // Decode image in background.
     &#64;Override
@@ -179,7 +179,7 @@
 rely on images being available in this cache. Components like {@link android.widget.GridView} with
 larger datasets can easily fill up a memory cache. Your application could be interrupted by another
 task like a phone call, and while in the background it might be killed and the memory cache
-destroyed. Once the user resumes, your application it has to process each image again.</p>
+destroyed. Once the user resumes, your application has to process each image again.</p>
 
 <p>A disk cache can be used in these cases to persist processed bitmaps and help decrease loading
 times where images are no longer available in a memory cache. Of course, fetching images from disk
@@ -190,18 +190,14 @@
 appropriate place to store cached images if they are accessed more frequently, for example in an
 image gallery application.</p>
 
-<p>Included in the sample code of this class is a basic {@code DiskLruCache} implementation.
-However, a more robust and recommended {@code DiskLruCache} solution is included in the Android 4.0
-source code ({@code libcore/luni/src/main/java/libcore/io/DiskLruCache.java}). Back-porting this
-class for use on previous Android releases should be fairly straightforward (a <a
-href="http://www.google.com/search?q=disklrucache">quick search</a> shows others who have already
-implemented this solution).</p>
-
-<p>Here’s updated example code that uses the simple {@code DiskLruCache} included in the sample
-application of this class:</p>
+<p>The sample code of this class uses a {@code DiskLruCache} implementation that is pulled from the 
+<a href="https://android.googlesource.com/platform/libcore/+/master/luni/src/main/java/libcore/io/DiskLruCache.java">Android source</a>. Here’s updated example code that adds a disk cache in addition
+to the existing memory cache:</p>
 
 <pre>
-private DiskLruCache mDiskCache;
+private DiskLruCache mDiskLruCache;
+private final Object mDiskCacheLock = new Object();
+private boolean mDiskCacheStarting = true;
 private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
 private static final String DISK_CACHE_SUBDIR = "thumbnails";
 
@@ -210,12 +206,26 @@
     ...
     // Initialize memory cache
     ...
-    File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);
-    mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);
+    // Initialize disk cache on background thread
+    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
+    new InitDiskCacheTask().execute(cacheDir);
     ...
 }
 
-class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
+class InitDiskCacheTask extends AsyncTask&lt;File, Void, Void&gt; {
+    &#64;Override
+    protected Void doInBackground(File... params) {
+        synchronized (mDiskCacheLock) {
+            File cacheDir = params[0];
+            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
+            mDiskCacheStarting = false; // Finished initialization
+            mDiskCacheLock.notifyAll(); // Wake any waiting threads
+        }
+        return null;
+    }
+}
+
+class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
     ...
     // Decode image in background.
     &#64;Override
@@ -232,7 +242,7 @@
         }
 
         // Add final bitmap to caches
-        addBitmapToCache(String.valueOf(imageKey, bitmap);
+        addBitmapToCache(imageKey, bitmap);
 
         return bitmap;
     }
@@ -246,28 +256,48 @@
     }
 
     // Also add to disk cache
-    if (!mDiskCache.containsKey(key)) {
-        mDiskCache.put(key, bitmap);
+    synchronized (mDiskCacheLock) {
+        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
+            mDiskLruCache.put(key, bitmap);
+        }
     }
 }
 
 public Bitmap getBitmapFromDiskCache(String key) {
-    return mDiskCache.get(key);
+    synchronized (mDiskCacheLock) {
+        // Wait while disk cache is started from background thread
+        while (mDiskCacheStarting) {
+            try {
+                mDiskCacheLock.wait();
+            } catch (InterruptedException e) {}
+        }
+        if (mDiskLruCache != null) {
+            return mDiskLruCache.get(key);
+        }
+    }
+    return null;
 }
 
 // Creates a unique subdirectory of the designated app cache directory. Tries to use external
 // but if not mounted, falls back on internal storage.
-public static File getCacheDir(Context context, String uniqueName) {
+public static File getDiskCacheDir(Context context, String uniqueName) {
     // Check if media is mounted or storage is built-in, if so, try and use external cache dir
     // otherwise use internal cache dir
-    final String cachePath = Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED
-            || !Environment.isExternalStorageRemovable() ?
-                    context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();
+    final String cachePath =
+            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||
+                    !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :
+                            context.getCacheDir().getPath();
 
     return new File(cachePath + File.separator + uniqueName);
 }
 </pre>
 
+<p class="note"><strong>Note:</strong> Even initializing the disk cache requires disk operations
+and therefore should not take place on the main thread. However, this does mean there's a chance
+the cache is accessed before initialization. To address this, in the above implementation, a lock
+object ensures that the app does not read from the disk cache until the cache has been
+initialized.</p>
+
 <p>While the memory cache is checked in the UI thread, the disk cache is checked in the background
 thread. Disk operations should never take place on the UI thread. When image processing is
 complete, the final bitmap is added to both the memory and disk cache for future use.</p>
@@ -292,7 +322,7 @@
 changes using a {@link android.app.Fragment}:</p>
 
 <pre>
-private LruCache<String, Bitmap> mMemoryCache;
+private LruCache&lt;String, Bitmap&gt; mMemoryCache;
 
 &#64;Override
 protected void onCreate(Bundle savedInstanceState) {
@@ -301,7 +331,7 @@
             RetainFragment.findOrCreateRetainFragment(getFragmentManager());
     mMemoryCache = RetainFragment.mRetainedCache;
     if (mMemoryCache == null) {
-        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
+        mMemoryCache = new LruCache&lt;String, Bitmap&gt;(cacheSize) {
             ... // Initialize cache here as usual
         }
         mRetainFragment.mRetainedCache = mMemoryCache;
@@ -311,7 +341,7 @@
 
 class RetainFragment extends Fragment {
     private static final String TAG = "RetainFragment";
-    public LruCache<String, Bitmap> mRetainedCache;
+    public LruCache&lt;String, Bitmap&gt; mRetainedCache;
 
     public RetainFragment() {}
 
diff --git a/docs/html/training/displaying-bitmaps/display-bitmap.jd b/docs/html/training/displaying-bitmaps/display-bitmap.jd
index 5eac04c..4572c42 100644
--- a/docs/html/training/displaying-bitmaps/display-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/display-bitmap.jd
@@ -103,7 +103,8 @@
 }
 </pre>
 
-<p>The details {@link android.app.Fragment} holds the {@link android.widget.ImageView} children:</p>
+<p>Here is an implementation of the details {@link android.app.Fragment} which holds the {@link android.widget.ImageView} children. This might seem like a perfectly reasonable approach, but can
+you see the drawbacks of this implementation? How could it be improved?</p>
 
 <pre>
 public class ImageDetailFragment extends Fragment {
@@ -146,11 +147,11 @@
 }
 </pre>
 
-<p>Hopefully you noticed the issue with this implementation; The images are being read from
-resources on the UI thread which can lead to an application hanging and being force closed. Using an
-{@link android.os.AsyncTask} as described in the <a href="process-bitmap.html">Processing Bitmaps Off
-the UI Thread</a> lesson, it’s straightforward to move image loading and processing to a background
-thread:</p>
+<p>Hopefully you noticed the issue: the images are being read from resources on the UI thread,
+which can lead to an application hanging and being force closed. Using an
+{@link android.os.AsyncTask} as described in the <a href="process-bitmap.html">Processing Bitmaps
+Off the UI Thread</a> lesson, it’s straightforward to move image loading and processing to a
+background thread:</p>
 
 <pre>
 public class ImageDetailActivity extends FragmentActivity {
@@ -190,7 +191,7 @@
 <pre>
 public class ImageDetailActivity extends FragmentActivity {
     ...
-    private LruCache<String, Bitmap> mMemoryCache;
+    private LruCache&lt;String, Bitmap&gt; mMemoryCache;
 
     &#64;Override
     public void onCreate(Bundle savedInstanceState) {
@@ -229,7 +230,8 @@
 the way {@link android.widget.GridView} recycles its children views).</p>
 
 <p>To start with, here is a standard {@link android.widget.GridView} implementation with {@link
-android.widget.ImageView} children placed inside a {@link android.app.Fragment}:</p>
+android.widget.ImageView} children placed inside a {@link android.app.Fragment}. Again, this might
+seem like a perfectly reasonable approach, but what would make it better?</p>
 
 <pre>
 public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
@@ -261,7 +263,7 @@
     }
 
     &#64;Override
-    public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
+    public void onItemClick(AdapterView&lt;?&gt; parent, View v, int position, long id) {
         final Intent i = new Intent(getActivity(), ImageDetailActivity.class);
         i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position);
         startActivity(i);
@@ -345,13 +347,13 @@
     }
 
     static class AsyncDrawable extends BitmapDrawable {
-        private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+        private final WeakReference&lt;BitmapWorkerTask&gt; bitmapWorkerTaskReference;
 
         public AsyncDrawable(Resources res, Bitmap bitmap,
                 BitmapWorkerTask bitmapWorkerTask) {
             super(res, bitmap);
             bitmapWorkerTaskReference =
-                new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
+                new WeakReference&lt;BitmapWorkerTask&gt;(bitmapWorkerTask);
         }
 
         public BitmapWorkerTask getBitmapWorkerTask() {
diff --git a/docs/html/training/displaying-bitmaps/index.jd b/docs/html/training/displaying-bitmaps/index.jd
index 78371ad..b91172b 100644
--- a/docs/html/training/displaying-bitmaps/index.jd
+++ b/docs/html/training/displaying-bitmaps/index.jd
@@ -43,8 +43,8 @@
   perform under this minimum memory limit. However, keep in mind many devices are configured with
   higher limits.</li>
   <li>Bitmaps take up a lot of memory, especially for rich images like photographs. For example, the
-  camera on the <a href="http://www.google.com/nexus/">Galaxy Nexus</a> takes photos up to 2592x1936
-  pixels (5 megapixels). If the bitmap configuration used is {@link
+  camera on the <a href="http://www.android.com/devices/detail/galaxy-nexus">Galaxy Nexus</a> takes 
+  photos up to 2592x1936 pixels (5 megapixels). If the bitmap configuration used is {@link
   android.graphics.Bitmap.Config ARGB_8888} (the default from the Android 2.3 onward) then loading
   this image into memory takes about 19MB of memory (2592*1936*4 bytes), immediately exhausting the
   per-app limit on some devices.</li>
@@ -75,4 +75,4 @@
     components like {@link android.support.v4.view.ViewPager} and {@link android.widget.GridView}
     using a background thread and bitmap cache.</dd>
 
-</dl>
\ No newline at end of file
+</dl>
diff --git a/docs/html/training/displaying-bitmaps/process-bitmap.jd b/docs/html/training/displaying-bitmaps/process-bitmap.jd
index d1e346c..d4fcff3 100644
--- a/docs/html/training/displaying-bitmaps/process-bitmap.jd
+++ b/docs/html/training/displaying-bitmaps/process-bitmap.jd
@@ -62,13 +62,13 @@
 
 <a name="BitmapWorkerTask"></a>
 <pre>
-class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
-    private final WeakReference<ImageView> imageViewReference;
+class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
+    private final WeakReference&lt;ImageView&gt; imageViewReference;
     private int data = 0;
 
     public BitmapWorkerTask(ImageView imageView) {
         // Use a WeakReference to ensure the ImageView can be garbage collected
-        imageViewReference = new WeakReference<ImageView>(imageView);
+        imageViewReference = new WeakReference&lt;ImageView&gt;(imageView);
     }
 
     // Decode image in background.
@@ -133,13 +133,13 @@
 <a name="AsyncDrawable"></a>
 <pre>
 static class AsyncDrawable extends BitmapDrawable {
-    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
+    private final WeakReference&lt;BitmapWorkerTask&gt; bitmapWorkerTaskReference;
 
     public AsyncDrawable(Resources res, Bitmap bitmap,
             BitmapWorkerTask bitmapWorkerTask) {
         super(res, bitmap);
         bitmapWorkerTaskReference =
-            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
+            new WeakReference&lt;BitmapWorkerTask&gt;(bitmapWorkerTask);
     }
 
     public BitmapWorkerTask getBitmapWorkerTask() {
@@ -211,7 +211,7 @@
 
 <a name="BitmapWorkerTaskUpdated"></a>
 <pre>
-class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
+class BitmapWorkerTask extends AsyncTask&lt;Integer, Void, Bitmap&gt; {
     ...
 
     &#64;Override
@@ -236,4 +236,4 @@
 android.widget.GridView} components as well as any other components that recycle their child
 views. Simply call {@code loadBitmap} where you normally set an image to your {@link
 android.widget.ImageView}. For example, in a {@link android.widget.GridView} implementation this
-would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
\ No newline at end of file
+would be in the {@link android.widget.Adapter#getView getView()} method of the backing adapter.</p>
diff --git a/graphics/java/android/graphics/drawable/BitmapDrawable.java b/graphics/java/android/graphics/drawable/BitmapDrawable.java
index e82ccd4..7a4a1ca 100644
--- a/graphics/java/android/graphics/drawable/BitmapDrawable.java
+++ b/graphics/java/android/graphics/drawable/BitmapDrawable.java
@@ -80,6 +80,7 @@
     @Deprecated
     public BitmapDrawable() {
         mBitmapState = new BitmapState((Bitmap) null);
+        mMutated = true;
     }
 
     /**
@@ -90,6 +91,7 @@
     public BitmapDrawable(Resources res) {
         mBitmapState = new BitmapState((Bitmap) null);
         mBitmapState.mTargetDensity = mTargetDensity;
+        mMutated = true;
     }
 
     /**
@@ -100,6 +102,7 @@
     @Deprecated
     public BitmapDrawable(Bitmap bitmap) {
         this(new BitmapState(bitmap), null);
+        mMutated = true;
     }
 
     /**
@@ -109,6 +112,7 @@
     public BitmapDrawable(Resources res, Bitmap bitmap) {
         this(new BitmapState(bitmap), res);
         mBitmapState.mTargetDensity = mTargetDensity;
+        mMutated = true;
     }
 
     /**
@@ -122,6 +126,7 @@
         if (mBitmap == null) {
             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
         }
+        mMutated = true;
     }
 
     /**
@@ -134,6 +139,7 @@
         if (mBitmap == null) {
             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + filepath);
         }
+        mMutated = true;
     }
 
     /**
@@ -147,6 +153,7 @@
         if (mBitmap == null) {
             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
         }
+        mMutated = true;
     }
 
     /**
@@ -159,6 +166,7 @@
         if (mBitmap == null) {
             android.util.Log.w("BitmapDrawable", "BitmapDrawable cannot decode " + is);
         }
+        mMutated = true;
     }
 
     /**
@@ -552,6 +560,7 @@
         } else {
             mTargetDensity = state.mTargetDensity;
         }
+        mMutated = false;
         setBitmap(state != null ? state.mBitmap : null);
     }
 }
diff --git a/graphics/java/android/graphics/drawable/ClipDrawable.java b/graphics/java/android/graphics/drawable/ClipDrawable.java
index bade9b4..723db6e 100644
--- a/graphics/java/android/graphics/drawable/ClipDrawable.java
+++ b/graphics/java/android/graphics/drawable/ClipDrawable.java
@@ -239,7 +239,11 @@
         return null;
     }
 
-    
+    @Override
+    public void setLayoutDirection(int layoutDirection) {
+        mClipState.mDrawable.setLayoutDirection(layoutDirection);
+        super.setLayoutDirection(layoutDirection);
+    }
 
     final static class ClipState extends ConstantState {
         Drawable mDrawable;
diff --git a/graphics/java/android/graphics/drawable/ColorDrawable.java b/graphics/java/android/graphics/drawable/ColorDrawable.java
index 88c9155..f8e3944 100644
--- a/graphics/java/android/graphics/drawable/ColorDrawable.java
+++ b/graphics/java/android/graphics/drawable/ColorDrawable.java
@@ -36,12 +36,14 @@
 public class ColorDrawable extends Drawable {
     private ColorState mState;
     private final Paint mPaint = new Paint();
+    private boolean mMutated;
 
     /**
      * Creates a new black ColorDrawable.
      */
     public ColorDrawable() {
         this(null);
+        mMutated = true;
     }
 
     /**
@@ -52,6 +54,7 @@
     public ColorDrawable(int color) {
         this(null);
         setColor(color);
+        mMutated = true;
     }
 
     private ColorDrawable(ColorState state) {
@@ -63,6 +66,21 @@
         return super.getChangingConfigurations() | mState.mChangingConfigurations;
     }
 
+    /**
+     * A mutable BitmapDrawable still shares its Bitmap with any other Drawable
+     * that comes from the same resource.
+     *
+     * @return This drawable.
+     */
+    @Override
+    public Drawable mutate() {
+        if (!mMutated && super.mutate() == this) {
+            mState = new ColorState(mState);
+            mMutated = true;
+        }
+        return this;
+    }
+
     @Override
     public void draw(Canvas canvas) {
         if ((mState.mUseColor >>> 24) != 0) {
@@ -165,6 +183,7 @@
             if (state != null) {
                 mBaseColor = state.mBaseColor;
                 mUseColor = state.mUseColor;
+                mChangingConfigurations = state.mChangingConfigurations;
             }
         }
 
diff --git a/graphics/java/android/graphics/drawable/Drawable.java b/graphics/java/android/graphics/drawable/Drawable.java
index 4bc5a5a..020a54f 100644
--- a/graphics/java/android/graphics/drawable/Drawable.java
+++ b/graphics/java/android/graphics/drawable/Drawable.java
@@ -376,8 +376,8 @@
     /**
      * Returns the resolved layout direction for this Drawable.
      *
-     * @return One of {@link View#LAYOUT_DIRECTION_LTR},
-     *   {@link View#LAYOUT_DIRECTION_RTL}
+     * @return One of {@link android.view.View#LAYOUT_DIRECTION_LTR},
+     *   {@link android.view.View#LAYOUT_DIRECTION_RTL}
      */
     public int getLayoutDirection() {
         return mLayoutDirection;
@@ -387,8 +387,8 @@
      * Set the layout direction for this drawable. Should be a resolved direction as the
      * Drawable as no capacity to do the resolution on his own.
      *
-     * @param layoutDirection One of {@link View#LAYOUT_DIRECTION_LTR},
-     *   {@link View#LAYOUT_DIRECTION_RTL},
+     * @param layoutDirection One of {@link android.view.View#LAYOUT_DIRECTION_LTR},
+     *   {@link android.view.View#LAYOUT_DIRECTION_RTL},
      *
      */
     public void setLayoutDirection(int layoutDirection) {
diff --git a/graphics/java/android/graphics/drawable/DrawableContainer.java b/graphics/java/android/graphics/drawable/DrawableContainer.java
index 15b2c0b..486390c 100644
--- a/graphics/java/android/graphics/drawable/DrawableContainer.java
+++ b/graphics/java/android/graphics/drawable/DrawableContainer.java
@@ -105,7 +105,7 @@
             mAlpha = alpha;
             if (mCurrDrawable != null) {
                 if (mEnterAnimationEnd == 0) {
-                    mCurrDrawable.setAlpha(alpha);
+                    mCurrDrawable.mutate().setAlpha(alpha);
                 } else {
                     animate(false);
                 }
@@ -118,7 +118,7 @@
         if (mDrawableContainerState.mDither != dither) {
             mDrawableContainerState.mDither = dither;
             if (mCurrDrawable != null) {
-                mCurrDrawable.setDither(mDrawableContainerState.mDither);
+                mCurrDrawable.mutate().setDither(mDrawableContainerState.mDither);
             }
         }
     }
@@ -128,7 +128,7 @@
         if (mColorFilter != cf) {
             mColorFilter = cf;
             if (mCurrDrawable != null) {
-                mCurrDrawable.setColorFilter(cf);
+                mCurrDrawable.mutate().setColorFilter(cf);
             }
         }
     }
@@ -176,7 +176,7 @@
         }
         if (mCurrDrawable != null) {
             mCurrDrawable.jumpToCurrentState();
-            mCurrDrawable.setAlpha(mAlpha);
+            mCurrDrawable.mutate().setAlpha(mAlpha);
         }
         if (mExitAnimationEnd != 0) {
             mExitAnimationEnd = 0;
@@ -312,6 +312,7 @@
             mCurrDrawable = d;
             mCurIndex = idx;
             if (d != null) {
+                d.mutate();
                 if (mDrawableContainerState.mEnterFadeDuration > 0) {
                     mEnterAnimationEnd = now + mDrawableContainerState.mEnterFadeDuration;
                 } else {
@@ -355,13 +356,13 @@
         if (mCurrDrawable != null) {
             if (mEnterAnimationEnd != 0) {
                 if (mEnterAnimationEnd <= now) {
-                    mCurrDrawable.setAlpha(mAlpha);
+                    mCurrDrawable.mutate().setAlpha(mAlpha);
                     mEnterAnimationEnd = 0;
                 } else {
                     int animAlpha = (int)((mEnterAnimationEnd-now)*255)
                             / mDrawableContainerState.mEnterFadeDuration;
                     if (DEBUG) android.util.Log.i(TAG, toString() + " cur alpha " + animAlpha);
-                    mCurrDrawable.setAlpha(((255-animAlpha)*mAlpha)/255);
+                    mCurrDrawable.mutate().setAlpha(((255-animAlpha)*mAlpha)/255);
                     animating = true;
                 }
             }
@@ -378,7 +379,7 @@
                     int animAlpha = (int)((mExitAnimationEnd-now)*255)
                             / mDrawableContainerState.mExitFadeDuration;
                     if (DEBUG) android.util.Log.i(TAG, toString() + " last alpha " + animAlpha);
-                    mLastDrawable.setAlpha((animAlpha*mAlpha)/255);
+                    mLastDrawable.mutate().setAlpha((animAlpha*mAlpha)/255);
                     animating = true;
                 }
             }
diff --git a/graphics/java/android/graphics/drawable/GradientDrawable.java b/graphics/java/android/graphics/drawable/GradientDrawable.java
index 5b50beb..21344f4 100644
--- a/graphics/java/android/graphics/drawable/GradientDrawable.java
+++ b/graphics/java/android/graphics/drawable/GradientDrawable.java
@@ -124,7 +124,7 @@
     
     private Paint mLayerPaint;    // internal, used if we use saveLayer()
     private boolean mRectIsDirty;   // internal state
-    private boolean mMutated;
+    private boolean mMutated = true;
     private Path mRingPath;
     private boolean mPathIsDirty = true;
 
@@ -1202,6 +1202,7 @@
         mGradientState = state;
         initializeWithState(state);
         mRectIsDirty = true;
+        mMutated = false;
     }
 
     private void initializeWithState(GradientState state) {
diff --git a/graphics/java/android/graphics/drawable/LayerDrawable.java b/graphics/java/android/graphics/drawable/LayerDrawable.java
index 383fe71..03531ac 100644
--- a/graphics/java/android/graphics/drawable/LayerDrawable.java
+++ b/graphics/java/android/graphics/drawable/LayerDrawable.java
@@ -590,6 +590,18 @@
         return this;
     }
 
+    @Override
+    public void setLayoutDirection(int layoutDirection) {
+        if (getLayoutDirection() != layoutDirection) {
+            final ChildDrawable[] array = mLayerState.mChildren;
+            final int N = mLayerState.mNum;
+            for (int i = 0; i < N; i++) {
+                array[i].mDrawable.setLayoutDirection(layoutDirection);
+            }
+        }
+        super.setLayoutDirection(layoutDirection);
+    }
+
     static class ChildDrawable {
         public Drawable mDrawable;
         public int mInsetL, mInsetT, mInsetR, mInsetB;
diff --git a/graphics/java/android/graphics/drawable/NinePatchDrawable.java b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
index 62aea71..7a43496 100644
--- a/graphics/java/android/graphics/drawable/NinePatchDrawable.java
+++ b/graphics/java/android/graphics/drawable/NinePatchDrawable.java
@@ -77,6 +77,7 @@
     @Deprecated
     public NinePatchDrawable(Bitmap bitmap, byte[] chunk, Rect padding, String srcName) {
         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), null);
+        mMutated = true;
     }
 
     /**
@@ -87,6 +88,7 @@
             Rect padding, String srcName) {
         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding), res);
         mNinePatchState.mTargetDensity = mTargetDensity;
+        mMutated = true;
     }
 
     /**
@@ -99,6 +101,7 @@
             Rect padding, Rect layoutInsets, String srcName) {
         this(new NinePatchState(new NinePatch(bitmap, chunk, srcName), padding, layoutInsets), res);
         mNinePatchState.mTargetDensity = mTargetDensity;
+        mMutated = true;
     }
 
     /**
@@ -109,6 +112,7 @@
     @Deprecated
     public NinePatchDrawable(NinePatch patch) {
         this(new NinePatchState(patch, new Rect()), null);
+        mMutated = true;
     }
 
     /**
@@ -118,6 +122,7 @@
     public NinePatchDrawable(Resources res, NinePatch patch) {
         this(new NinePatchState(patch, new Rect()), res);
         mNinePatchState.mTargetDensity = mTargetDensity;
+        mMutated = true;
     }
 
     private void setNinePatchState(NinePatchState state, Resources res) {
@@ -181,7 +186,7 @@
         }
     }
 
-    private Insets scaleFromDensity(Insets insets, int sdensity, int tdensity) {
+    private static Insets scaleFromDensity(Insets insets, int sdensity, int tdensity) {
         int left = Bitmap.scaleFromDensity(insets.left, sdensity, tdensity);
         int top = Bitmap.scaleFromDensity(insets.top, sdensity, tdensity);
         int right = Bitmap.scaleFromDensity(insets.right, sdensity, tdensity);
diff --git a/graphics/java/android/graphics/drawable/StateListDrawable.java b/graphics/java/android/graphics/drawable/StateListDrawable.java
index 384ca81..a5c3614 100644
--- a/graphics/java/android/graphics/drawable/StateListDrawable.java
+++ b/graphics/java/android/graphics/drawable/StateListDrawable.java
@@ -261,6 +261,15 @@
         return this;
     }
 
+    @Override
+    public void setLayoutDirection(int layoutDirection) {
+        final int numStates = getStateCount();
+        for (int i = 0; i < numStates; i++) {
+            getStateDrawable(i).setLayoutDirection(layoutDirection);
+        }
+        super.setLayoutDirection(layoutDirection);
+    }
+
     static final class StateListState extends DrawableContainerState {
         int[][] mStateSets;
 
diff --git a/graphics/java/android/renderscript/Matrix4f.java b/graphics/java/android/renderscript/Matrix4f.java
index a85d464..4600424 100644
--- a/graphics/java/android/renderscript/Matrix4f.java
+++ b/graphics/java/android/renderscript/Matrix4f.java
@@ -113,6 +113,34 @@
     }
 
     /**
+    * Sets the values of the matrix to those of the parameter
+    *
+    * @param src matrix to load the values from
+    * @hide
+    */
+    public void load(Matrix3f src) {
+        mMat[0] = src.mMat[0];
+        mMat[1] = src.mMat[1];
+        mMat[2] = src.mMat[2];
+        mMat[3] = 0;
+
+        mMat[4] = src.mMat[3];
+        mMat[5] = src.mMat[4];
+        mMat[6] = src.mMat[5];
+        mMat[7] = 0;
+
+        mMat[8] = src.mMat[6];
+        mMat[9] = src.mMat[7];
+        mMat[10] = src.mMat[8];
+        mMat[11] = 0;
+
+        mMat[12] = 0;
+        mMat[13] = 0;
+        mMat[14] = 0;
+        mMat[15] = 1;
+    }
+
+    /**
     * Sets current values to be a rotation matrix of certain angle
     * about a given axis
     *
diff --git a/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java b/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java
index 41e7e00..dce1939 100644
--- a/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java
+++ b/graphics/java/android/renderscript/ScriptIntrinsicColorMatrix.java
@@ -39,8 +39,7 @@
     }
 
     /**
-     * Supported elements types are float, float4, uchar, uchar4
-     *
+     * Supported elements types are uchar4
      *
      * @param rs
      * @param e
@@ -53,13 +52,98 @@
 
     }
 
-    public void setColorMatrix(Matrix4f m) {
-        mMatrix.load(m);
+    private void setMatrix() {
         FieldPacker fp = new FieldPacker(16*4);
-        fp.addMatrix(m);
+        fp.addMatrix(mMatrix);
         setVar(0, fp);
     }
 
+    /**
+     * Set the color matrix which will be applied to each cell of the image.
+     *
+     * @param m The 4x4 matrix to set.
+     */
+    public void setColorMatrix(Matrix4f m) {
+        mMatrix.load(m);
+        setMatrix();
+    }
+
+    /**
+     * Set the color matrix which will be applied to each cell of the image.
+     * This will set the alpha channel to be a copy.
+     *
+     * @param m The 3x3 matrix to set.
+     */
+    public void setColorMatrix(Matrix3f m) {
+        mMatrix.load(m);
+        setMatrix();
+    }
+
+    /**
+     * Set a color matrix to convert from RGB to luminace. The alpha channel
+     * will be a copy.
+     *
+     */
+    public void setGreyscale() {
+        mMatrix.loadIdentity();
+        mMatrix.set(0, 0, 0.299f);
+        mMatrix.set(1, 0, 0.587f);
+        mMatrix.set(2, 0, 0.114f);
+        mMatrix.set(0, 1, 0.299f);
+        mMatrix.set(1, 1, 0.587f);
+        mMatrix.set(2, 1, 0.114f);
+        mMatrix.set(0, 2, 0.299f);
+        mMatrix.set(1, 2, 0.587f);
+        mMatrix.set(2, 2, 0.114f);
+        setMatrix();
+    }
+
+    /**
+     * Set the matrix to convert from YUV to RGB with a direct copy of the 4th
+     * channel.
+     *
+     */
+    public void setYUVtoRGB() {
+        mMatrix.loadIdentity();
+        mMatrix.set(0, 0, 1.f);
+        mMatrix.set(1, 0, 0.f);
+        mMatrix.set(2, 0, 1.13983f);
+        mMatrix.set(0, 1, 1.f);
+        mMatrix.set(1, 1, -0.39465f);
+        mMatrix.set(2, 1, -0.5806f);
+        mMatrix.set(0, 2, 1.f);
+        mMatrix.set(1, 2, 2.03211f);
+        mMatrix.set(2, 2, 0.f);
+        setMatrix();
+    }
+
+    /**
+     * Set the matrix to convert from RGB to YUV with a direct copy of the 4th
+     * channel.
+     *
+     */
+    public void setRGBtoYUV() {
+        mMatrix.loadIdentity();
+        mMatrix.set(0, 0, 0.299f);
+        mMatrix.set(1, 0, 0.587f);
+        mMatrix.set(2, 0, 0.114f);
+        mMatrix.set(0, 1, -0.14713f);
+        mMatrix.set(1, 1, -0.28886f);
+        mMatrix.set(2, 1, 0.436f);
+        mMatrix.set(0, 2, 0.615f);
+        mMatrix.set(1, 2, -0.51499f);
+        mMatrix.set(2, 2, -0.10001f);
+        setMatrix();
+    }
+
+
+    /**
+     * Invoke the kernel and apply the matrix to each cell of ain and copy to
+     * aout.
+     *
+     * @param ain Input allocation
+     * @param aout Output allocation
+     */
     public void forEach(Allocation ain, Allocation aout) {
         forEach(0, ain, aout, null);
     }
diff --git a/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java b/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java
index ee5f938..b4a228b 100644
--- a/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java
+++ b/graphics/java/android/renderscript/ScriptIntrinsicYuvToRGB.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2008 The Android Open Source Project
+ * Copyright (C) 2012 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.
diff --git a/include/android_runtime/android_view_Surface.h b/include/android_runtime/android_view_Surface.h
index e50186d..df0fe72 100644
--- a/include/android_runtime/android_view_Surface.h
+++ b/include/android_runtime/android_view_Surface.h
@@ -24,6 +24,7 @@
 namespace android {
 
 class Surface;
+class ISurfaceTexture;
 
 /* Gets the underlying ANativeWindow for a Surface. */
 extern sp<ANativeWindow> android_view_Surface_getNativeWindow(
@@ -35,6 +36,10 @@
 /* Gets the underlying Surface from a Surface Java object. */
 extern sp<Surface> android_view_Surface_getSurface(JNIEnv* env, jobject surfaceObj);
 
+/* Creates a Surface from an ISurfaceTexture. */
+extern jobject android_view_Surface_createFromISurfaceTexture(JNIEnv* env,
+        const sp<ISurfaceTexture>& surfaceTexture);
+
 } // namespace android
 
 #endif // _ANDROID_VIEW_SURFACE_H
diff --git a/libs/androidfw/ResourceTypes.cpp b/libs/androidfw/ResourceTypes.cpp
index 069dfa3..0107da4 100644
--- a/libs/androidfw/ResourceTypes.cpp
+++ b/libs/androidfw/ResourceTypes.cpp
@@ -2061,10 +2061,10 @@
         if (res.size() > 0) res.append("-");
         switch (screenLayout&ResTable_config::MASK_LAYOUTDIR) {
             case ResTable_config::LAYOUTDIR_LTR:
-                res.append("ltr");
+                res.append("ldltr");
                 break;
             case ResTable_config::LAYOUTDIR_RTL:
-                res.append("rtl");
+                res.append("ldrtl");
                 break;
             default:
                 res.appendFormat("layoutDir=%d",
diff --git a/libs/hwui/Android.mk b/libs/hwui/Android.mk
index c0f79df..e032ae4 100644
--- a/libs/hwui/Android.mk
+++ b/libs/hwui/Android.mk
@@ -16,6 +16,7 @@
 		Dither.cpp \
 		FboCache.cpp \
 		GradientCache.cpp \
+		Layer.cpp \
 		LayerCache.cpp \
 		LayerRenderer.cpp \
 		Matrix.cpp \
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 2de70d462..755170f 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -149,6 +149,7 @@
     delete mTransformMatrix3D;
     delete mStaticMatrix;
     delete mAnimationMatrix;
+
     mTransformMatrix = NULL;
     mTransformCamera = NULL;
     mTransformMatrix3D = NULL;
@@ -156,50 +157,54 @@
     mAnimationMatrix = NULL;
 
     Caches& caches = Caches::getInstance();
+    caches.resourceCache.lock();
 
     for (size_t i = 0; i < mBitmapResources.size(); i++) {
-        caches.resourceCache.decrementRefcount(mBitmapResources.itemAt(i));
+        caches.resourceCache.decrementRefcountLocked(mBitmapResources.itemAt(i));
     }
-    mBitmapResources.clear();
 
     for (size_t i = 0; i < mOwnedBitmapResources.size(); i++) {
         SkBitmap* bitmap = mOwnedBitmapResources.itemAt(i);
-        caches.resourceCache.decrementRefcount(bitmap);
-        caches.resourceCache.destructor(bitmap);
+        caches.resourceCache.decrementRefcountLocked(bitmap);
+        caches.resourceCache.destructorLocked(bitmap);
     }
-    mOwnedBitmapResources.clear();
 
     for (size_t i = 0; i < mFilterResources.size(); i++) {
-        caches.resourceCache.decrementRefcount(mFilterResources.itemAt(i));
+        caches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i));
     }
-    mFilterResources.clear();
 
     for (size_t i = 0; i < mShaders.size(); i++) {
-        caches.resourceCache.decrementRefcount(mShaders.itemAt(i));
-        caches.resourceCache.destructor(mShaders.itemAt(i));
+        caches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i));
+        caches.resourceCache.destructorLocked(mShaders.itemAt(i));
     }
-    mShaders.clear();
+
+    for (size_t i = 0; i < mSourcePaths.size(); i++) {
+        caches.resourceCache.decrementRefcountLocked(mSourcePaths.itemAt(i));
+    }
+
+    caches.resourceCache.unlock();
 
     for (size_t i = 0; i < mPaints.size(); i++) {
         delete mPaints.itemAt(i);
     }
-    mPaints.clear();
 
     for (size_t i = 0; i < mPaths.size(); i++) {
         SkPath* path = mPaths.itemAt(i);
         caches.pathCache.remove(path);
         delete path;
     }
-    mPaths.clear();
-
-    for (size_t i = 0; i < mSourcePaths.size(); i++) {
-        caches.resourceCache.decrementRefcount(mSourcePaths.itemAt(i));
-    }
-    mSourcePaths.clear();
 
     for (size_t i = 0; i < mMatrices.size(); i++) {
         delete mMatrices.itemAt(i);
     }
+
+    mBitmapResources.clear();
+    mOwnedBitmapResources.clear();
+    mFilterResources.clear();
+    mShaders.clear();
+    mSourcePaths.clear();
+    mPaints.clear();
+    mPaths.clear();
     mMatrices.clear();
 }
 
@@ -223,35 +228,44 @@
     mReader.setMemory(buffer, mSize);
 
     Caches& caches = Caches::getInstance();
+    caches.resourceCache.lock();
 
     const Vector<SkBitmap*>& bitmapResources = recorder.getBitmapResources();
     for (size_t i = 0; i < bitmapResources.size(); i++) {
         SkBitmap* resource = bitmapResources.itemAt(i);
         mBitmapResources.add(resource);
-        caches.resourceCache.incrementRefcount(resource);
+        caches.resourceCache.incrementRefcountLocked(resource);
     }
 
     const Vector<SkBitmap*> &ownedBitmapResources = recorder.getOwnedBitmapResources();
     for (size_t i = 0; i < ownedBitmapResources.size(); i++) {
         SkBitmap* resource = ownedBitmapResources.itemAt(i);
         mOwnedBitmapResources.add(resource);
-        caches.resourceCache.incrementRefcount(resource);
+        caches.resourceCache.incrementRefcountLocked(resource);
     }
 
     const Vector<SkiaColorFilter*>& filterResources = recorder.getFilterResources();
     for (size_t i = 0; i < filterResources.size(); i++) {
         SkiaColorFilter* resource = filterResources.itemAt(i);
         mFilterResources.add(resource);
-        caches.resourceCache.incrementRefcount(resource);
+        caches.resourceCache.incrementRefcountLocked(resource);
     }
 
     const Vector<SkiaShader*>& shaders = recorder.getShaders();
     for (size_t i = 0; i < shaders.size(); i++) {
         SkiaShader* resource = shaders.itemAt(i);
         mShaders.add(resource);
-        caches.resourceCache.incrementRefcount(resource);
+        caches.resourceCache.incrementRefcountLocked(resource);
     }
 
+    const SortedVector<SkPath*>& sourcePaths = recorder.getSourcePaths();
+    for (size_t i = 0; i < sourcePaths.size(); i++) {
+        mSourcePaths.add(sourcePaths.itemAt(i));
+        caches.resourceCache.incrementRefcountLocked(sourcePaths.itemAt(i));
+    }
+
+    caches.resourceCache.unlock();
+
     const Vector<SkPaint*>& paints = recorder.getPaints();
     for (size_t i = 0; i < paints.size(); i++) {
         mPaints.add(paints.itemAt(i));
@@ -262,12 +276,6 @@
         mPaths.add(paths.itemAt(i));
     }
 
-    const SortedVector<SkPath*>& sourcePaths = recorder.getSourcePaths();
-    for (size_t i = 0; i < sourcePaths.size(); i++) {
-        mSourcePaths.add(sourcePaths.itemAt(i));
-        caches.resourceCache.incrementRefcount(sourcePaths.itemAt(i));
-    }
-
     const Vector<SkMatrix*>& matrices = recorder.getMatrices();
     for (size_t i = 0; i < matrices.size(); i++) {
         mMatrices.add(matrices.itemAt(i));
@@ -1004,29 +1012,39 @@
             }
             break;
             case DrawLayer: {
+                int oldAlpha = -1;
                 Layer* layer = (Layer*) getInt();
                 float x = getFloat();
                 float y = getFloat();
                 SkPaint* paint = getPaint(renderer);
-                if (mCaching) {
-                    paint->setAlpha(mMultipliedAlpha);
+                if (mCaching && mMultipliedAlpha < 255) {
+                    oldAlpha = layer->getAlpha();
+                    layer->setAlpha(mMultipliedAlpha);
                 }
                 DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
                         layer, x, y, paint);
                 drawGlStatus |= renderer.drawLayer(layer, x, y, paint);
+                if (oldAlpha >= 0) {
+                    layer->setAlpha(oldAlpha);
+                }
             }
             break;
             case DrawBitmap: {
+                int oldAlpha = -1;
                 SkBitmap* bitmap = getBitmap();
                 float x = getFloat();
                 float y = getFloat();
                 SkPaint* paint = getPaint(renderer);
-                if (mCaching) {
+                if (mCaching && mMultipliedAlpha < 255) {
+                    oldAlpha = paint->getAlpha();
                     paint->setAlpha(mMultipliedAlpha);
                 }
                 DISPLAY_LIST_LOGD("%s%s %p, %.2f, %.2f, %p", (char*) indent, OP_NAMES[op],
                         bitmap, x, y, paint);
                 drawGlStatus |= renderer.drawBitmap(bitmap, x, y, paint);
+                if (oldAlpha >= 0) {
+                    paint->setAlpha(oldAlpha);
+                }
             }
             break;
             case DrawBitmapMatrix: {
@@ -1309,7 +1327,8 @@
 // Base structure
 ///////////////////////////////////////////////////////////////////////////////
 
-DisplayListRenderer::DisplayListRenderer() : mWriter(MIN_WRITER_SIZE),
+DisplayListRenderer::DisplayListRenderer():
+        mCaches(Caches::getInstance()), mWriter(MIN_WRITER_SIZE),
         mTranslateX(0.0f), mTranslateY(0.0f), mHasTranslate(false), mHasDrawOps(false) {
 }
 
@@ -1320,34 +1339,38 @@
 void DisplayListRenderer::reset() {
     mWriter.reset();
 
-    Caches& caches = Caches::getInstance();
+    mCaches.resourceCache.lock();
+
     for (size_t i = 0; i < mBitmapResources.size(); i++) {
-        caches.resourceCache.decrementRefcount(mBitmapResources.itemAt(i));
+        mCaches.resourceCache.decrementRefcountLocked(mBitmapResources.itemAt(i));
     }
-    mBitmapResources.clear();
 
     for (size_t i = 0; i < mOwnedBitmapResources.size(); i++) {
-        SkBitmap* bitmap = mOwnedBitmapResources.itemAt(i);
-        caches.resourceCache.decrementRefcount(bitmap);
+        mCaches.resourceCache.decrementRefcountLocked(mOwnedBitmapResources.itemAt(i));
     }
-    mOwnedBitmapResources.clear();
 
     for (size_t i = 0; i < mFilterResources.size(); i++) {
-        caches.resourceCache.decrementRefcount(mFilterResources.itemAt(i));
+        mCaches.resourceCache.decrementRefcountLocked(mFilterResources.itemAt(i));
     }
-    mFilterResources.clear();
 
     for (size_t i = 0; i < mShaders.size(); i++) {
-        caches.resourceCache.decrementRefcount(mShaders.itemAt(i));
+        mCaches.resourceCache.decrementRefcountLocked(mShaders.itemAt(i));
     }
-    mShaders.clear();
-    mShaderMap.clear();
 
     for (size_t i = 0; i < mSourcePaths.size(); i++) {
-        caches.resourceCache.decrementRefcount(mSourcePaths.itemAt(i));
+        mCaches.resourceCache.decrementRefcountLocked(mSourcePaths.itemAt(i));
     }
+
+    mCaches.resourceCache.unlock();
+
+    mBitmapResources.clear();
+    mOwnedBitmapResources.clear();
+    mFilterResources.clear();
     mSourcePaths.clear();
 
+    mShaders.clear();
+    mShaderMap.clear();
+
     mPaints.clear();
     mPaintMap.clear();
 
diff --git a/libs/hwui/DisplayListRenderer.h b/libs/hwui/DisplayListRenderer.h
index c8b3e47..8e4f2d3 100644
--- a/libs/hwui/DisplayListRenderer.h
+++ b/libs/hwui/DisplayListRenderer.h
@@ -763,7 +763,7 @@
             mPaths.add(pathCopy);
         }
         if (mSourcePaths.indexOf(path) < 0) {
-            Caches::getInstance().resourceCache.incrementRefcount(path);
+            mCaches.resourceCache.incrementRefcount(path);
             mSourcePaths.add(path);
         }
 
@@ -811,13 +811,13 @@
         // which doesn't seem worth the extra cycles for this unlikely case.
         addInt((int) bitmap);
         mBitmapResources.add(bitmap);
-        Caches::getInstance().resourceCache.incrementRefcount(bitmap);
+        mCaches.resourceCache.incrementRefcount(bitmap);
     }
 
     void addBitmapData(SkBitmap* bitmap) {
         addInt((int) bitmap);
         mOwnedBitmapResources.add(bitmap);
-        Caches::getInstance().resourceCache.incrementRefcount(bitmap);
+        mCaches.resourceCache.incrementRefcount(bitmap);
     }
 
     inline void addShader(SkiaShader* shader) {
@@ -833,7 +833,7 @@
             // replaceValueFor() performs an add if the entry doesn't exist
             mShaderMap.replaceValueFor(shader, shaderCopy);
             mShaders.add(shaderCopy);
-            Caches::getInstance().resourceCache.incrementRefcount(shaderCopy);
+            mCaches.resourceCache.incrementRefcount(shaderCopy);
         }
 
         addInt((int) shaderCopy);
@@ -842,7 +842,7 @@
     inline void addColorFilter(SkiaColorFilter* colorFilter) {
         addInt((int) colorFilter);
         mFilterResources.add(colorFilter);
-        Caches::getInstance().resourceCache.incrementRefcount(colorFilter);
+        mCaches.resourceCache.incrementRefcount(colorFilter);
     }
 
     Vector<SkBitmap*> mBitmapResources;
@@ -862,15 +862,16 @@
 
     Vector<SkMatrix*> mMatrices;
 
-    SkWriter32 mWriter;
     uint32_t mBufferSize;
 
     int mRestoreSaveCount;
 
+    Caches& mCaches;
+    SkWriter32 mWriter;
+
     float mTranslateX;
     float mTranslateY;
     bool mHasTranslate;
-
     bool mHasDrawOps;
 
     friend class DisplayList;
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
new file mode 100644
index 0000000..31e169b
--- /dev/null
+++ b/libs/hwui/Layer.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#define LOG_TAG "OpenGLRenderer"
+
+#include <utils/Log.h>
+
+#include "Layer.h"
+#include "OpenGLRenderer.h"
+#include "Caches.h"
+
+namespace android {
+namespace uirenderer {
+
+Layer::~Layer() {
+    if (mesh) delete mesh;
+    if (meshIndices) delete meshIndices;
+    if (colorFilter) Caches::getInstance().resourceCache.decrementRefcount(colorFilter);
+}
+
+void Layer::setPaint(SkPaint* paint) {
+    OpenGLRenderer::getAlphaAndModeDirect(paint, &alpha, &mode);
+}
+
+void Layer::setColorFilter(SkiaColorFilter* filter) {
+    if (colorFilter) {
+        Caches::getInstance().resourceCache.decrementRefcount(colorFilter);
+    }
+    colorFilter = filter;
+    if (colorFilter) {
+        Caches::getInstance().resourceCache.incrementRefcount(colorFilter);
+    }
+}
+
+
+
+}; // namespace uirenderer
+}; // namespace android
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index f243177..76da671 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -45,6 +45,7 @@
  * A layer has dimensions and is backed by an OpenGL texture or FBO.
  */
 struct Layer {
+
     Layer(const uint32_t layerWidth, const uint32_t layerHeight) {
         mesh = NULL;
         meshIndices = NULL;
@@ -60,10 +61,7 @@
         displayList = NULL;
     }
 
-    ~Layer() {
-        if (mesh) delete mesh;
-        if (meshIndices) delete meshIndices;
-    }
+    ~Layer();
 
     /**
      * Sets this layer's region to a rectangle. Computes the appropriate
@@ -106,6 +104,8 @@
         texture.height = height;
     }
 
+    ANDROID_API void setPaint(SkPaint* paint);
+
     inline void setBlend(bool blend) {
         texture.blend = blend;
     }
@@ -191,9 +191,7 @@
         return colorFilter;
     }
 
-    inline void setColorFilter(SkiaColorFilter* filter) {
-        colorFilter = filter;
-    }
+    ANDROID_API void setColorFilter(SkiaColorFilter* filter);
 
     inline void bindTexture() {
         glBindTexture(renderTarget, texture.id);
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index b66c898..9f8b87c 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1978,6 +1978,10 @@
 
         // Find the normal to the line
         vec2 n = (b - a).copyNormalized() * halfStrokeWidth;
+        float x = n.x;
+        n.x = -n.y;
+        n.y = x;
+
         if (isHairLine) {
             if (isAA) {
                 float wideningFactor;
@@ -2002,14 +2006,10 @@
 
             float extendedNLength = extendedN.length();
             // We need to set this value on the shader prior to drawing
-            boundaryWidthProportion = extendedNLength / (halfStrokeWidth + extendedNLength);
+            boundaryWidthProportion = .5 - extendedNLength / (halfStrokeWidth + extendedNLength);
             n += extendedN;
         }
 
-        float x = n.x;
-        n.x = -n.y;
-        n.y = x;
-
         // aa lines expand the endpoint vertices to encompass the AA boundary
         if (isAA) {
             vec2 abVector = (b - a);
@@ -2593,17 +2593,13 @@
 
     mCaches.activeTexture(0);
 
-    int alpha;
-    SkXfermode::Mode mode;
-    getAlphaAndMode(paint, &alpha, &mode);
-
-    layer->setAlpha(alpha, mode);
-
     if (CC_LIKELY(!layer->region.isEmpty())) {
         if (layer->region.isRect()) {
             composeLayerRect(layer, layer->regionRect);
         } else if (layer->mesh) {
-            const float a = alpha / 255.0f;
+            const float a = layer->getAlpha() / 255.0f;
+            SkiaColorFilter *oldFilter = mColorFilter;
+            mColorFilter = layer->getColorFilter();
 
             setupDraw();
             setupDrawWithTexture();
@@ -2633,6 +2629,8 @@
 
             finishDrawTexture();
 
+            mColorFilter = oldFilter;
+
 #if DEBUG_LAYERS_AS_REGIONS
             drawRegionRects(layer->region);
 #endif
diff --git a/libs/hwui/OpenGLRenderer.h b/libs/hwui/OpenGLRenderer.h
index f2b5f0a..7f9405f 100644
--- a/libs/hwui/OpenGLRenderer.h
+++ b/libs/hwui/OpenGLRenderer.h
@@ -227,6 +227,32 @@
      */
     void endMark() const;
 
+    /**
+     * Gets the alpha and xfermode out of a paint object. If the paint is null
+     * alpha will be 255 and the xfermode will be SRC_OVER. This method does
+     * not multiply the paint's alpha by the current snapshot's alpha.
+     *
+     * @param paint The paint to extract values from
+     * @param alpha Where to store the resulting alpha
+     * @param mode Where to store the resulting xfermode
+     */
+    static inline void getAlphaAndModeDirect(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
+        if (paint) {
+            *mode = getXfermode(paint->getXfermode());
+
+            // Skia draws using the color's alpha channel if < 255
+            // Otherwise, it uses the paint's alpha
+            int color = paint->getColor();
+            *alpha = (color >> 24) & 0xFF;
+            if (*alpha == 255) {
+                *alpha = paint->getAlpha();
+            }
+        } else {
+            *mode = SkXfermode::kSrcOver_Mode;
+            *alpha = 255;
+        }
+    }
+
 protected:
     /**
      * Compose the layer defined in the current snapshot with the layer
@@ -291,32 +317,6 @@
     inline void getAlphaAndMode(SkPaint* paint, int* alpha, SkXfermode::Mode* mode);
 
     /**
-     * Gets the alpha and xfermode out of a paint object. If the paint is null
-     * alpha will be 255 and the xfermode will be SRC_OVER. This method does
-     * not multiply the paint's alpha by the current snapshot's alpha.
-     *
-     * @param paint The paint to extract values from
-     * @param alpha Where to store the resulting alpha
-     * @param mode Where to store the resulting xfermode
-     */
-    static inline void getAlphaAndModeDirect(SkPaint* paint, int* alpha, SkXfermode::Mode* mode) {
-        if (paint) {
-            *mode = getXfermode(paint->getXfermode());
-
-            // Skia draws using the color's alpha channel if < 255
-            // Otherwise, it uses the paint's alpha
-            int color = paint->getColor();
-            *alpha = (color >> 24) & 0xFF;
-            if (*alpha == 255) {
-                *alpha = paint->getAlpha();
-            }
-        } else {
-            *mode = SkXfermode::kSrcOver_Mode;
-            *alpha = 255;
-        }
-    }
-
-    /**
      * Safely retrieves the mode from the specified xfermode. If the specified
      * xfermode is null, the mode is assumed to be SkXfermode::kSrcOver_Mode.
      */
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index 2153a8b..b0c57d1 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -38,7 +38,7 @@
 
 ResourceCache::ResourceCache() {
     Mutex::Autolock _l(mLock);
-    mCache = new KeyedVector<void *, ResourceReference *>();
+    mCache = new KeyedVector<void*, ResourceReference*>();
 }
 
 ResourceCache::~ResourceCache() {
@@ -46,15 +46,17 @@
     delete mCache;
 }
 
+void ResourceCache::lock() {
+    mLock.lock();
+}
+
+void ResourceCache::unlock() {
+    mLock.unlock();
+}
+
 void ResourceCache::incrementRefcount(void* resource, ResourceType resourceType) {
     Mutex::Autolock _l(mLock);
-    ssize_t index = mCache->indexOfKey(resource);
-    ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
-    if (ref == NULL || mCache->size() == 0) {
-        ref = new ResourceReference(resourceType);
-        mCache->add(resource, ref);
-    }
-    ref->refCount++;
+    incrementRefcountLocked(resource, resourceType);
 }
 
 void ResourceCache::incrementRefcount(SkBitmap* bitmapResource) {
@@ -77,18 +79,39 @@
     incrementRefcount((void*) filterResource, kColorFilter);
 }
 
-void ResourceCache::decrementRefcount(void* resource) {
-    Mutex::Autolock _l(mLock);
+void ResourceCache::incrementRefcountLocked(void* resource, ResourceType resourceType) {
     ssize_t index = mCache->indexOfKey(resource);
     ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
-    if (ref == NULL) {
-        // Should not get here - shouldn't get a call to decrement if we're not yet tracking it
-        return;
+    if (ref == NULL || mCache->size() == 0) {
+        ref = new ResourceReference(resourceType);
+        mCache->add(resource, ref);
     }
-    ref->refCount--;
-    if (ref->refCount == 0) {
-        deleteResourceReference(resource, ref);
-    }
+    ref->refCount++;
+}
+
+void ResourceCache::incrementRefcountLocked(SkBitmap* bitmapResource) {
+    SkSafeRef(bitmapResource->pixelRef());
+    SkSafeRef(bitmapResource->getColorTable());
+    incrementRefcountLocked((void*) bitmapResource, kBitmap);
+}
+
+void ResourceCache::incrementRefcountLocked(SkPath* pathResource) {
+    incrementRefcountLocked((void*) pathResource, kPath);
+}
+
+void ResourceCache::incrementRefcountLocked(SkiaShader* shaderResource) {
+    SkSafeRef(shaderResource->getSkShader());
+    incrementRefcountLocked((void*) shaderResource, kShader);
+}
+
+void ResourceCache::incrementRefcountLocked(SkiaColorFilter* filterResource) {
+    SkSafeRef(filterResource->getSkColorFilter());
+    incrementRefcountLocked((void*) filterResource, kColorFilter);
+}
+
+void ResourceCache::decrementRefcount(void* resource) {
+    Mutex::Autolock _l(mLock);
+    decrementRefcountLocked(resource);
 }
 
 void ResourceCache::decrementRefcount(SkBitmap* bitmapResource) {
@@ -111,27 +134,45 @@
     decrementRefcount((void*) filterResource);
 }
 
-void ResourceCache::recycle(SkBitmap* resource) {
-    Mutex::Autolock _l(mLock);
+void ResourceCache::decrementRefcountLocked(void* resource) {
     ssize_t index = mCache->indexOfKey(resource);
-    if (index < 0) {
-        // not tracking this resource; just recycle the pixel data
-        resource->setPixels(NULL, NULL);
-        return;
-    }
-    ResourceReference* ref = mCache->valueAt(index);
+    ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
     if (ref == NULL) {
-        // Should not get here - shouldn't get a call to recycle if we're not yet tracking it
+        // Should not get here - shouldn't get a call to decrement if we're not yet tracking it
         return;
     }
-    ref->recycled = true;
+    ref->refCount--;
     if (ref->refCount == 0) {
         deleteResourceReference(resource, ref);
     }
 }
 
+void ResourceCache::decrementRefcountLocked(SkBitmap* bitmapResource) {
+    SkSafeUnref(bitmapResource->pixelRef());
+    SkSafeUnref(bitmapResource->getColorTable());
+    decrementRefcountLocked((void*) bitmapResource);
+}
+
+void ResourceCache::decrementRefcountLocked(SkPath* pathResource) {
+    decrementRefcountLocked((void*) pathResource);
+}
+
+void ResourceCache::decrementRefcountLocked(SkiaShader* shaderResource) {
+    SkSafeUnref(shaderResource->getSkShader());
+    decrementRefcountLocked((void*) shaderResource);
+}
+
+void ResourceCache::decrementRefcountLocked(SkiaColorFilter* filterResource) {
+    SkSafeUnref(filterResource->getSkColorFilter());
+    decrementRefcountLocked((void*) filterResource);
+}
+
 void ResourceCache::destructor(SkPath* resource) {
     Mutex::Autolock _l(mLock);
+    destructorLocked(resource);
+}
+
+void ResourceCache::destructorLocked(SkPath* resource) {
     ssize_t index = mCache->indexOfKey(resource);
     ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
     if (ref == NULL) {
@@ -150,6 +191,10 @@
 
 void ResourceCache::destructor(SkBitmap* resource) {
     Mutex::Autolock _l(mLock);
+    destructorLocked(resource);
+}
+
+void ResourceCache::destructorLocked(SkBitmap* resource) {
     ssize_t index = mCache->indexOfKey(resource);
     ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
     if (ref == NULL) {
@@ -168,6 +213,10 @@
 
 void ResourceCache::destructor(SkiaShader* resource) {
     Mutex::Autolock _l(mLock);
+    destructorLocked(resource);
+}
+
+void ResourceCache::destructorLocked(SkiaShader* resource) {
     ssize_t index = mCache->indexOfKey(resource);
     ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
     if (ref == NULL) {
@@ -183,6 +232,10 @@
 
 void ResourceCache::destructor(SkiaColorFilter* resource) {
     Mutex::Autolock _l(mLock);
+    destructorLocked(resource);
+}
+
+void ResourceCache::destructorLocked(SkiaColorFilter* resource) {
     ssize_t index = mCache->indexOfKey(resource);
     ResourceReference* ref = index >= 0 ? mCache->valueAt(index) : NULL;
     if (ref == NULL) {
@@ -196,6 +249,29 @@
     }
 }
 
+void ResourceCache::recycle(SkBitmap* resource) {
+    Mutex::Autolock _l(mLock);
+    recycleLocked(resource);
+}
+
+void ResourceCache::recycleLocked(SkBitmap* resource) {
+    ssize_t index = mCache->indexOfKey(resource);
+    if (index < 0) {
+        // not tracking this resource; just recycle the pixel data
+        resource->setPixels(NULL, NULL);
+        return;
+    }
+    ResourceReference* ref = mCache->valueAt(index);
+    if (ref == NULL) {
+        // Should not get here - shouldn't get a call to recycle if we're not yet tracking it
+        return;
+    }
+    ref->recycled = true;
+    if (ref->refCount == 0) {
+        deleteResourceReference(resource, ref);
+    }
+}
+
 /**
  * This method should only be called while the mLock mutex is held (that mutex is grabbed
  * by the various destructor() and recycle() methods which call this method).
diff --git a/libs/hwui/ResourceCache.h b/libs/hwui/ResourceCache.h
index 8cf466b..60ffa7d 100644
--- a/libs/hwui/ResourceCache.h
+++ b/libs/hwui/ResourceCache.h
@@ -52,28 +52,59 @@
 };
 
 class ANDROID_API ResourceCache {
-    KeyedVector<void *, ResourceReference *>* mCache;
 public:
     ResourceCache();
     ~ResourceCache();
+
+    /**
+     * When using these two methods, make sure to only invoke the *Locked()
+     * variants of increment/decrementRefcount(), recyle() and destructor()
+     */
+    void lock();
+    void unlock();
+
     void incrementRefcount(SkPath* resource);
     void incrementRefcount(SkBitmap* resource);
     void incrementRefcount(SkiaShader* resource);
     void incrementRefcount(SkiaColorFilter* resource);
-    void incrementRefcount(const void* resource, ResourceType resourceType);
-    void decrementRefcount(void* resource);
+
+    void incrementRefcountLocked(SkPath* resource);
+    void incrementRefcountLocked(SkBitmap* resource);
+    void incrementRefcountLocked(SkiaShader* resource);
+    void incrementRefcountLocked(SkiaColorFilter* resource);
+
     void decrementRefcount(SkBitmap* resource);
     void decrementRefcount(SkPath* resource);
     void decrementRefcount(SkiaShader* resource);
     void decrementRefcount(SkiaColorFilter* resource);
-    void recycle(SkBitmap* resource);
+
+    void decrementRefcountLocked(SkBitmap* resource);
+    void decrementRefcountLocked(SkPath* resource);
+    void decrementRefcountLocked(SkiaShader* resource);
+    void decrementRefcountLocked(SkiaColorFilter* resource);
+
     void destructor(SkPath* resource);
     void destructor(SkBitmap* resource);
     void destructor(SkiaShader* resource);
     void destructor(SkiaColorFilter* resource);
+
+    void destructorLocked(SkPath* resource);
+    void destructorLocked(SkBitmap* resource);
+    void destructorLocked(SkiaShader* resource);
+    void destructorLocked(SkiaColorFilter* resource);
+
+    void recycle(SkBitmap* resource);
+    void recycleLocked(SkBitmap* resource);
+
 private:
     void deleteResourceReference(void* resource, ResourceReference* ref);
+
     void incrementRefcount(void* resource, ResourceType resourceType);
+    void incrementRefcountLocked(void* resource, ResourceType resourceType);
+
+    void decrementRefcount(void* resource);
+    void decrementRefcountLocked(void* resource);
+
     void logCache();
 
     /**
@@ -82,6 +113,8 @@
      * or a reference queue finalization thread.
      */
     mutable Mutex mLock;
+
+    KeyedVector<void*, ResourceReference*>* mCache;
 };
 
 }; // namespace uirenderer
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 1ca0df4..f3a8558 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -188,6 +188,13 @@
      * AudioPolicyService methods
      */
 
+    //
+    // audio device definitions: must be kept in sync with values in system/core/audio.h
+    //
+
+    // reserved bits
+    public static final int DEVICE_BIT_IN = 0x80000000;
+    public static final int DEVICE_BIT_DEFAULT = 0x40000000;
     // output devices, be sure to update AudioManager.java also
     public static final int DEVICE_OUT_EARPIECE = 0x1;
     public static final int DEVICE_OUT_SPEAKER = 0x2;
@@ -204,8 +211,9 @@
     public static final int DEVICE_OUT_DGTL_DOCK_HEADSET = 0x1000;
     public static final int DEVICE_OUT_USB_ACCESSORY = 0x2000;
     public static final int DEVICE_OUT_USB_DEVICE = 0x4000;
+    public static final int DEVICE_OUT_REMOTE_SUBMIX = 0x8000;
 
-    public static final int DEVICE_OUT_DEFAULT = 0x8000;
+    public static final int DEVICE_OUT_DEFAULT = DEVICE_BIT_DEFAULT;
     public static final int DEVICE_OUT_ALL = (DEVICE_OUT_EARPIECE |
                                               DEVICE_OUT_SPEAKER |
                                               DEVICE_OUT_WIRED_HEADSET |
@@ -221,6 +229,7 @@
                                               DEVICE_OUT_DGTL_DOCK_HEADSET |
                                               DEVICE_OUT_USB_ACCESSORY |
                                               DEVICE_OUT_USB_DEVICE |
+                                              DEVICE_OUT_REMOTE_SUBMIX |
                                               DEVICE_OUT_DEFAULT);
     public static final int DEVICE_OUT_ALL_A2DP = (DEVICE_OUT_BLUETOOTH_A2DP |
                                                    DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
@@ -232,15 +241,36 @@
                                                   DEVICE_OUT_USB_DEVICE);
 
     // input devices
-    public static final int DEVICE_IN_COMMUNICATION = 0x10000;
-    public static final int DEVICE_IN_AMBIENT = 0x20000;
-    public static final int DEVICE_IN_BUILTIN_MIC1 = 0x40000;
-    public static final int DEVICE_IN_BUILTIN_MIC2 = 0x80000;
-    public static final int DEVICE_IN_MIC_ARRAY = 0x100000;
-    public static final int DEVICE_IN_BLUETOOTH_SCO_HEADSET = 0x200000;
-    public static final int DEVICE_IN_WIRED_HEADSET = 0x400000;
-    public static final int DEVICE_IN_AUX_DIGITAL = 0x800000;
-    public static final int DEVICE_IN_DEFAULT = 0x80000000;
+    public static final int DEVICE_IN_COMMUNICATION = DEVICE_BIT_IN | 0x1;
+    public static final int DEVICE_IN_AMBIENT = DEVICE_BIT_IN | 0x2;
+    public static final int DEVICE_IN_BUILTIN_MIC = DEVICE_BIT_IN | 0x4;
+    public static final int DEVICE_IN_BLUETOOTH_SCO_HEADSET = DEVICE_BIT_IN | 0x8;
+    public static final int DEVICE_IN_WIRED_HEADSET = DEVICE_BIT_IN | 0x10;
+    public static final int DEVICE_IN_AUX_DIGITAL = DEVICE_BIT_IN | 0x20;
+    public static final int DEVICE_IN_VOICE_CALL = DEVICE_BIT_IN | 0x40;
+    public static final int DEVICE_IN_BACK_MIC = DEVICE_BIT_IN | 0x80;
+    public static final int DEVICE_IN_REMOTE_SUBMIX = DEVICE_BIT_IN | 0x100;
+    public static final int DEVICE_IN_ANLG_DOCK_HEADSET = DEVICE_BIT_IN | 0x200;
+    public static final int DEVICE_IN_DGTL_DOCK_HEADSET = DEVICE_BIT_IN | 0x400;
+    public static final int DEVICE_IN_USB_ACCESSORY = DEVICE_BIT_IN | 0x800;
+    public static final int DEVICE_IN_USB_DEVICE = DEVICE_BIT_IN | 0x1000;
+    public static final int DEVICE_IN_DEFAULT = DEVICE_BIT_IN | DEVICE_BIT_DEFAULT;
+
+    public static final int DEVICE_IN_ALL = (DEVICE_IN_COMMUNICATION |
+                                             DEVICE_IN_AMBIENT |
+                                             DEVICE_IN_BUILTIN_MIC |
+                                             DEVICE_IN_BLUETOOTH_SCO_HEADSET |
+                                             DEVICE_IN_WIRED_HEADSET |
+                                             DEVICE_IN_AUX_DIGITAL |
+                                             DEVICE_IN_VOICE_CALL |
+                                             DEVICE_IN_BACK_MIC |
+                                             DEVICE_IN_REMOTE_SUBMIX |
+                                             DEVICE_IN_ANLG_DOCK_HEADSET |
+                                             DEVICE_IN_DGTL_DOCK_HEADSET |
+                                             DEVICE_IN_USB_ACCESSORY |
+                                             DEVICE_IN_USB_DEVICE |
+                                             DEVICE_IN_DEFAULT);
+    public static final int DEVICE_IN_ALL_SCO = DEVICE_IN_BLUETOOTH_SCO_HEADSET;
 
     // device states, must match AudioSystem::device_connection_state
     public static final int DEVICE_STATE_UNAVAILABLE = 0;
@@ -262,6 +292,7 @@
     public static final String DEVICE_OUT_DGTL_DOCK_HEADSET_NAME = "digital_dock";
     public static final String DEVICE_OUT_USB_ACCESSORY_NAME = "usb_accessory";
     public static final String DEVICE_OUT_USB_DEVICE_NAME = "usb_device";
+    public static final String DEVICE_OUT_REMOTE_SUBMIX_NAME = "remote_submix";
 
     public static String getDeviceName(int device)
     {
@@ -296,7 +327,9 @@
             return DEVICE_OUT_USB_ACCESSORY_NAME;
         case DEVICE_OUT_USB_DEVICE:
             return DEVICE_OUT_USB_DEVICE_NAME;
-        case DEVICE_IN_DEFAULT:
+        case DEVICE_OUT_REMOTE_SUBMIX:
+            return DEVICE_OUT_REMOTE_SUBMIX_NAME;
+        case DEVICE_OUT_DEFAULT:
         default:
             return "";
         }
diff --git a/media/java/android/media/RemoteDisplay.java b/media/java/android/media/RemoteDisplay.java
new file mode 100644
index 0000000..b463d26
--- /dev/null
+++ b/media/java/android/media/RemoteDisplay.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2012 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.media;
+
+import dalvik.system.CloseGuard;
+
+import android.os.Handler;
+import android.view.Surface;
+
+/**
+ * Listens for Wifi remote display connections managed by the media server.
+ *
+ * @hide
+ */
+public final class RemoteDisplay {
+    /* these constants must be kept in sync with IRemoteDisplayClient.h */
+
+    public static final int DISPLAY_FLAG_SECURE = 1 << 0;
+
+    public static final int DISPLAY_ERROR_UNKOWN = 1;
+    public static final int DISPLAY_ERROR_CONNECTION_DROPPED = 2;
+
+    private final CloseGuard mGuard = CloseGuard.get();
+    private final Listener mListener;
+    private final Handler mHandler;
+
+    private int mPtr;
+
+    private native int nativeListen(String iface);
+    private native void nativeDispose(int ptr);
+
+    private RemoteDisplay(Listener listener, Handler handler) {
+        mListener = listener;
+        mHandler = handler;
+    }
+
+    @Override
+    protected void finalize() throws Throwable {
+        try {
+            dispose(true);
+        } finally {
+            super.finalize();
+        }
+    }
+
+    /**
+     * Starts listening for displays to be connected on the specified interface.
+     *
+     * @param iface The interface address and port in the form "x.x.x.x:y".
+     * @param listener The listener to invoke when displays are connected or disconnected.
+     * @param handler The handler on which to invoke the listener.
+     */
+    public static RemoteDisplay listen(String iface, Listener listener, Handler handler) {
+        if (iface == null) {
+            throw new IllegalArgumentException("iface must not be null");
+        }
+        if (listener == null) {
+            throw new IllegalArgumentException("listener must not be null");
+        }
+        if (handler == null) {
+            throw new IllegalArgumentException("handler must not be null");
+        }
+
+        RemoteDisplay display = new RemoteDisplay(listener, handler);
+        display.startListening(iface);
+        return display;
+    }
+
+    /**
+     * Disconnects the remote display and stops listening for new connections.
+     */
+    public void dispose() {
+        dispose(false);
+    }
+
+    private void dispose(boolean finalized) {
+        if (mPtr != 0) {
+            if (mGuard != null) {
+                if (finalized) {
+                    mGuard.warnIfOpen();
+                } else {
+                    mGuard.close();
+                }
+            }
+
+            nativeDispose(mPtr);
+            mPtr = 0;
+        }
+    }
+
+    private void startListening(String iface) {
+        mPtr = nativeListen(iface);
+        if (mPtr == 0) {
+            throw new IllegalStateException("Could not start listening for "
+                    + "remote display connection on \"" + iface + "\"");
+        }
+        mGuard.open("dispose");
+    }
+
+    // Called from native.
+    private void notifyDisplayConnected(final Surface surface,
+            final int width, final int height, final int flags) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onDisplayConnected(surface, width, height, flags);
+            }
+        });
+    }
+
+    // Called from native.
+    private void notifyDisplayDisconnected() {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onDisplayDisconnected();
+            }
+        });
+    }
+
+    // Called from native.
+    private void notifyDisplayError(final int error) {
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onDisplayError(error);
+            }
+        });
+    }
+
+    /**
+     * Listener invoked when the remote display connection changes state.
+     */
+    public interface Listener {
+        void onDisplayConnected(Surface surface, int width, int height, int flags);
+        void onDisplayDisconnected();
+        void onDisplayError(int error);
+    }
+}
diff --git a/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java b/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java
index d5c7aaa..f977e60 100644
--- a/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java
+++ b/media/mca/effect/java/android/media/effect/effects/BackDropperEffect.java
@@ -91,6 +91,9 @@
         if (parameterKey.equals("source")) {
             Filter background = mGraph.getFilter("background");
             background.setInputValue("sourceUrl", value);
+        } else if (parameterKey.equals("context")) {
+            Filter background = mGraph.getFilter("background");
+            background.setInputValue("context", value);
         }
     }
 
diff --git a/media/tests/EffectsTest/Android.mk b/media/tests/EffectsTest/Android.mk
new file mode 100755
index 0000000..25b4fe4
--- /dev/null
+++ b/media/tests/EffectsTest/Android.mk
@@ -0,0 +1,10 @@
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+
+LOCAL_SRC_FILES := $(call all-subdir-java-files)
+
+LOCAL_PACKAGE_NAME := EffectsTest
+
+include $(BUILD_PACKAGE)
diff --git a/media/tests/EffectsTest/AndroidManifest.xml b/media/tests/EffectsTest/AndroidManifest.xml
new file mode 100755
index 0000000..9b59891
--- /dev/null
+++ b/media/tests/EffectsTest/AndroidManifest.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.effectstest">
+
+   <uses-permission android:name="android.permission.RECORD_AUDIO" />
+   <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
+
+   <application>
+        <activity android:label="@string/app_name"
+                android:name="EffectsTest">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER"/>
+            </intent-filter>
+        </activity>
+
+        <activity android:label="@string/envreverb_test_name"
+                android:name="EnvReverbTest">
+        </activity>
+
+        <activity android:label="@string/presetrvb_test_name"
+                android:name="PresetReverbTest">
+        </activity>
+
+        <activity android:label="@string/equalizer_test_name"
+                android:name="EqualizerTest">
+        </activity>
+
+        <activity android:label="@string/virtualizer_test_name"
+                android:name="VirtualizerTest">
+        </activity>
+
+        <activity android:label="@string/bassboost_test_name"
+                android:name="BassBoostTest">
+        </activity>
+
+        <activity android:label="@string/visualizer_test_name"
+                android:name="VisualizerTest">
+        </activity>
+
+    </application>
+</manifest>
diff --git a/media/tests/EffectsTest/res/drawable/icon.png b/media/tests/EffectsTest/res/drawable/icon.png
new file mode 100755
index 0000000..64e3601
--- /dev/null
+++ b/media/tests/EffectsTest/res/drawable/icon.png
Binary files differ
diff --git a/media/tests/EffectsTest/res/drawable/stop.png b/media/tests/EffectsTest/res/drawable/stop.png
new file mode 100755
index 0000000..83f012c
--- /dev/null
+++ b/media/tests/EffectsTest/res/drawable/stop.png
Binary files differ
diff --git a/media/tests/EffectsTest/res/layout/bassboosttest.xml b/media/tests/EffectsTest/res/layout/bassboosttest.xml
new file mode 100755
index 0000000..0888e98
--- /dev/null
+++ b/media/tests/EffectsTest/res/layout/bassboosttest.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/bbReleaseLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/bbReleaseText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_release"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/bbReleaseButton"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/bbControlLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/bbControlText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_control"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/bassboostOnOff"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/SessionFrame"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/sessionText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/session"
+            style="@android:style/TextAppearance.Medium" />
+
+        <EditText android:id="@+id/sessionEdit"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|right" />
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/bbStrengthName"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stength_name" />
+
+                <LinearLayout android:id="@+id/bbStrengthDesc"
+                    android:orientation="horizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginTop="10dip"
+                    android:layout_marginRight="30dip"
+                    android:layout_marginBottom="10dip" >
+
+                    <TextView android:id="@+id/bbStrengthMin"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1"
+                        android:layout_gravity="left"
+                        android:gravity="left"/>
+                    <TextView android:id="@+id/bbStrengthMax"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1"
+                        android:layout_gravity="right"
+                        android:gravity="right"/>
+                </LinearLayout>
+
+                <SeekBar android:id="@+id/bbStrengthSeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/bbStrengthValue"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</LinearLayout>
diff --git a/media/tests/EffectsTest/res/layout/effectstest.xml b/media/tests/EffectsTest/res/layout/effectstest.xml
new file mode 100755
index 0000000..9af4eb6
--- /dev/null
+++ b/media/tests/EffectsTest/res/layout/effectstest.xml
@@ -0,0 +1,58 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <Button android:id="@+id/env_reverb_actvity"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="@string/envreverb_test_name">
+    </Button>
+
+    <Button android:id="@+id/preset_reverb_actvity"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="@string/presetrvb_test_name">
+    </Button>
+
+    <Button android:id="@+id/equalizer_actvity"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="@string/equalizer_test_name">
+    </Button>
+
+    <Button android:id="@+id/virtualizer_actvity"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="@string/virtualizer_test_name">
+    </Button>
+
+    <Button android:id="@+id/bassboost_actvity"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="@string/bassboost_test_name">
+    </Button>
+
+    <Button android:id="@+id/visualizer_actvity"
+        android:layout_width="fill_parent" android:layout_height="wrap_content"
+        android:text="@string/visualizer_test_name">
+    </Button>
+
+    <ListView android:id="@+id/effect_list"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:drawSelectorOnTop="false"/>
+
+
+</LinearLayout>
diff --git a/media/tests/EffectsTest/res/layout/envreverbtest.xml b/media/tests/EffectsTest/res/layout/envreverbtest.xml
new file mode 100755
index 0000000..01c3240
--- /dev/null
+++ b/media/tests/EffectsTest/res/layout/envreverbtest.xml
@@ -0,0 +1,553 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/rvbReleaseLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/rvbReleaseText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_release"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/rvbReleaseButton"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/rvbControlLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/rvbControlText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_control"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/rvbOnOff"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/auxFrame"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="3dip"
+        android:layout_marginTop="3dip"
+        android:layout_marginRight="3dip"
+        android:layout_marginBottom="3dip" >
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_gravity="left"
+            android:layout_weight="1.0"
+            android:orientation="vertical"
+            android:layout_marginLeft="1dip"
+            android:layout_marginTop="1dip"
+            android:layout_marginRight="1dip"
+            android:layout_marginBottom="1dip"
+            >
+
+            <LinearLayout android:id="@+id/playPauseFrame"
+                android:orientation="horizontal"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:layout_marginLeft="1dip"
+                android:layout_marginTop="1dip"
+                android:layout_marginRight="3dip"
+                android:layout_marginBottom="1dip" >
+
+                <ImageButton android:id="@+id/stop1"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical|left"
+                    android:layout_weight="0.0"
+                    android:src="@drawable/stop"/>
+
+                 <ImageButton android:id="@+id/playPause1"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical|center"
+                    android:layout_weight="0.0"
+                    android:src="@android:drawable/ic_media_play"/>
+
+                 <ToggleButton android:id="@+id/attachButton"
+                    android:layout_width="wrap_content"
+                    android:layout_height="wrap_content"
+                    android:layout_gravity="center_vertical|right"
+                    android:layout_weight="0.0"
+                    android:textOff="@string/effect_attach_off"
+                    android:textOn="@string/effect_attach_on" />
+             </LinearLayout>
+
+             <TextView android:id="@+id/sessionText"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:orientation="vertical"
+            android:layout_gravity="right"
+            android:layout_weight="1.0"
+            android:layout_marginLeft="3dip"
+            android:layout_marginTop="3dip"
+            android:layout_marginRight="1dip"
+            android:layout_marginBottom="3dip"
+            >
+
+            <TextView android:id="@+id/sendLevelText"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/send_level_name" />
+
+            <SeekBar android:id="@+id/sendLevelSeekBar"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:max="100"
+                android:progress="50"
+                android:layout_marginLeft="10dip"
+                android:layout_marginRight="30dip" />
+
+            <TextView android:id="@+id/sendLevelValue"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam1Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_1_name" />
+
+                <SeekBar android:id="@+id/rvbParam1SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam1Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam2Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_2_name" />
+
+                <SeekBar android:id="@+id/rvbParam2SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam2Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam3Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_3_name" />
+
+                <SeekBar android:id="@+id/rvbParam3SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam3Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam4Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_4_name" />
+
+                <SeekBar android:id="@+id/rvbParam4SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam4Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam5Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_5_name" />
+
+                <SeekBar android:id="@+id/rvbParam5SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam5Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam6Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_6_name" />
+
+                <SeekBar android:id="@+id/rvbParam6SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam6Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam7Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_7_name" />
+
+                <SeekBar android:id="@+id/rvbParam7SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam7Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam8Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_8_name" />
+
+                <SeekBar android:id="@+id/rvbParam8SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam8Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam9Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_9_name" />
+
+                <SeekBar android:id="@+id/rvbParam9SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam9Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/rvbParam10Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/rvb_param_10_name" />
+
+                <SeekBar android:id="@+id/rvbParam10SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/rvbParam10Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+        </LinearLayout>
+
+    </ScrollView>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+</LinearLayout>
diff --git a/media/tests/EffectsTest/res/layout/equalizertest.xml b/media/tests/EffectsTest/res/layout/equalizertest.xml
new file mode 100755
index 0000000..2223c48
--- /dev/null
+++ b/media/tests/EffectsTest/res/layout/equalizertest.xml
@@ -0,0 +1,468 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/eqReleaseLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/eqReleaseText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_release"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/eqReleaseButton"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/eqControlLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/eqControlText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_control"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/equalizerOnOff"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/SessionFrame"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/sessionText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/session"
+            style="@android:style/TextAppearance.Medium" />
+
+        <EditText android:id="@+id/sessionEdit"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|right" />
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/eqParam1Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/eq_param_1_name" />
+
+                <LinearLayout android:id="@+id/eqParam1Desc"
+                    android:orientation="horizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginTop="10dip"
+                    android:layout_marginRight="10dip"
+                    android:layout_marginBottom="10dip" >
+
+                    <TextView android:id="@+id/eqParam1Min"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam1Center"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam1Max"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                </LinearLayout>
+
+                <SeekBar android:id="@+id/eqParam1SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/eqParam1Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/eqParam2Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/eq_param_2_name" />
+
+                <LinearLayout android:id="@+id/eqParam2Desc"
+                    android:orientation="horizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginTop="10dip"
+                    android:layout_marginRight="10dip"
+                    android:layout_marginBottom="10dip" >
+
+                    <TextView android:id="@+id/eqParam2Min"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam2Center"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam2Max"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+
+                </LinearLayout>
+
+                <SeekBar android:id="@+id/eqParam2SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/eqParam2Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/eqParam3Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/eq_param_3_name" />
+
+                <LinearLayout android:id="@+id/eqParam3Desc"
+                    android:orientation="horizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginTop="10dip"
+                    android:layout_marginRight="10dip"
+                    android:layout_marginBottom="10dip" >
+
+                    <TextView android:id="@+id/eqParam3Min"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam3Center"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam3Max"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+
+                </LinearLayout>
+
+                <SeekBar android:id="@+id/eqParam3SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/eqParam3Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/eqParam4Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/eq_param_4_name" />
+
+                <LinearLayout android:id="@+id/eqParam4Desc"
+                    android:orientation="horizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginTop="10dip"
+                    android:layout_marginRight="10dip"
+                    android:layout_marginBottom="10dip" >
+
+                    <TextView android:id="@+id/eqParam4Min"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam4Center"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam4Max"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+
+                </LinearLayout>
+
+                <SeekBar android:id="@+id/eqParam4SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/eqParam4Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/eqParam5Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/eq_param_5_name" />
+
+                <LinearLayout android:id="@+id/eqParam5Desc"
+                    android:orientation="horizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginTop="10dip"
+                    android:layout_marginRight="10dip"
+                    android:layout_marginBottom="10dip" >
+
+                    <TextView android:id="@+id/eqParam5Min"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam5Center"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+                    <TextView android:id="@+id/eqParam5Max"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1.0" />
+
+                </LinearLayout>
+
+                <SeekBar android:id="@+id/eqParam5SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/eqParam5Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+                        <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/eqParam6Name"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/eq_param_6_name" />
+
+                <SeekBar android:id="@+id/eqParam6SeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/eqParam6Value"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</LinearLayout>
diff --git a/media/tests/EffectsTest/res/layout/presetreverbtest.xml b/media/tests/EffectsTest/res/layout/presetreverbtest.xml
new file mode 100755
index 0000000..b648899
--- /dev/null
+++ b/media/tests/EffectsTest/res/layout/presetreverbtest.xml
@@ -0,0 +1,177 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2010 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/presetrvbReleaseLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/presetrvbReleaseText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_release"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/presetrvbReleaseButton"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/presetrvbControlLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/presetrvbControlText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_control"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/presetrvbOnOff"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/SessionFrame"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/sessionText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/session"
+            style="@android:style/TextAppearance.Medium" />
+
+        <EditText android:id="@+id/sessionEdit"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|right" />
+    </LinearLayout>
+
+  <ImageView
+       android:src="@android:drawable/divider_horizontal_dark"
+       android:layout_width="fill_parent"
+       android:layout_height="wrap_content"
+       android:scaleType="fitXY"/>
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            android:layout_marginLeft="10dip"
+            android:layout_marginTop="10dip"
+            android:layout_marginRight="10dip"
+            android:layout_marginBottom="10dip"
+            >
+
+            <TextView android:id="@+id/presetrvbParam1Name"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:text="@string/presetrvb_param_1_name" />
+
+            <SeekBar android:id="@+id/presetrvbParam1SeekBar"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:max="100"
+                android:progress="50"
+                android:layout_marginLeft="10dip"
+                android:layout_marginRight="30dip" />
+
+            <TextView android:id="@+id/presetrvbParam1Value"
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content" />
+
+        </LinearLayout>
+
+        <ImageView
+             android:src="@android:drawable/divider_horizontal_dark"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content"
+             android:scaleType="fitXY"/>
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</LinearLayout>
diff --git a/media/tests/EffectsTest/res/layout/virtualizertest.xml b/media/tests/EffectsTest/res/layout/virtualizertest.xml
new file mode 100755
index 0000000..c9203de
--- /dev/null
+++ b/media/tests/EffectsTest/res/layout/virtualizertest.xml
@@ -0,0 +1,192 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/virtReleaseLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/virtReleaseText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_release"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/virtReleaseButton"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/virtControlLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/virtControlText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_control"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/virtualizerOnOff"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/SessionFrame"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/sessionText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/session"
+            style="@android:style/TextAppearance.Medium" />
+
+        <EditText android:id="@+id/sessionEdit"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|right" />
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <LinearLayout
+                android:layout_width="fill_parent"
+                android:layout_height="wrap_content"
+                android:gravity="center_horizontal"
+                android:orientation="vertical"
+                android:layout_marginLeft="10dip"
+                android:layout_marginTop="10dip"
+                android:layout_marginRight="10dip"
+                android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/virtStrengthName"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/stength_name" />
+
+                <LinearLayout android:id="@+id/virtStrengthDesc"
+                    android:orientation="horizontal"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginTop="10dip"
+                    android:layout_marginRight="30dip"
+                    android:layout_marginBottom="10dip" >
+
+                    <TextView android:id="@+id/virtStrengthMin"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1"
+                        android:layout_gravity="left"
+                        android:gravity="left"/>
+                    <TextView android:id="@+id/virtStrengthMax"
+                        android:layout_width="fill_parent"
+                        android:layout_height="fill_parent"
+                        android:layout_weight="1"
+                        android:layout_gravity="right"
+                        android:gravity="right"/>
+                </LinearLayout>
+
+                <SeekBar android:id="@+id/virtStrengthSeekBar"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:max="100"
+                    android:progress="50"
+                    android:layout_marginLeft="10dip"
+                    android:layout_marginRight="30dip" />
+
+                <TextView android:id="@+id/virtStrengthValue"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content" />
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</LinearLayout>
diff --git a/media/tests/EffectsTest/res/layout/visualizertest.xml b/media/tests/EffectsTest/res/layout/visualizertest.xml
new file mode 100755
index 0000000..8611e8c
--- /dev/null
+++ b/media/tests/EffectsTest/res/layout/visualizertest.xml
@@ -0,0 +1,261 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2009 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="fill_parent"
+    android:layout_height="fill_parent"
+    android:orientation="vertical">
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/visuReleaseLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/visuReleaseText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_release"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/visuReleaseButton"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/visuControlLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/visuControlText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="1.0"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/effect_control"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/visualizerOnOff"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+            android:layout_gravity="center_vertical|right"
+            android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/SessionFrame"
+          android:orientation="horizontal"
+          android:layout_width="fill_parent"
+          android:layout_height="wrap_content"
+          android:layout_marginLeft="10dip"
+          android:layout_marginTop="10dip"
+          android:layout_marginRight="10dip"
+          android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/sessionText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|left"
+            android:text="@string/session"
+            style="@android:style/TextAppearance.Medium" />
+
+        <EditText android:id="@+id/sessionEdit"
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:layout_weight="0.5"
+            android:layout_gravity="center_vertical|right" />
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <LinearLayout android:id="@+id/visuCallbackLayout"
+        android:orientation="horizontal"
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginLeft="10dip"
+        android:layout_marginTop="10dip"
+        android:layout_marginRight="10dip"
+        android:layout_marginBottom="10dip" >
+
+        <TextView android:id="@+id/visuCallbackText"
+            android:layout_width="fill_parent"
+            android:layout_height="fill_parent"
+          android:layout_weight="1.0"
+          android:layout_gravity="center_vertical|left"
+            android:text="@string/visu_callback"
+            style="@android:style/TextAppearance.Medium" />
+
+        <ToggleButton android:id="@+id/visuCallbackOnOff"
+            android:layout_width="wrap_content"
+            android:layout_height="fill_parent"
+          android:layout_gravity="center_vertical|right"
+          android:layout_weight="0.0" />
+
+    </LinearLayout>
+
+    <ImageView
+         android:src="@android:drawable/divider_horizontal_dark"
+         android:layout_width="fill_parent"
+         android:layout_height="wrap_content"
+         android:scaleType="fitXY"/>
+
+    <ScrollView
+        android:layout_width="fill_parent"
+        android:layout_height="wrap_content" >
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:orientation="vertical">
+
+            <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            android:layout_marginLeft="10dip"
+            android:layout_marginTop="10dip"
+            android:layout_marginRight="10dip"
+            android:layout_marginBottom="10dip"
+                >
+
+                <TextView android:id="@+id/waveformName"
+                    android:layout_width="fill_parent"
+                    android:layout_height="wrap_content"
+                    android:text="@string/waveform_name" />
+
+          <LinearLayout android:id="@+id/eqParam1Desc"
+              android:orientation="horizontal"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:layout_marginLeft="10dip"
+              android:layout_marginTop="10dip"
+              android:layout_marginRight="10dip"
+              android:layout_marginBottom="10dip" >
+
+            <TextView android:id="@+id/waveformMin"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="1.0" />
+            <TextView android:id="@+id/waveformCenter"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="1.0" />
+            <TextView android:id="@+id/waveformMax"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="1.0" />
+
+          </LinearLayout>
+
+            </LinearLayout>
+
+            <ImageView
+                 android:src="@android:drawable/divider_horizontal_dark"
+                 android:layout_width="fill_parent"
+                 android:layout_height="wrap_content"
+                 android:scaleType="fitXY"/>
+
+        <LinearLayout
+            android:layout_width="fill_parent"
+            android:layout_height="wrap_content"
+            android:gravity="center_horizontal"
+            android:orientation="vertical"
+            android:layout_marginLeft="10dip"
+            android:layout_marginTop="10dip"
+            android:layout_marginRight="10dip"
+            android:layout_marginBottom="10dip"
+            >
+
+          <TextView android:id="@+id/fftName"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:text="@string/fft_name" />
+
+          <LinearLayout android:id="@+id/eqParam1Desc"
+              android:orientation="horizontal"
+              android:layout_width="fill_parent"
+              android:layout_height="wrap_content"
+              android:layout_marginLeft="10dip"
+              android:layout_marginTop="10dip"
+              android:layout_marginRight="10dip"
+              android:layout_marginBottom="10dip" >
+
+            <TextView android:id="@+id/fftMin"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="1.0" />
+            <TextView android:id="@+id/fftCenter"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="1.0" />
+            <TextView android:id="@+id/fftMax"
+                android:layout_width="fill_parent"
+                android:layout_height="fill_parent"
+                android:layout_weight="1.0" />
+
+          </LinearLayout>
+
+        </LinearLayout>
+
+        <ImageView
+             android:src="@android:drawable/divider_horizontal_dark"
+             android:layout_width="fill_parent"
+             android:layout_height="wrap_content"
+             android:scaleType="fitXY"/>
+
+
+        </LinearLayout>
+
+    </ScrollView>
+
+</LinearLayout>
diff --git a/media/tests/EffectsTest/res/raw/mp3_sample.mp3 b/media/tests/EffectsTest/res/raw/mp3_sample.mp3
new file mode 100644
index 0000000..a9d8635
--- /dev/null
+++ b/media/tests/EffectsTest/res/raw/mp3_sample.mp3
Binary files differ
diff --git a/media/tests/EffectsTest/res/raw/sine440_mo_16b_16k.wav b/media/tests/EffectsTest/res/raw/sine440_mo_16b_16k.wav
new file mode 100644
index 0000000..2538b4d6
--- /dev/null
+++ b/media/tests/EffectsTest/res/raw/sine440_mo_16b_16k.wav
Binary files differ
diff --git a/media/tests/EffectsTest/res/values/strings.xml b/media/tests/EffectsTest/res/values/strings.xml
new file mode 100755
index 0000000..2a85184
--- /dev/null
+++ b/media/tests/EffectsTest/res/values/strings.xml
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+    <string name="app_name">Effects Test</string>
+    <string name="effect_control">Effect State</string>
+    <string name="effect_release">Effect Instantiated</string>
+    <string name="effect_bypass">Bypass</string>
+    <string name="envreverb_test_name">Environmental Reverb Test</string>
+    <string name="rvb_param_1_name">Room Level</string>
+    <string name="rvb_param_2_name">Room HF Level</string>
+    <string name="rvb_param_3_name">Decay Time</string>
+    <string name="rvb_param_4_name">Decay HF Ratio</string>
+    <string name="rvb_param_5_name">Reflections Level</string>
+    <string name="rvb_param_6_name">Reflections Delay</string>
+    <string name="rvb_param_7_name">Reverb Level</string>
+    <string name="rvb_param_8_name">Reverb Delay</string>
+    <string name="rvb_param_9_name">Diffusion</string>
+    <string name="rvb_param_10_name">Density</string>
+    <string name="presetrvb_test_name">Preset Reverb Test</string>
+    <string name="presetrvb_param_1_name">Presets</string>
+    <string name="equalizer_test_name">Equalizer Test</string>
+    <string name="session">Audio Session</string>
+    <string name="eq_param_1_name">Band 1 Level</string>
+    <string name="eq_param_2_name">Band 2 Level</string>
+    <string name="eq_param_3_name">Band 3 Level</string>
+    <string name="eq_param_4_name">Band 4 Level</string>
+    <string name="eq_param_5_name">Band 5 Level</string>
+    <string name="eq_param_6_name">Presets</string>
+    <string name="virtualizer_test_name">Virtualizer Test</string>
+    <string name="stength_name">Strength</string>
+    <string name="bassboost_test_name">Bass Boost Test</string>
+    <string name="visualizer_test_name">Visualizer Test</string>
+    <string name="visu_callback">Callback Mode</string>
+    <string name="waveform_name">PCM capture</string>
+    <string name="fft_name">FFT Capture</string>
+    <string name="effect_attach_off">Attach</string>
+    <string name="effect_attach_on">Detach</string>
+    <string name="send_level_name">Send Level</string>
+</resources>
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
new file mode 100755
index 0000000..1a10d64
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/BassBoostTest.java
@@ -0,0 +1,276 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.effectstest;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View.OnClickListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.ToggleButton;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.media.audiofx.BassBoost;
+import android.media.audiofx.AudioEffect;
+
+public class BassBoostTest extends Activity implements OnCheckedChangeListener {
+
+    private final static String TAG = "BassBoostTest";
+
+    private static int NUM_PARAMS = 1;
+
+    private EffectParameter mStrength;
+    private BassBoost mBassBoost = null;
+    ToggleButton mOnOffButton;
+    ToggleButton mReleaseButton;
+    EditText mSessionText;
+    static int sSession = 0;
+    EffectListner mEffectListener = new EffectListner();
+    private static HashMap<Integer, BassBoost> sInstances = new HashMap<Integer, BassBoost>(10);
+    String mSettings = "";
+
+    public BassBoostTest() {
+        Log.d(TAG, "contructor");
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        SeekBar seekBar;
+        TextView textView;
+
+        setContentView(R.layout.bassboosttest);
+
+        mSessionText = (EditText) findViewById(R.id.sessionEdit);
+        mSessionText.setOnKeyListener(mSessionKeyListener);
+
+        mSessionText.setText(Integer.toString(sSession));
+
+        mReleaseButton = (ToggleButton)findViewById(R.id.bbReleaseButton);
+        mOnOffButton = (ToggleButton)findViewById(R.id.bassboostOnOff);
+
+        getEffect(sSession);
+
+        if (mBassBoost != null) {
+            mReleaseButton.setOnCheckedChangeListener(this);
+            mOnOffButton.setOnCheckedChangeListener(this);
+
+            textView = (TextView)findViewById(R.id.bbStrengthMin);
+            textView.setText("0");
+            textView = (TextView)findViewById(R.id.bbStrengthMax);
+            textView.setText("1000");
+            seekBar = (SeekBar)findViewById(R.id.bbStrengthSeekBar);
+            textView = (TextView)findViewById(R.id.bbStrengthValue);
+            mStrength = new BassBoostParam(mBassBoost, 0, 1000, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mStrength);
+            mStrength.setEnabled(mBassBoost.getStrengthSupported());
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    private View.OnKeyListener mSessionKeyListener
+    = new View.OnKeyListener() {
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            Log.d(TAG, "onKey() keyCode: "+keyCode+" event.getAction(): "+event.getAction());
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                switch (keyCode) {
+                    case KeyEvent.KEYCODE_DPAD_CENTER:
+                    case KeyEvent.KEYCODE_ENTER:
+                        try {
+                            sSession = Integer.parseInt(mSessionText.getText().toString());
+                            getEffect(sSession);
+                            if (mBassBoost != null) {
+                                mStrength.setEffect(mBassBoost);
+                                mStrength.setEnabled(mBassBoost.getStrengthSupported());
+                            }
+                        } catch (NumberFormatException e) {
+                            Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString());
+                        }
+                        return true;
+                }
+            }
+            return false;
+        }
+    };
+
+    // OnCheckedChangeListener
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (buttonView.getId() == R.id.bassboostOnOff) {
+            if (mBassBoost != null) {
+                mBassBoost.setEnabled(isChecked);
+                mStrength.updateDisplay();
+            }
+        }
+        if (buttonView.getId() == R.id.bbReleaseButton) {
+            if (isChecked) {
+                if (mBassBoost == null) {
+                    getEffect(sSession);
+                    if (mBassBoost != null) {
+                        mStrength.setEffect(mBassBoost);
+                        mStrength.setEnabled(mBassBoost.getStrengthSupported());
+                    }
+                }
+            } else {
+                if (mBassBoost != null) {
+                    mStrength.setEnabled(false);
+                    putEffect(sSession);
+                }
+            }
+        }
+    }
+
+    private class BassBoostParam extends EffectParameter {
+        private BassBoost mBassBoost;
+
+        public BassBoostParam(BassBoost bassboost, int min, int max, SeekBar seekBar, TextView textView) {
+            super (min, max, seekBar, textView, "o/oo");
+
+            mBassBoost = bassboost;
+            updateDisplay();
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mBassBoost != null) {
+                mBassBoost.setStrength(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mBassBoost != null) {
+                return new Integer(mBassBoost.getRoundedStrength());
+            }
+            return new Integer(0);
+        }
+
+        @Override
+        public void setEffect(Object effect) {
+            mBassBoost = (BassBoost)effect;
+        }
+    }
+
+    public class EffectListner implements AudioEffect.OnEnableStatusChangeListener,
+        AudioEffect.OnControlStatusChangeListener, AudioEffect.OnParameterChangeListener
+   {
+        public EffectListner() {
+        }
+        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+            Log.d(TAG,"onEnableStatusChange: "+ enabled);
+        }
+        public void onControlStatusChange(AudioEffect effect, boolean controlGranted) {
+            Log.d(TAG,"onControlStatusChange: "+ controlGranted);
+        }
+        public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
+            int p = byteArrayToInt(param, 0);
+            short v = byteArrayToShort(value, 0);
+
+            Log.d(TAG,"onParameterChange, status: "+status+" p: "+p+" v: "+v);
+        }
+
+        private int byteArrayToInt(byte[] valueBuf, int offset) {
+            ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+            converter.order(ByteOrder.nativeOrder());
+            return converter.getInt(offset);
+
+        }
+        private short byteArrayToShort(byte[] valueBuf, int offset) {
+            ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+            converter.order(ByteOrder.nativeOrder());
+            return converter.getShort(offset);
+
+        }
+
+    }
+
+    private void getEffect(int session) {
+        synchronized (sInstances) {
+            if (sInstances.containsKey(session)) {
+                mBassBoost = sInstances.get(session);
+            } else {
+                try{
+                    mBassBoost = new BassBoost(0, session);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG,"BassBoost effect not supported");
+                } catch (IllegalStateException e) {
+                    Log.e(TAG,"BassBoost cannot get strength supported");
+                } catch (UnsupportedOperationException e) {
+                    Log.e(TAG,"BassBoost library not loaded");
+                } catch (RuntimeException e) {
+                    Log.e(TAG,"BassBoost effect not found");
+                }
+                sInstances.put(session, mBassBoost);
+            }
+            mReleaseButton.setEnabled(false);
+            mOnOffButton.setEnabled(false);
+
+            if (mBassBoost != null) {
+                if (mSettings != "") {
+                    mBassBoost.setProperties(new BassBoost.Settings(mSettings));
+                }
+                mBassBoost.setEnableStatusListener(mEffectListener);
+                mBassBoost.setControlStatusListener(mEffectListener);
+                mBassBoost.setParameterListener(mEffectListener);
+
+                mReleaseButton.setChecked(true);
+                mReleaseButton.setEnabled(true);
+
+                mOnOffButton.setChecked(mBassBoost.getEnabled());
+                mOnOffButton.setEnabled(true);
+            }
+        }
+    }
+
+    private void putEffect(int session) {
+        mOnOffButton.setChecked(false);
+        mOnOffButton.setEnabled(false);
+        synchronized (sInstances) {
+            if (mBassBoost != null) {
+                mSettings = mBassBoost.getProperties().toString();
+                mBassBoost.release();
+                Log.d(TAG,"BassBoost released");
+                mBassBoost = null;
+                sInstances.remove(session);
+            }
+        }
+    }
+
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectParameter.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectParameter.java
new file mode 100755
index 0000000..95077e7
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectParameter.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.effectstest;
+
+import android.os.Bundle;
+import android.util.Log;
+import android.widget.TextView;
+import android.widget.SeekBar;
+
+
+abstract class EffectParameter implements SeekBar.OnSeekBarChangeListener {
+
+    private final static String TAG = "EffectParameter";
+
+    protected int mMin;
+    protected int mMax;
+    protected String mUnit;
+    protected SeekBar mSeekBar;
+    protected TextView mValueText;
+
+    public EffectParameter (int min, int max, SeekBar seekBar, TextView textView, String unit) {
+        mMin = min;
+        mMax = max;
+        mSeekBar = seekBar;
+        mValueText = textView;
+        mUnit = unit;
+        byte[] paramBuf = new byte[4];
+
+        mSeekBar.setMax(max-min);
+    }
+
+    public void displayValue(int value, boolean fromTouch) {
+        String text = Integer.toString(value)+" "+mUnit;
+        mValueText.setText(text);
+        if (!fromTouch) {
+            mSeekBar.setProgress(value - mMin);
+        }
+    }
+
+    public void updateDisplay() {
+        displayValue(getParameter(), false);
+    }
+
+    public abstract void setParameter(Integer value);
+
+    public abstract Integer getParameter();
+
+    public abstract void setEffect(Object effect);
+
+    // SeekBar.OnSeekBarChangeListener
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
+
+        if (seekBar != mSeekBar) {
+            Log.e(TAG, "onProgressChanged called with wrong seekBar");
+            return;
+        }
+
+        int value = progress + mMin;
+        if (fromTouch) {
+            setParameter(value);
+        }
+
+        displayValue(getParameter(), fromTouch);
+    }
+
+    public void onStartTrackingTouch(SeekBar seekBar) {
+    }
+
+    public void onStopTrackingTouch(SeekBar seekBar) {
+    }
+
+    public void setEnabled(boolean e) {
+        mSeekBar.setEnabled(e);
+    }
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
new file mode 100755
index 0000000..6612766
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.effectstest;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View.OnClickListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.ListView;
+import android.widget.BaseAdapter;
+import android.widget.LinearLayout;
+import android.media.audiofx.AudioEffect;
+import java.util.UUID;
+
+public class EffectsTest extends Activity {
+
+    private final static String TAG = "EffectsTest";
+
+
+    public EffectsTest() {
+        Log.d(TAG, "contructor");
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        setContentView(R.layout.effectstest);
+
+        Button button = (Button) findViewById(R.id.env_reverb_actvity);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                startActivity(new Intent(EffectsTest.this, EnvReverbTest.class));
+            }
+        });
+
+        button = (Button) findViewById(R.id.preset_reverb_actvity);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                startActivity(new Intent(EffectsTest.this, PresetReverbTest.class));
+            }
+        });
+
+        button = (Button) findViewById(R.id.equalizer_actvity);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                startActivity(new Intent(EffectsTest.this, EqualizerTest.class));
+            }
+        });
+
+        button = (Button) findViewById(R.id.virtualizer_actvity);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                startActivity(new Intent(EffectsTest.this, VirtualizerTest.class));
+            }
+        });
+
+        button = (Button) findViewById(R.id.bassboost_actvity);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                startActivity(new Intent(EffectsTest.this, BassBoostTest.class));
+            }
+        });
+
+        button = (Button) findViewById(R.id.visualizer_actvity);
+        button.setOnClickListener(new OnClickListener() {
+            public void onClick(View v) {
+                startActivity(new Intent(EffectsTest.this, VisualizerTest.class));
+            }
+        });
+
+        AudioEffect.Descriptor[] descriptors = AudioEffect.queryEffects();
+
+        ListView list = (ListView) findViewById(R.id.effect_list);
+        list.setAdapter(new EffectListAdapter(this, descriptors));
+
+    }
+
+    private class EffectListAdapter extends BaseAdapter {
+
+        private Context mContext;
+
+        AudioEffect.Descriptor[] mDescriptors;
+
+        public EffectListAdapter(Context context, AudioEffect.Descriptor[] descriptors) {
+            Log.d(TAG, "EffectListAdapter contructor");
+            mContext = context;
+            mDescriptors = descriptors;
+            for (int i = 0; i < mDescriptors.length; i++) {
+                Log.d(TAG, "Effect: "+i+" name: "+ mDescriptors[i].name);
+            }
+        }
+
+         public int getCount() {
+            Log.d(TAG, "EffectListAdapter getCount(): "+mDescriptors.length);
+            return mDescriptors.length;
+        }
+
+        public Object getItem(int position) {
+            Log.d(TAG, "EffectListAdapter getItem() at: "+position+" name: "
+                    +mDescriptors[position].name);
+            return mDescriptors[position];
+        }
+
+        public long getItemId(int position) {
+            return position;
+        }
+
+        public View getView(int position, View convertView, ViewGroup parent) {
+            EffectView ev;
+            if (convertView == null) {
+                Log.d(TAG, "getView() new EffectView position: " + position);
+                ev = new EffectView(mContext, mDescriptors);
+            } else {
+                Log.d(TAG, "getView() convertView position: " + position);
+                ev = new EffectView(mContext, mDescriptors);
+                //ev = (EffectView) convertView;
+            }
+            ev.set(position);
+            return ev;
+        }
+    }
+
+    private class EffectView extends LinearLayout {
+        private Context mContext;
+        AudioEffect.Descriptor[] mDescriptors;
+
+        public EffectView(Context context, AudioEffect.Descriptor[] descriptors) {
+            super(context);
+
+            mContext = context;
+            mDescriptors = descriptors;
+            this.setOrientation(VERTICAL);
+        }
+
+        public void set(int position) {
+            TextView tv = new TextView(mContext);
+            tv.setText("Effect "+ position);
+            addView(tv, new LinearLayout.LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+            tv = new TextView(mContext);
+            tv.setText(" type: "+ mDescriptors[position].type.toString());
+            addView(tv, new LinearLayout.LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+            tv = new TextView(mContext);
+            tv.setText(" uuid: "+ mDescriptors[position].uuid.toString());
+            addView(tv, new LinearLayout.LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+            tv = new TextView(mContext);
+            tv.setText(" name: "+ mDescriptors[position].name);
+            addView(tv, new LinearLayout.LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+            tv = new TextView(mContext);
+            tv.setText(" vendor: "+ mDescriptors[position].implementor);
+            addView(tv, new LinearLayout.LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+            tv = new TextView(mContext);
+            tv.setText(" mode: "+ mDescriptors[position].connectMode);
+            addView(tv, new LinearLayout.LayoutParams(
+                    LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
+        }
+    }
+
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EnvReverbTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EnvReverbTest.java
new file mode 100755
index 0000000..594e844
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EnvReverbTest.java
@@ -0,0 +1,568 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.effectstest;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View.OnClickListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.SeekBar;
+import android.widget.ToggleButton;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.media.audiofx.EnvironmentalReverb;
+import android.media.audiofx.AudioEffect;
+import android.media.AudioManager;
+
+public class EnvReverbTest extends Activity implements OnCheckedChangeListener, SeekBar.OnSeekBarChangeListener {
+
+    private final static String TAG = "EnvReverbTest";
+
+    private static int NUM_PARAMS = 10;
+
+    private EffectParameter[] mParameters = new EffectParameter[NUM_PARAMS];
+    private EnvironmentalReverb mReverb;
+    ToggleButton mOnOffButton;
+    ToggleButton mReleaseButton;
+    ToggleButton mAttachButton;
+    private static HashMap<Integer, EnvironmentalReverb> sInstances = new HashMap<Integer, EnvironmentalReverb>(10);
+    static SimplePlayer sPlayerController = null;
+    SeekBar mSendLevelSeekBar;
+    TextView mSendLevelDisplay;
+    static float sSendLevel = linToExp(50,100);
+    static boolean sAttached = false;
+    String mSettings = "";
+
+    public EnvReverbTest() {
+        Log.d(TAG, "contructor");
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+        Log.d(TAG, "onCreate");
+        SeekBar seekBar;
+        TextView textView;
+        ToggleButton button;
+        setContentView(R.layout.envreverbtest);
+
+        ImageView playPause = (ImageView) findViewById(R.id.playPause1);
+        ImageView stop = (ImageView) findViewById(R.id.stop1);
+        textView = (TextView) findViewById(R.id.sessionText);
+        if (sPlayerController == null) {
+            sPlayerController = new SimplePlayer(this, R.id.playPause1, playPause,
+                    R.id.stop1, stop, textView,
+                    R.raw.mp3_sample, AudioManager.STREAM_MUSIC, 0);
+        } else {
+            sPlayerController.set(this, R.id.playPause1, playPause,
+                    R.id.stop1, stop, textView,
+                    AudioManager.STREAM_MUSIC, 0);
+        }
+
+        // send level
+        mSendLevelSeekBar = (SeekBar)findViewById(R.id.sendLevelSeekBar);
+        mSendLevelDisplay = (TextView)findViewById(R.id.sendLevelValue);
+        mSendLevelSeekBar.setMax(100);
+        mSendLevelSeekBar.setOnSeekBarChangeListener(this);
+        mSendLevelSeekBar.setProgress(expToLin(sSendLevel,100));
+        sPlayerController.setAuxEffectSendLevel(sSendLevel);
+
+        mOnOffButton = (ToggleButton)findViewById(R.id.rvbOnOff);
+        mReleaseButton = (ToggleButton)findViewById(R.id.rvbReleaseButton);
+        mAttachButton = (ToggleButton)findViewById(R.id.attachButton);
+
+        getEffect(0);
+
+        if (mReverb != null) {
+            mOnOffButton.setOnCheckedChangeListener(this);
+            mReleaseButton.setOnCheckedChangeListener(this);
+            mAttachButton.setOnCheckedChangeListener(this);
+
+//            button = (ToggleButton)findViewById(R.id.rvbBypass);
+//            button.setChecked(false);
+//            button.setOnCheckedChangeListener(this);
+
+            // Room level
+            seekBar = (SeekBar)findViewById(R.id.rvbParam1SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam1Value);
+            mParameters[0] = new RoomLevelParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[0]);
+
+            // Room HF level
+            seekBar = (SeekBar)findViewById(R.id.rvbParam2SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam2Value);
+            mParameters[1] = new RoomHFLevelParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[1]);
+
+            // Decay time
+            seekBar = (SeekBar)findViewById(R.id.rvbParam3SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam3Value);
+            mParameters[2] = new DecayTimeParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[2]);
+
+            // Decay HF ratio
+            seekBar = (SeekBar)findViewById(R.id.rvbParam4SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam4Value);
+            mParameters[3] = new DecayHFRatioParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[3]);
+
+            // Reflections level
+            seekBar = (SeekBar)findViewById(R.id.rvbParam5SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam5Value);
+            mParameters[4] = new ReflectionsLevelParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[4]);
+
+            // Reflections delay
+            seekBar = (SeekBar)findViewById(R.id.rvbParam6SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam6Value);
+            mParameters[5] = new ReflectionsDelayParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[5]);
+
+            // Reverb level
+            seekBar = (SeekBar)findViewById(R.id.rvbParam7SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam7Value);
+            mParameters[6] = new ReverbLevelParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[6]);
+
+            // Reverb delay
+            seekBar = (SeekBar)findViewById(R.id.rvbParam8SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam8Value);
+            mParameters[7] = new ReverbDelayParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[7]);
+
+            // Diffusion
+            seekBar = (SeekBar)findViewById(R.id.rvbParam9SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam9Value);
+            mParameters[8] = new DiffusionParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[8]);
+
+            // Density
+            seekBar = (SeekBar)findViewById(R.id.rvbParam10SeekBar);
+            textView = (TextView)findViewById(R.id.rvbParam10Value);
+            mParameters[9] = new DensityParam(mReverb, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[9]);
+        }
+    }
+    @Override
+    public void onResume() {
+        super.onResume();
+        Log.d(TAG, "onResume");
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    // OnCheckedChangeListener
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (buttonView.getId() == R.id.rvbOnOff) {
+            if (mReverb != null) {
+                mReverb.setEnabled(isChecked);
+                Log.d(TAG,"onCheckedChanged: rvbOnOff");
+                for (int i = 0 ; i < mParameters.length; i++) {
+                    mParameters[i].updateDisplay();
+                }
+            }
+        }
+        if (buttonView.getId() == R.id.rvbReleaseButton) {
+            if (isChecked) {
+                if (mReverb == null) {
+                    getEffect(0);
+                    for (int i = 0 ; i < mParameters.length; i++) {
+                        mParameters[i].setEffect(mReverb);
+                        mParameters[i].setEnabled(true);
+                    }
+                }
+            } else {
+                if (mReverb != null) {
+                    for (int i = 0 ; i < mParameters.length; i++) {
+                        mParameters[i].setEnabled(false);
+                    }
+                    putEffect(0);
+                }
+            }
+        }
+//        if (buttonView.getId() == R.id.rvbBypass) {
+//            // REVERB_PARAM_BYPASS parametervalue is 11 in EffectEnvironmentalReverApi.h
+//            if (mReverb != null) {
+//                if (isChecked) {
+//                    mReverb.setParameter((int)11, (int)1);
+//                } else {
+//                    mReverb.setParameter((int)11, (int)0);
+//                }
+//            }
+//        }
+        if (buttonView.getId() == R.id.attachButton) {
+            if (mReverb != null) {
+                if (isChecked) {
+                    sPlayerController.attachAuxEffect(mReverb.getId());
+                    sAttached = true;
+                } else {
+                    sPlayerController.attachAuxEffect(0);
+                    sAttached = false;
+                }
+            }
+        }
+    }
+
+    // SeekBar.OnSeekBarChangeListener
+    public void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch) {
+
+        if (seekBar != mSendLevelSeekBar) {
+            Log.e(TAG, "onProgressChanged called with wrong seekBar");
+            return;
+        }
+
+        sSendLevel = linToExp(progress,100);
+        if (fromTouch) {
+            sPlayerController.setAuxEffectSendLevel(sSendLevel);
+        }
+        String text = Float.toString(sSendLevel);
+        mSendLevelDisplay.setText(text);
+        if (!fromTouch) {
+            seekBar.setProgress(progress);
+        }
+    }
+
+    static float linToExp(int lin, int range) {
+        if (lin == 0) return 0;
+        return (float)Math.pow((double)10,(double)72*(lin-range)/(20*range));
+    }
+
+    static int expToLin(float exp, int range) {
+        if (exp == 0) return 0;
+        return (int)(20*range*Math.log10((double)exp)/72 + range);
+    }
+
+    public void onStartTrackingTouch(SeekBar seekBar) {
+    }
+
+    public void onStopTrackingTouch(SeekBar seekBar) {
+    }
+
+    private class EnvReverbParam extends EffectParameter {
+        private EnvironmentalReverb mReverb;
+
+        public EnvReverbParam(EnvironmentalReverb reverb, int min, int max, SeekBar seekBar, TextView textView, String unit) {
+            super (min, max, seekBar, textView, unit);
+            mReverb = reverb;
+            updateDisplay();
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+        }
+
+        @Override
+        public Integer getParameter() {
+            return new Integer(0);
+        }
+
+        @Override
+        public void setEffect(Object reverb) {
+            mReverb = (EnvironmentalReverb)reverb;
+        }
+    }
+
+    private class RoomLevelParam extends EnvReverbParam {
+
+        public RoomLevelParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, -9600, 0, seekBar, textView, "mB");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setRoomLevel(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return new Integer(mReverb.getRoomLevel());
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class RoomHFLevelParam extends EnvReverbParam {
+
+        public RoomHFLevelParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, -4000, 0, seekBar, textView, "mB");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setRoomHFLevel(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return new Integer(mReverb.getRoomHFLevel());
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class DecayTimeParam extends EnvReverbParam {
+
+        public DecayTimeParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, 200, 4000, seekBar, textView, "ms");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setDecayTime(value.intValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return mReverb.getDecayTime();
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class DecayHFRatioParam extends EnvReverbParam {
+
+        public DecayHFRatioParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, 100, 1000, seekBar, textView, "permilles");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setDecayHFRatio(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return new Integer(mReverb.getDecayHFRatio());
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class ReflectionsLevelParam extends EnvReverbParam {
+
+        public ReflectionsLevelParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, -9600, 0, seekBar, textView, "mB");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setReflectionsLevel(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return new Integer(mReverb.getReflectionsLevel());
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class ReflectionsDelayParam extends EnvReverbParam {
+
+        public ReflectionsDelayParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, 0, 65, seekBar, textView, "ms");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setReflectionsDelay(value.intValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return mReverb.getReflectionsDelay();
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class ReverbLevelParam extends EnvReverbParam {
+
+        public ReverbLevelParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, -9600, 2000, seekBar, textView, "mB");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setReverbLevel(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return new Integer(mReverb.getReverbLevel());
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class ReverbDelayParam extends EnvReverbParam {
+
+        public ReverbDelayParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, 0, 65, seekBar, textView, "ms");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setReverbDelay(value.intValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return mReverb.getReverbDelay();
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class DiffusionParam extends EnvReverbParam {
+
+        public DiffusionParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, 0, 1000, seekBar, textView, "permilles");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setDiffusion(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return new Integer(mReverb.getDiffusion());
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class DensityParam extends EnvReverbParam {
+
+        public DensityParam(EnvironmentalReverb reverb, SeekBar seekBar, TextView textView) {
+            super (reverb, 0, 1000, seekBar, textView, "permilles");
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mReverb != null) {
+                mReverb.setDensity(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mReverb != null) {
+                return new Integer(mReverb.getDensity());
+            }
+            return new Integer(0);
+        }
+    }
+
+    private void getEffect(int session) {
+        synchronized (sInstances) {
+            if (sInstances.containsKey(session)) {
+                mReverb = sInstances.get(session);
+            } else {
+                try{
+                    mReverb = new EnvironmentalReverb(0, session);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG,"Reverb effect not supported");
+                } catch (UnsupportedOperationException e) {
+                    Log.e(TAG,"Reverb library not loaded");
+                } catch (RuntimeException e) {
+                    Log.e(TAG,"Reverb effect not found");
+                }
+                Log.d(TAG, "new reverb: "+mReverb);
+                sInstances.put(session, mReverb);
+            }
+        }
+        mReleaseButton.setEnabled(false);
+        mOnOffButton.setEnabled(false);
+        mAttachButton.setEnabled(false);
+        if (mReverb != null) {
+            if (mSettings != "") {
+                mReverb.setProperties(new EnvironmentalReverb.Settings(mSettings));
+            }
+            mReleaseButton.setChecked(true);
+            mReleaseButton.setEnabled(true);
+            mOnOffButton.setChecked(mReverb.getEnabled());
+            mOnOffButton.setEnabled(true);
+            mAttachButton.setChecked(false);
+            mAttachButton.setEnabled(true);
+            if (sAttached) {
+                mAttachButton.setChecked(true);
+                sPlayerController.attachAuxEffect(mReverb.getId());
+            }
+        }
+    }
+
+    private void putEffect(int session) {
+        mOnOffButton.setChecked(false);
+        mOnOffButton.setEnabled(false);
+        mAttachButton.setChecked(false);
+        mAttachButton.setEnabled(false);
+        synchronized (sInstances) {
+            if (mReverb != null) {
+                mSettings = mReverb.getProperties().toString();
+                mReverb.release();
+                Log.d(TAG,"Reverb released, settings: "+mSettings);
+                mReverb = null;
+                sInstances.remove(session);
+            }
+        }
+    }
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EqualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EqualizerTest.java
new file mode 100755
index 0000000..f30a26f
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EqualizerTest.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.effectstest;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View.OnClickListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.ToggleButton;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+
+import android.media.audiofx.Equalizer;
+import android.media.audiofx.AudioEffect;
+
+public class EqualizerTest extends Activity implements OnCheckedChangeListener {
+
+    private final static String TAG = "EqualizerTest";
+
+    private static int NUM_BANDS = 5;
+    private static int NUM_PARAMS = NUM_BANDS + 1;
+
+    private EffectParameter[] mParameters = new EffectParameter[NUM_PARAMS];
+    private Equalizer mEqualizer;
+    ToggleButton mOnOffButton;
+    ToggleButton mReleaseButton;
+    EditText mSessionText;
+    static int sSession = 0;
+    EffectListner mEffectListener = new EffectListner();
+    private static HashMap<Integer, Equalizer> sInstances = new HashMap<Integer, Equalizer>(10);
+    String mSettings = "";
+
+    public EqualizerTest() {
+        Log.d(TAG, "contructor");
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        SeekBar seekBar;
+        TextView textView;
+
+        setContentView(R.layout.equalizertest);
+
+        mSessionText = (EditText) findViewById(R.id.sessionEdit);
+        mSessionText.setOnKeyListener(mSessionKeyListener);
+
+        mSessionText.setText(Integer.toString(sSession));
+
+        mReleaseButton = (ToggleButton)findViewById(R.id.eqReleaseButton);
+        mOnOffButton = (ToggleButton)findViewById(R.id.equalizerOnOff);
+
+        getEffect(sSession);
+
+        if (mEqualizer != null) {
+            mReleaseButton.setOnCheckedChangeListener(this);
+            mOnOffButton.setOnCheckedChangeListener(this);
+
+            short[] bandLevelRange = mEqualizer.getBandLevelRange();
+            int centerFreq;
+            int []freqRange;
+
+            // Band 1 level
+            centerFreq = mEqualizer.getCenterFreq((short)0);
+            freqRange = mEqualizer.getBandFreqRange((short)0);
+            displayFreq(R.id.eqParam1Center, centerFreq);
+            displayFreq(R.id.eqParam1Min, freqRange[0]);
+            displayFreq(R.id.eqParam1Max, freqRange[1]);
+            seekBar = (SeekBar)findViewById(R.id.eqParam1SeekBar);
+            textView = (TextView)findViewById(R.id.eqParam1Value);
+            mParameters[0] = new BandLevelParam(mEqualizer, 0, bandLevelRange[0], bandLevelRange[1], seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[0]);
+
+            // Band 2 level
+            centerFreq = mEqualizer.getCenterFreq((short)1);
+            freqRange = mEqualizer.getBandFreqRange((short)1);
+            displayFreq(R.id.eqParam2Center, centerFreq);
+            displayFreq(R.id.eqParam2Min, freqRange[0]);
+            displayFreq(R.id.eqParam2Max, freqRange[1]);
+            seekBar = (SeekBar)findViewById(R.id.eqParam2SeekBar);
+            textView = (TextView)findViewById(R.id.eqParam2Value);
+            mParameters[1] = new BandLevelParam(mEqualizer, 1, bandLevelRange[0], bandLevelRange[1], seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[1]);
+
+            // Band 3 level
+            centerFreq = mEqualizer.getCenterFreq((short)2);
+            freqRange = mEqualizer.getBandFreqRange((short)2);
+            displayFreq(R.id.eqParam3Center, centerFreq);
+            displayFreq(R.id.eqParam3Min, freqRange[0]);
+            displayFreq(R.id.eqParam3Max, freqRange[1]);
+            seekBar = (SeekBar)findViewById(R.id.eqParam3SeekBar);
+            textView = (TextView)findViewById(R.id.eqParam3Value);
+            mParameters[2] = new BandLevelParam(mEqualizer, 2, bandLevelRange[0], bandLevelRange[1], seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[2]);
+
+            // Band 4 level
+            centerFreq = mEqualizer.getCenterFreq((short)3);
+            freqRange = mEqualizer.getBandFreqRange((short)3);
+            displayFreq(R.id.eqParam4Center, centerFreq);
+            displayFreq(R.id.eqParam4Min, freqRange[0]);
+            displayFreq(R.id.eqParam4Max, freqRange[1]);
+            seekBar = (SeekBar)findViewById(R.id.eqParam4SeekBar);
+            textView = (TextView)findViewById(R.id.eqParam4Value);
+            mParameters[3] = new BandLevelParam(mEqualizer, 3, bandLevelRange[0], bandLevelRange[1], seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[3]);
+
+            // Band 5 level
+            centerFreq = mEqualizer.getCenterFreq((short)4);
+            freqRange = mEqualizer.getBandFreqRange((short)4);
+            displayFreq(R.id.eqParam5Center, centerFreq);
+            displayFreq(R.id.eqParam5Min, freqRange[0]);
+            displayFreq(R.id.eqParam5Max, freqRange[1]);
+            seekBar = (SeekBar)findViewById(R.id.eqParam5SeekBar);
+            textView = (TextView)findViewById(R.id.eqParam5Value);
+            mParameters[4] = new BandLevelParam(mEqualizer, 4, bandLevelRange[0], bandLevelRange[1], seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[4]);
+
+            // Presets
+            short numPresets = mEqualizer.getNumberOfPresets();
+            seekBar = (SeekBar)findViewById(R.id.eqParam6SeekBar);
+            textView = (TextView)findViewById(R.id.eqParam6Value);
+            mParameters[5] = new PresetParam(mEqualizer, (short)0, (short)(numPresets-1), seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[5]);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    private View.OnKeyListener mSessionKeyListener
+    = new View.OnKeyListener() {
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                switch (keyCode) {
+                    case KeyEvent.KEYCODE_DPAD_CENTER:
+                    case KeyEvent.KEYCODE_ENTER:
+                        try {
+                            sSession = Integer.parseInt(mSessionText.getText().toString());
+                            getEffect(sSession);
+                            if (mEqualizer != null) {
+                                for (int i = 0 ; i < mParameters.length; i++) {
+                                    mParameters[i].setEffect(mEqualizer);
+                                    mParameters[i].setEnabled(true);
+                                }
+                            }
+                        } catch (NumberFormatException e) {
+                            Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString());
+                        }
+
+                        return true;
+                }
+            }
+            return false;
+        }
+    };
+
+    // OnCheckedChangeListener
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (buttonView.getId() == R.id.equalizerOnOff) {
+            if (mEqualizer != null) {
+                mEqualizer.setEnabled(isChecked);
+                updateBands();
+            }
+        }
+        if (buttonView.getId() == R.id.eqReleaseButton) {
+            if (isChecked) {
+                if (mEqualizer == null) {
+                    getEffect(sSession);
+                    if (mEqualizer != null) {
+                        for (int i = 0 ; i < mParameters.length; i++) {
+                            mParameters[i].setEffect(mEqualizer);
+                            mParameters[i].setEnabled(true);
+                        }
+                    }
+                }
+            } else {
+                if (mEqualizer != null) {
+                    for (int i = 0 ; i < mParameters.length; i++) {
+                        mParameters[i].setEnabled(false);
+                    }
+                    putEffect(sSession);
+                }
+            }
+        }
+    }
+
+    protected void updateBands() {
+        for (int i = 0 ; i < NUM_BANDS; i++) {
+            mParameters[i].updateDisplay();
+        }
+    }
+
+    private void displayFreq(int viewId, int freq) {
+        TextView textView = (TextView)findViewById(viewId);
+        String text = Integer.toString(freq/1000)+" Hz";
+        textView.setText(text);
+    }
+
+    private class EqualizerParam extends EffectParameter {
+        private Equalizer mEqualizer;
+
+        public EqualizerParam(Equalizer equalizer, int min, int max, SeekBar seekBar, TextView textView, String unit) {
+            super (min, max, seekBar, textView, unit);
+
+            mEqualizer = equalizer;
+            updateDisplay();
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+        }
+
+        @Override
+        public Integer getParameter() {
+            return new Integer(0);
+        }
+
+        @Override
+        public void setEffect(Object eq) {
+            mEqualizer = (Equalizer)eq;
+        }
+    }
+
+    private class BandLevelParam extends EqualizerParam {
+        private int mBand;
+
+        public BandLevelParam(Equalizer equalizer, int band, short min, short max, SeekBar seekBar, TextView textView) {
+            super (equalizer, min, max, seekBar, textView, "mB");
+
+            mBand = band;
+            mEqualizer = equalizer;
+            updateDisplay();
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mEqualizer != null) {
+                mEqualizer.setBandLevel((short)mBand, value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mEqualizer != null) {
+                return new Integer(mEqualizer.getBandLevel((short)mBand));
+            }
+            return new Integer(0);
+        }
+    }
+
+    private class PresetParam extends EqualizerParam {
+
+        public PresetParam(Equalizer equalizer, short min, short max, SeekBar seekBar, TextView textView) {
+            super (equalizer, min, max, seekBar, textView, "");
+
+            mEqualizer = equalizer;
+            updateDisplay();
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mEqualizer != null) {
+                mEqualizer.usePreset(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mEqualizer != null) {
+                return new Integer(mEqualizer.getCurrentPreset());
+            }
+            return new Integer(0);
+        }
+
+        @Override
+        public void displayValue(int value, boolean fromTouch) {
+            String text = mEqualizer.getPresetName((short)value);
+            mValueText.setText(text);
+            if (!fromTouch) {
+                mSeekBar.setProgress(value - mMin);
+            } else {
+                updateBands();
+            }
+        }
+    }
+
+    public class EffectListner implements AudioEffect.OnEnableStatusChangeListener,
+    AudioEffect.OnControlStatusChangeListener,
+    Equalizer.OnParameterChangeListener
+   {
+        public EffectListner() {
+        }
+        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+            Log.d(TAG,"onEnableStatusChange: "+ enabled);
+        }
+        public void onControlStatusChange(AudioEffect effect, boolean controlGranted) {
+            Log.d(TAG,"onControlStatusChange: "+ controlGranted);
+        }
+
+        public void onParameterChange(Equalizer effect, int status, int param1, int param2, int value) {
+            Log.d(TAG,"onParameterChange EQ, status: "+status+" p1: "+param1+" p2: "+param2+" v: "+value);
+        }
+
+        private int byteArrayToInt(byte[] valueBuf, int offset) {
+            ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+            converter.order(ByteOrder.nativeOrder());
+            return converter.getInt(offset);
+
+        }
+        private short byteArrayToShort(byte[] valueBuf, int offset) {
+            ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+            converter.order(ByteOrder.nativeOrder());
+            return converter.getShort(offset);
+
+        }
+    }
+
+    private void getEffect(int session) {
+        synchronized (sInstances) {
+            if (sInstances.containsKey(session)) {
+                mEqualizer = sInstances.get(session);
+            } else {
+                try{
+                    mEqualizer = new Equalizer(0, session);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG,"Equalizer effect not supported");
+                } catch (UnsupportedOperationException e) {
+                    Log.e(TAG,"Equalizer library not loaded");
+                } catch (IllegalStateException e) {
+                    Log.e(TAG,"Equalizer cannot get presets");
+                } catch (RuntimeException e) {
+                    Log.e(TAG,"Equalizer effect not found");
+                }
+                sInstances.put(session, mEqualizer);
+            }
+        }
+        mReleaseButton.setEnabled(false);
+        mOnOffButton.setEnabled(false);
+        if (mEqualizer != null) {
+            if (mSettings != "") {
+                Log.d(TAG,"Equalizer settings: "+mSettings);
+                mEqualizer.setProperties(new Equalizer.Settings(mSettings));
+            }
+
+            mEqualizer.setEnableStatusListener(mEffectListener);
+            mEqualizer.setControlStatusListener(mEffectListener);
+            mEqualizer.setParameterListener(mEffectListener);
+
+            mReleaseButton.setChecked(true);
+            mReleaseButton.setEnabled(true);
+
+            mOnOffButton.setChecked(mEqualizer.getEnabled());
+            mOnOffButton.setEnabled(true);
+        }
+    }
+
+    private void putEffect(int session) {
+//        mOnOffButton.setChecked(false);
+        mOnOffButton.setEnabled(false);
+        synchronized (sInstances) {
+            if (mEqualizer != null) {
+                mSettings = mEqualizer.getProperties().toString();
+                mEqualizer.release();
+                Log.d(TAG,"Equalizer released, settings: "+mSettings);
+                mEqualizer = null;
+                sInstances.remove(session);
+            }
+        }
+    }
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/PresetReverbTest.java b/media/tests/EffectsTest/src/com/android/effectstest/PresetReverbTest.java
new file mode 100755
index 0000000..91d7948
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/PresetReverbTest.java
@@ -0,0 +1,298 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.effectstest;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View.OnClickListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.ToggleButton;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.media.audiofx.PresetReverb;
+import android.media.audiofx.AudioEffect;
+
+public class PresetReverbTest extends Activity implements OnCheckedChangeListener {
+
+    private final static String TAG = "PresetReverbTest";
+
+    private static int NUM_PARAMS = 1;
+
+    private EffectParameter[] mParameters = new EffectParameter[NUM_PARAMS];
+    private PresetReverb mPresetReverb;
+    ToggleButton mOnOffButton;
+    ToggleButton mReleaseButton;
+    EditText mSessionText;
+    static int sSession = 0;
+    EffectListner mEffectListener = new EffectListner();
+    private static HashMap<Integer, PresetReverb> sInstances = new HashMap<Integer, PresetReverb>(10);
+    String mSettings = "";
+
+    public PresetReverbTest() {
+        Log.d(TAG, "contructor");
+    }
+
+    private static String[] sPresetNames = {
+        "NONE",         //PresetReverb.PRESET_NONE
+        "SMALLROOM",    //PresetReverb.PRESET_SMALLROOM
+        "MEDIUMROOM",   //PresetReverb.PRESET_MEDIUMROOM
+        "LARGEROOM",    //PresetReverb.PRESET_LARGEROOM
+        "MEDIUMHALL",   //PresetReverb.PRESET_MEDIUMHALL
+        "LARGEHALL",    //PresetReverb.PRESET_LARGEHALL
+        "PLATE",        //PresetReverb.PRESET_PLATE
+    };
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        setContentView(R.layout.presetreverbtest);
+
+        mSessionText = (EditText) findViewById(R.id.sessionEdit);
+        mSessionText.setOnKeyListener(mSessionKeyListener);
+
+        mSessionText.setText(Integer.toString(sSession));
+
+        mReleaseButton = (ToggleButton)findViewById(R.id.presetrvbReleaseButton);
+        mOnOffButton = (ToggleButton)findViewById(R.id.presetrvbOnOff);
+
+        getEffect(sSession);
+
+        if (mPresetReverb != null) {
+            mReleaseButton.setOnCheckedChangeListener(this);
+            mOnOffButton.setOnCheckedChangeListener(this);
+            // Presets
+            SeekBar seekBar = (SeekBar)findViewById(R.id.presetrvbParam1SeekBar);
+            TextView textView = (TextView)findViewById(R.id.presetrvbParam1Value);
+            mParameters[0] = new PresetParam(mPresetReverb, (short)0, (short)(sPresetNames.length - 1), seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mParameters[0]);
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    private View.OnKeyListener mSessionKeyListener
+    = new View.OnKeyListener() {
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                switch (keyCode) {
+                    case KeyEvent.KEYCODE_DPAD_CENTER:
+                    case KeyEvent.KEYCODE_ENTER:
+                        try {
+                            sSession = Integer.parseInt(mSessionText.getText().toString());
+                            getEffect(sSession);
+                            if (mPresetReverb != null) {
+                                for (int i = 0 ; i < mParameters.length; i++) {
+                                    mParameters[i].setEffect(mPresetReverb);
+                                    mParameters[i].setEnabled(true);
+                                    mParameters[i].updateDisplay();
+                                }
+                            }
+                        } catch (NumberFormatException e) {
+                            Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString());
+                        }
+
+                        return true;
+                }
+            }
+            return false;
+        }
+    };
+
+    // OnCheckedChangeListener
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (buttonView.getId() == R.id.presetrvbOnOff) {
+            if (mPresetReverb != null) {
+                mPresetReverb.setEnabled(isChecked);
+                updateParams();
+            }
+        }
+        if (buttonView.getId() == R.id.presetrvbReleaseButton) {
+            if (isChecked) {
+                if (mPresetReverb == null) {
+                    getEffect(sSession);
+                    if (mPresetReverb != null) {
+                        for (int i = 0 ; i < mParameters.length; i++) {
+                            mParameters[i].setEffect(mPresetReverb);
+                            mParameters[i].setEnabled(true);
+                            mParameters[i].updateDisplay();
+                        }
+                    }
+                }
+            } else {
+                if (mPresetReverb != null) {
+                    for (int i = 0 ; i < mParameters.length; i++) {
+                        mParameters[i].setEnabled(false);
+                    }
+                    putEffect(sSession);
+                }
+            }
+        }
+    }
+
+    private class PresetParam extends EffectParameter {
+        private PresetReverb mPresetReverb;
+
+        public PresetParam(PresetReverb presetrvb, short min, short max, SeekBar seekBar, TextView textView) {
+            super (min, max, seekBar, textView, "");
+
+            mPresetReverb = presetrvb;
+            updateDisplay();
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mPresetReverb != null) {
+                mPresetReverb.setPreset(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mPresetReverb != null) {
+                return new Integer(mPresetReverb.getPreset());
+            }
+            return new Integer(0);
+        }
+
+        @Override
+        public void displayValue(int value, boolean fromTouch) {
+            mValueText.setText(sPresetNames[value]);
+            if (!fromTouch) {
+                mSeekBar.setProgress(value - mMin);
+            } else {
+                updateParams();
+            }
+        }
+
+        @Override
+        public void setEffect(Object presetrvb) {
+            mPresetReverb = (PresetReverb)presetrvb;
+        }
+
+    }
+
+    protected void updateParams() {
+        for (int i = 0 ; i < mParameters.length; i++) {
+            mParameters[i].updateDisplay();
+        }
+    }
+
+    public class EffectListner implements AudioEffect.OnEnableStatusChangeListener,
+    AudioEffect.OnControlStatusChangeListener,
+    PresetReverb.OnParameterChangeListener
+   {
+        public EffectListner() {
+        }
+        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+            Log.d(TAG,"onEnableStatusChange: "+ enabled);
+        }
+        public void onControlStatusChange(AudioEffect effect, boolean controlGranted) {
+            Log.d(TAG,"onControlStatusChange: "+ controlGranted);
+        }
+
+        public void onParameterChange(PresetReverb effect, int status, int param, short value) {
+            Log.d(TAG,"onParameterChange, status: "+status+" p: "+param+" v: "+value);
+        }
+
+        private int byteArrayToInt(byte[] valueBuf, int offset) {
+            ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+            converter.order(ByteOrder.nativeOrder());
+            return converter.getInt(offset);
+
+        }
+        private short byteArrayToShort(byte[] valueBuf, int offset) {
+            ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+            converter.order(ByteOrder.nativeOrder());
+            return converter.getShort(offset);
+
+        }
+    }
+
+    private void getEffect(int session) {
+        synchronized (sInstances) {
+            if (sInstances.containsKey(session)) {
+                mPresetReverb = sInstances.get(session);
+            } else {
+                try{
+                    mPresetReverb = new PresetReverb(0, session);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG,"PresetReverb effect not supported");
+                } catch (UnsupportedOperationException e) {
+                    Log.e(TAG,"PresetReverb library not loaded");
+                } catch (RuntimeException e) {
+                    Log.e(TAG,"PresetReverb effect not found");
+                }
+                sInstances.put(session, mPresetReverb);
+            }
+        }
+        mReleaseButton.setEnabled(false);
+        mOnOffButton.setEnabled(false);
+
+        if (mPresetReverb != null) {
+            if (mSettings != "") {
+                mPresetReverb.setProperties(new PresetReverb.Settings(mSettings));
+            }
+            mPresetReverb.setEnableStatusListener(mEffectListener);
+            mPresetReverb.setControlStatusListener(mEffectListener);
+            mPresetReverb.setParameterListener(mEffectListener);
+
+            mReleaseButton.setChecked(true);
+            mReleaseButton.setEnabled(true);
+
+            mOnOffButton.setChecked(mPresetReverb.getEnabled());
+            mOnOffButton.setEnabled(true);
+        }
+    }
+
+    private void putEffect(int session) {
+        mOnOffButton.setChecked(false);
+        mOnOffButton.setEnabled(false);
+        synchronized (sInstances) {
+            if (mPresetReverb != null) {
+                mSettings = mPresetReverb.getProperties().toString();
+                mPresetReverb.release();
+                Log.d(TAG,"PresetReverb released");
+                mPresetReverb = null;
+                sInstances.remove(session);
+            }
+        }
+    }
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/SimplePlayer.java b/media/tests/EffectsTest/src/com/android/effectstest/SimplePlayer.java
new file mode 100644
index 0000000..119a604
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/SimplePlayer.java
@@ -0,0 +1,197 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.effectstest;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View.OnClickListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.ToggleButton;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.ImageButton;
+import android.widget.ImageView;
+import android.media.AudioManager;
+import android.media.MediaPlayer;
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+public class SimplePlayer implements OnClickListener {
+
+    private final static String TAG = "SimplePlayer";
+
+    int mPlayPauseButtonId;
+    int mStopButtonId;
+    Context mContext;
+    ImageView mPlayPauseButton;
+    int mPlayImageResource;
+    int mPauseImageResource;
+    String mFileName;
+    int mFileResId;
+    MediaPlayer mMediaPlayer;
+    int mStreamType;
+    int mSession;
+    float mSendLevel = (float)0.5;
+    int mEffectId = 0;
+    TextView mSessionText;
+
+    SimplePlayer(Context context, int playPausebuttonId, ImageView playPausebutton,
+                int stopButtonId, ImageView stopButton, TextView sessionText, String fileName, int stream, int session)
+    {
+        set(context, playPausebuttonId, playPausebutton, stopButtonId, stopButton, sessionText, stream, session);
+        mFileName = fileName;
+    }
+
+    SimplePlayer(Context context, int playPausebuttonId, ImageView playPausebutton,
+            int stopButtonId, ImageView stopButton, TextView sessionText, int fileResId, int stream, int session) {
+        set(context, playPausebuttonId, playPausebutton, stopButtonId, stopButton, sessionText, stream, session);
+        mFileResId = fileResId;
+        mFileName = "";
+    }
+
+    public void set(Context context, int playPausebuttonId, ImageView playPausebutton,
+            int stopButtonId, ImageView stopButton, TextView sessionText, int stream, int session) {
+        mContext = context;
+        mPlayPauseButtonId = playPausebuttonId;
+        mStopButtonId = stopButtonId;
+        mPlayPauseButton = (ImageButton) playPausebutton;
+        ImageButton stop = (ImageButton) stopButton;
+
+        mPlayPauseButton.setOnClickListener(this);
+        mPlayPauseButton.requestFocus();
+        stop.setOnClickListener(this);
+
+        mPlayImageResource = android.R.drawable.ic_media_play;
+        mPauseImageResource = android.R.drawable.ic_media_pause;
+        mStreamType = stream;
+        mSession = session;
+        mSessionText = sessionText;
+    }
+
+
+    public void onClick(View v) {
+        if (v.getId() == mPlayPauseButtonId) {
+            playOrPause();
+        } else if (v.getId() == mStopButtonId) {
+            stop();
+        }
+    }
+
+    public void playOrPause() {
+        if (mMediaPlayer == null || !mMediaPlayer.isPlaying()){
+              if (mMediaPlayer == null) {
+                  try {
+                      mMediaPlayer = new MediaPlayer();
+                      if (mSession != 0) {
+                          mMediaPlayer.setAudioSessionId(mSession);
+                          Log.d(TAG, "mMediaPlayer.setAudioSessionId(): "+ mSession);
+                      }
+
+                      if (mFileName.equals("")) {
+                          Log.d(TAG, "Playing from resource");
+                          AssetFileDescriptor afd = mContext.getResources().openRawResourceFd(mFileResId);
+                          mMediaPlayer.setDataSource(afd.getFileDescriptor(), afd.getStartOffset(), afd.getLength());
+                          afd.close();
+                      } else {
+                          Log.d(TAG, "Playing file: "+mFileName);
+                          mMediaPlayer.setDataSource(mFileName);
+                      }
+                      mMediaPlayer.setAudioStreamType(mStreamType);
+                      mMediaPlayer.prepare();
+                      mMediaPlayer.setLooping(true);
+                  } catch (IOException ex) {
+                      Log.e(TAG, "mMediaPlayercreate failed:", ex);
+                      mMediaPlayer = null;
+                  } catch (IllegalArgumentException ex) {
+                      Log.e(TAG, "mMediaPlayercreate failed:", ex);
+                      mMediaPlayer = null;
+                  } catch (SecurityException ex) {
+                      Log.e(TAG, "mMediaPlayercreate failed:", ex);
+                      mMediaPlayer = null;
+                  }
+
+                  if (mMediaPlayer != null) {
+                      mMediaPlayer.setAuxEffectSendLevel(mSendLevel);
+                      mMediaPlayer.attachAuxEffect(mEffectId);
+                      mMediaPlayer.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {
+                          public void onCompletion(MediaPlayer mp) {
+                              updatePlayPauseButton();
+                          }
+                      });
+                      mSessionText.setText("Session: "+Integer.toString(mMediaPlayer.getAudioSessionId()));
+                  }
+              }
+              if (mMediaPlayer != null) {
+                  mMediaPlayer.start();
+              }
+          } else {
+              mMediaPlayer.pause();
+          }
+          updatePlayPauseButton();
+    }
+
+    public void stop() {
+      if (mMediaPlayer != null) {
+          mMediaPlayer.stop();
+          mMediaPlayer.release();
+          mMediaPlayer = null;
+      }
+      updatePlayPauseButton();
+    }
+
+    public boolean isPlaying() {
+        if (mMediaPlayer != null) {
+            return mMediaPlayer.isPlaying();
+        } else {
+            return false;
+        }
+    }
+
+    public void updatePlayPauseButton() {
+        mPlayPauseButton.setImageResource(isPlaying() ? mPauseImageResource : mPlayImageResource);
+    }
+
+    public void attachAuxEffect(int effectId) {
+        mEffectId = effectId;
+        if (mMediaPlayer != null) {
+            Log.d(TAG,"attach effect: "+effectId);
+            mMediaPlayer.attachAuxEffect(effectId);
+        }
+    }
+    public void setAuxEffectSendLevel(float level) {
+        mSendLevel = level;
+        if (mMediaPlayer != null) {
+            mMediaPlayer.setAuxEffectSendLevel(level);
+        }
+    }
+
+    public void setContext(Context context) {
+        mContext = context;
+    }
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VirtualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/VirtualizerTest.java
new file mode 100755
index 0000000..bb32e6f
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/VirtualizerTest.java
@@ -0,0 +1,271 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.effectstest;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View.OnClickListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+import android.widget.EditText;
+import android.widget.SeekBar;
+import android.widget.ToggleButton;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+import android.media.audiofx.Virtualizer;
+import android.media.audiofx.AudioEffect;
+
+public class VirtualizerTest extends Activity implements OnCheckedChangeListener {
+
+    private final static String TAG = "VirtualizerTest";
+
+    private static int NUM_PARAMS = 1;
+
+    private EffectParameter mStrength;
+    private Virtualizer mVirtualizer;
+    ToggleButton mOnOffButton;
+    ToggleButton mReleaseButton;
+    EditText mSessionText;
+    static int sSession = 0;
+    EffectListner mEffectListener = new EffectListner();
+    private static HashMap<Integer, Virtualizer> sInstances = new HashMap<Integer, Virtualizer>(10);
+    String mSettings = "";
+
+    public VirtualizerTest() {
+        Log.d(TAG, "contructor");
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        SeekBar seekBar;
+        TextView textView;
+
+        setContentView(R.layout.virtualizertest);
+
+        mSessionText = (EditText) findViewById(R.id.sessionEdit);
+        mSessionText.setOnKeyListener(mSessionKeyListener);
+        mSessionText.setText(Integer.toString(sSession));
+
+        mReleaseButton = (ToggleButton)findViewById(R.id.virtReleaseButton);
+        mOnOffButton = (ToggleButton)findViewById(R.id.virtualizerOnOff);
+
+        getEffect(sSession);
+
+        if (mVirtualizer != null) {
+            mReleaseButton.setOnCheckedChangeListener(this);
+            mOnOffButton.setOnCheckedChangeListener(this);
+            textView = (TextView)findViewById(R.id.virtStrengthMin);
+            textView.setText("0");
+            textView = (TextView)findViewById(R.id.virtStrengthMax);
+            textView.setText("1000");
+            seekBar = (SeekBar)findViewById(R.id.virtStrengthSeekBar);
+            textView = (TextView)findViewById(R.id.virtStrengthValue);
+            mStrength = new VirtualizerParam(mVirtualizer, 0, 1000, seekBar, textView);
+            seekBar.setOnSeekBarChangeListener(mStrength);
+            mStrength.setEnabled(mVirtualizer.getStrengthSupported());
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    private View.OnKeyListener mSessionKeyListener
+    = new View.OnKeyListener() {
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                switch (keyCode) {
+                    case KeyEvent.KEYCODE_DPAD_CENTER:
+                    case KeyEvent.KEYCODE_ENTER:
+                        try {
+                            sSession = Integer.parseInt(mSessionText.getText().toString());
+                            getEffect(sSession);
+                            if (mVirtualizer != null) {
+                                mStrength.setEffect(mVirtualizer);
+                                mStrength.setEnabled(mVirtualizer.getStrengthSupported());
+                            }
+                        } catch (NumberFormatException e) {
+                            Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString());
+                        }
+                        return true;
+                }
+            }
+            return false;
+        }
+    };
+
+    // OnCheckedChangeListener
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (buttonView.getId() == R.id.virtualizerOnOff) {
+            if (mVirtualizer != null) {
+                mVirtualizer.setEnabled(isChecked);
+                mStrength.updateDisplay();
+            }
+        }
+        if (buttonView.getId() == R.id.virtReleaseButton) {
+            if (isChecked) {
+                if (mVirtualizer == null) {
+                    getEffect(sSession);
+                    if (mVirtualizer != null) {
+                        mStrength.setEffect(mVirtualizer);
+                        mStrength.setEnabled(mVirtualizer.getStrengthSupported());
+                    }
+                }
+            } else {
+                if (mVirtualizer != null) {
+                    mStrength.setEnabled(false);
+                    putEffect(sSession);
+                }
+            }
+        }
+    }
+
+    private class VirtualizerParam extends EffectParameter {
+        private Virtualizer mVirtualizer;
+
+        public VirtualizerParam(Virtualizer virtualizer, int min, int max, SeekBar seekBar, TextView textView) {
+            super (min, max, seekBar, textView, "o/oo");
+
+            mVirtualizer = virtualizer;
+            updateDisplay();
+        }
+
+        @Override
+        public void setParameter(Integer value) {
+            if (mVirtualizer != null) {
+                mVirtualizer.setStrength(value.shortValue());
+            }
+        }
+
+        @Override
+        public Integer getParameter() {
+            if (mVirtualizer != null) {
+                return new Integer(mVirtualizer.getRoundedStrength());
+            }
+            return new Integer(0);
+        }
+
+        @Override
+        public void setEffect(Object effect) {
+            mVirtualizer = (Virtualizer)effect;
+        }
+    }
+
+    public class EffectListner implements AudioEffect.OnEnableStatusChangeListener,
+        AudioEffect.OnControlStatusChangeListener, AudioEffect.OnParameterChangeListener
+   {
+        public EffectListner() {
+        }
+        public void onEnableStatusChange(AudioEffect effect, boolean enabled) {
+            Log.d(TAG,"onEnableStatusChange: "+ enabled);
+        }
+        public void onControlStatusChange(AudioEffect effect, boolean controlGranted) {
+            Log.d(TAG,"onControlStatusChange: "+ controlGranted);
+        }
+        public void onParameterChange(AudioEffect effect, int status, byte[] param, byte[] value) {
+            int p = byteArrayToInt(param, 0);
+            short v = byteArrayToShort(value, 0);
+
+            Log.d(TAG,"onParameterChange, status: "+status+" p: "+p+" v: "+v);
+        }
+
+        private int byteArrayToInt(byte[] valueBuf, int offset) {
+            ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+            converter.order(ByteOrder.nativeOrder());
+            return converter.getInt(offset);
+
+        }
+        private short byteArrayToShort(byte[] valueBuf, int offset) {
+            ByteBuffer converter = ByteBuffer.wrap(valueBuf);
+            converter.order(ByteOrder.nativeOrder());
+            return converter.getShort(offset);
+
+        }
+    }
+
+    private void getEffect(int session) {
+        synchronized (sInstances) {
+            if (sInstances.containsKey(session)) {
+                mVirtualizer = sInstances.get(session);
+            } else {
+                try{
+                    mVirtualizer = new Virtualizer(0, session);
+                } catch (IllegalArgumentException e) {
+                    Log.e(TAG,"Virtualizer effect not supported");
+                } catch (IllegalStateException e) {
+                    Log.e(TAG,"Virtualizer cannot get strength supported");
+                } catch (UnsupportedOperationException e) {
+                    Log.e(TAG,"Virtualizer library not loaded");
+                } catch (RuntimeException e) {
+                    Log.e(TAG,"Virtualizer effect not found");
+                }
+                sInstances.put(session, mVirtualizer);
+            }
+        }
+        mReleaseButton.setEnabled(false);
+        mOnOffButton.setEnabled(false);
+
+        if (mVirtualizer != null) {
+            if (mSettings != "") {
+                mVirtualizer.setProperties(new Virtualizer.Settings(mSettings));
+            }
+            mVirtualizer.setEnableStatusListener(mEffectListener);
+            mVirtualizer.setControlStatusListener(mEffectListener);
+            mVirtualizer.setParameterListener(mEffectListener);
+
+            mReleaseButton.setChecked(true);
+            mReleaseButton.setEnabled(true);
+
+            mOnOffButton.setChecked(mVirtualizer.getEnabled());
+            mOnOffButton.setEnabled(true);
+        }
+    }
+
+    private void putEffect(int session) {
+        mOnOffButton.setChecked(false);
+        mOnOffButton.setEnabled(false);
+        synchronized (sInstances) {
+            if (mVirtualizer != null) {
+                mSettings = mVirtualizer.getProperties().toString();
+                mVirtualizer.release();
+                Log.d(TAG,"Virtualizer released");
+                mVirtualizer = null;
+                sInstances.remove(session);
+            }
+        }
+    }
+}
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
new file mode 100755
index 0000000..60583e0
--- /dev/null
+++ b/media/tests/EffectsTest/src/com/android/effectstest/VisualizerTest.java
@@ -0,0 +1,296 @@
+/*
+ * Copyright (C) 2009 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.effectstest;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.media.audiofx.Visualizer;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.CompoundButton;
+import android.widget.CompoundButton.OnCheckedChangeListener;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.ToggleButton;
+import android.widget.SeekBar;
+
+import java.nio.ByteOrder;
+import java.nio.ByteBuffer;
+import java.util.HashMap;
+import java.util.Map;
+
+public class VisualizerTest extends Activity implements OnCheckedChangeListener {
+
+    private final static String TAG = "Visualizer Test";
+
+    private Visualizer mVisualizer;
+    ToggleButton mOnOffButton;
+    ToggleButton mReleaseButton;
+    boolean mEnabled;
+    EditText mSessionText;
+    static int sSession = 0;
+    int mCaptureSize;
+    ToggleButton mCallbackButton;
+    boolean mCallbackOn;
+    VisualizerListener mVisualizerListener;
+    private static HashMap<Integer, Visualizer> sInstances = new HashMap<Integer, Visualizer>(10);
+    private VisualizerTestHandler mVisualizerTestHandler = null;
+
+    public VisualizerTest() {
+        Log.d(TAG, "contructor");
+    }
+
+    @Override
+    public void onCreate(Bundle icicle) {
+        super.onCreate(icicle);
+
+        TextView textView;
+
+        setContentView(R.layout.visualizertest);
+
+        mSessionText = (EditText) findViewById(R.id.sessionEdit);
+        mSessionText.setOnKeyListener(mSessionKeyListener);
+        mSessionText.setText(Integer.toString(sSession));
+
+        mReleaseButton = (ToggleButton)findViewById(R.id.visuReleaseButton);
+        mOnOffButton = (ToggleButton)findViewById(R.id.visualizerOnOff);
+        mCallbackButton = (ToggleButton)findViewById(R.id.visuCallbackOnOff);
+        mCallbackOn = false;
+        mCallbackButton.setChecked(mCallbackOn);
+
+        mVisualizerTestHandler = new VisualizerTestHandler();
+        mVisualizerListener = new VisualizerListener();
+
+        getEffect(sSession);
+
+        if (mVisualizer != null) {
+            mReleaseButton.setOnCheckedChangeListener(this);
+            mOnOffButton.setOnCheckedChangeListener(this);
+            mCallbackButton.setOnCheckedChangeListener(this);
+        }
+    }
+
+    private static final int MSG_START_CAPTURE = 0;
+    private static final int MSG_STOP_CAPTURE = 1;
+    private static final int MSG_NEW_CAPTURE = 2;
+    private static final int CAPTURE_PERIOD_MS = 100;
+
+    private class VisualizerTestHandler extends Handler {
+        boolean mActive = false;
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MSG_START_CAPTURE:
+                if (!mActive) {
+                    Log.d(TAG, "Start capture");
+                    mActive = true;
+                    sendMessageDelayed(obtainMessage(MSG_NEW_CAPTURE, 0, 0, null), CAPTURE_PERIOD_MS);
+                }
+                break;
+            case MSG_STOP_CAPTURE:
+                if (mActive) {
+                    Log.d(TAG, "Stop capture");
+                    mActive = false;
+                }
+                break;
+            case MSG_NEW_CAPTURE:
+                if (mActive && mVisualizer != null) {
+                    if (mCaptureSize > 0) {
+                        byte[] data = new byte[mCaptureSize];
+                        if (mVisualizer.getWaveForm(data) == Visualizer.SUCCESS) {
+                            int len = data.length < mCaptureSize ? data.length : mCaptureSize;
+                            displayVal(R.id.waveformMin, data[0]);
+                            displayVal(R.id.waveformMax, data[len-1]);
+                            displayVal(R.id.waveformCenter, data[len/2]);
+                        };
+                        if (mVisualizer.getFft(data) == Visualizer.SUCCESS) {
+                            int len = data.length < mCaptureSize ? data.length : mCaptureSize;
+                            displayVal(R.id.fftMin, data[0]);
+                            displayVal(R.id.fftMax, data[len-1]);
+                            displayVal(R.id.fftCenter, data[len/2]);
+                        };
+                    }
+                    sendMessageDelayed(obtainMessage(MSG_NEW_CAPTURE, 0, 0, null), CAPTURE_PERIOD_MS);
+                }
+                break;
+            }
+        }
+    }
+
+    private class VisualizerListener implements Visualizer.OnDataCaptureListener {
+
+        public VisualizerListener() {
+        }
+        public void onWaveFormDataCapture(Visualizer visualizer, byte[] waveform, int samplingRate) {
+            if (visualizer == mVisualizer) {
+                if (waveform.length > 0) {
+                    Log.d(TAG, "onWaveFormDataCapture(): "+waveform[0]+" smp rate: "+samplingRate/1000);
+                    displayVal(R.id.waveformMin, waveform[0]);
+                    displayVal(R.id.waveformMax, waveform[waveform.length - 1]);
+                    displayVal(R.id.waveformCenter, waveform[waveform.length/2]);
+                }
+            }
+        }
+        public void onFftDataCapture(Visualizer visualizer, byte[] fft, int samplingRate) {
+            if (visualizer == mVisualizer) {
+                if (fft.length > 0) {
+                    Log.d(TAG, "onFftDataCapture(): "+fft[0]);
+                    displayVal(R.id.fftMin, fft[0]);
+                    displayVal(R.id.fftMax, fft[fft.length - 1]);
+                    displayVal(R.id.fftCenter, fft[fft.length/2]);
+                }
+            }
+        }
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+    }
+
+    private View.OnKeyListener mSessionKeyListener
+    = new View.OnKeyListener() {
+        public boolean onKey(View v, int keyCode, KeyEvent event) {
+            if (event.getAction() == KeyEvent.ACTION_DOWN) {
+                switch (keyCode) {
+                    case KeyEvent.KEYCODE_DPAD_CENTER:
+                    case KeyEvent.KEYCODE_ENTER:
+                        try {
+                            sSession = Integer.parseInt(mSessionText.getText().toString());
+                            getEffect(sSession);
+                        } catch (NumberFormatException e) {
+                            Log.d(TAG, "Invalid session #: "+mSessionText.getText().toString());
+                        }
+
+                        return true;
+                }
+            }
+            return false;
+        }
+    };
+
+    // OnCheckedChangeListener
+    public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+        if (buttonView.getId() == R.id.visualizerOnOff) {
+            if (mVisualizer != null) {
+                mEnabled = isChecked;
+                mCallbackButton.setEnabled(!mEnabled);
+                if (mCallbackOn && mEnabled) {
+                    mVisualizer.setDataCaptureListener(mVisualizerListener,
+                            10000,
+                            true,
+                            true);
+                }
+                mVisualizer.setEnabled(mEnabled);
+                if (mCallbackOn) {
+                    if (!mEnabled) {
+                        mVisualizer.setDataCaptureListener(null,
+                                10000,
+                                false,
+                                false);
+                    }
+                } else {
+                    int msg = isChecked ? MSG_START_CAPTURE : MSG_STOP_CAPTURE;
+                    mVisualizerTestHandler.sendMessage(
+                            mVisualizerTestHandler.obtainMessage(msg, 0, 0, null));
+                }
+            }
+        }
+        if (buttonView.getId() == R.id.visuReleaseButton) {
+            if (isChecked) {
+                if (mVisualizer == null) {
+                    getEffect(sSession);
+                }
+            } else {
+                if (mVisualizer != null) {
+                    putEffect(sSession);
+                }
+            }
+        }
+        if (buttonView.getId() == R.id.visuCallbackOnOff) {
+            mCallbackOn = isChecked;
+        }
+    }
+
+    private void displayVal(int viewId, int val) {
+        TextView textView = (TextView)findViewById(viewId);
+        String text = Integer.toString(val);
+        textView.setText(text);
+    }
+
+
+    private void getEffect(int session) {
+        synchronized (sInstances) {
+            if (sInstances.containsKey(session)) {
+                mVisualizer = sInstances.get(session);
+            } else {
+                try{
+                    mVisualizer = new Visualizer(session);
+                } catch (UnsupportedOperationException e) {
+                    Log.e(TAG,"Visualizer library not loaded");
+                    throw (new RuntimeException("Cannot initialize effect"));
+                } catch (RuntimeException e) {
+                    throw e;
+                }
+                sInstances.put(session, mVisualizer);
+            }
+        }
+        mReleaseButton.setEnabled(false);
+        mOnOffButton.setEnabled(false);
+        if (mVisualizer != null) {
+            mCaptureSize = mVisualizer.getCaptureSize();
+
+            mReleaseButton.setChecked(true);
+            mReleaseButton.setEnabled(true);
+
+            mEnabled = mVisualizer.getEnabled();
+            mOnOffButton.setChecked(mEnabled);
+            mOnOffButton.setEnabled(true);
+
+            mCallbackButton.setEnabled(!mEnabled);
+        }
+    }
+
+    private void putEffect(int session) {
+        mOnOffButton.setChecked(false);
+        mOnOffButton.setEnabled(false);
+        synchronized (sInstances) {
+            if (mVisualizer != null) {
+                mVisualizer.release();
+                Log.d(TAG,"Visualizer released");
+                mVisualizer = null;
+                sInstances.remove(session);
+            }
+        }
+    }
+
+}
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
index 0917b24..05673c3 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java
@@ -67,7 +67,7 @@
     // database gets upgraded properly. At a minimum, please confirm that 'upgradeVersion'
     // is properly propagated through your change.  Not doing so will result in a loss of user
     // settings.
-    private static final int DATABASE_VERSION = 84;
+    private static final int DATABASE_VERSION = 87;
 
     private Context mContext;
     private int mUserHandle;
@@ -308,7 +308,7 @@
                     Settings.Secure.WIFI_WATCHDOG_PING_DELAY_MS,
                     Settings.Secure.WIFI_WATCHDOG_PING_TIMEOUT_MS,
                 };
-            moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_SECURE, settingsToMove);
+            moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_SECURE, settingsToMove, false);
             upgradeVersion = 28;
         }
 
@@ -674,7 +674,7 @@
                    "lockscreen.lockedoutpermanently",
                    "lockscreen.password_salt"
            };
-           moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_SECURE, settingsToMove);
+           moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_SECURE, settingsToMove, false);
            upgradeVersion = 52;
        }
 
@@ -724,7 +724,7 @@
                     Secure.SET_INSTALL_LOCATION,
                     Secure.DEFAULT_INSTALL_LOCATION
             };
-            moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_SECURE, settingsToMove);
+            moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_SECURE, settingsToMove, false);
             db.beginTransaction();
             SQLiteStatement stmt = null;
             try {
@@ -1209,9 +1209,9 @@
                 // new users can be created.
                 createGlobalTable(db);
                 String[] settingsToMove = hashsetToStringArray(SettingsProvider.sSystemGlobalKeys);
-                moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_GLOBAL, settingsToMove);
+                moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_GLOBAL, settingsToMove, false);
                 settingsToMove = hashsetToStringArray(SettingsProvider.sSecureGlobalKeys);
-                moveSettingsToNewTable(db, TABLE_SECURE, TABLE_GLOBAL, settingsToMove);
+                moveSettingsToNewTable(db, TABLE_SECURE, TABLE_GLOBAL, settingsToMove, false);
 
                 db.setTransactionSuccessful();
             } finally {
@@ -1241,6 +1241,8 @@
                 loadBooleanSetting(stmt,
                         Settings.Secure.ACCESSIBILITY_DISPLAY_MAGNIFICATION_AUTO_UPDATE,
                         R.bool.def_accessibility_display_magnification_auto_update);
+
+                db.setTransactionSuccessful();
             } finally {
                 db.endTransaction();
                 if (stmt != null) stmt.close();
@@ -1248,6 +1250,61 @@
             upgradeVersion = 84;
         }
 
+        if (upgradeVersion == 84) {
+            db.beginTransaction();
+            SQLiteStatement stmt = null;
+            try {
+                // Patch up the slightly-wrong key migration from 82 -> 83 for those
+                // devices that missed it, ignoring if the move is redundant
+                String[] settingsToMove = {
+                        Settings.Secure.ADB_ENABLED,
+                        Settings.Secure.BLUETOOTH_ON,
+                        Settings.Secure.DATA_ROAMING,
+                        Settings.Secure.DEVICE_PROVISIONED,
+                        Settings.Secure.INSTALL_NON_MARKET_APPS,
+                        Settings.Secure.USB_MASS_STORAGE_ENABLED
+                };
+                moveSettingsToNewTable(db, TABLE_SECURE, TABLE_GLOBAL, settingsToMove, true);
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+                if (stmt != null) stmt.close();
+            }
+            upgradeVersion = 85;
+        }
+
+        if (upgradeVersion == 85) {
+            db.beginTransaction();
+            try {
+                // Fix up the migration, ignoring already-migrated elements, to snap up to
+                // date with new changes to the set of global versus system/secure settings
+                String[] settingsToMove = { Settings.System.STAY_ON_WHILE_PLUGGED_IN };
+                moveSettingsToNewTable(db, TABLE_SYSTEM, TABLE_GLOBAL, settingsToMove, true);
+
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+            upgradeVersion = 86;
+        }
+
+        if (upgradeVersion == 86) {
+            db.beginTransaction();
+            try {
+                String[] settingsToMove = {
+                        Settings.Secure.PACKAGE_VERIFIER_ENABLE,
+                        Settings.Secure.PACKAGE_VERIFIER_TIMEOUT,
+                        Settings.Secure.PACKAGE_VERIFIER_DEFAULT_RESPONSE
+                };
+                moveSettingsToNewTable(db, TABLE_SECURE, TABLE_GLOBAL, settingsToMove, true);
+
+                db.setTransactionSuccessful();
+            } finally {
+                db.endTransaction();
+            }
+            upgradeVersion = 87;
+        }
+
         // *** Remember to update DATABASE_VERSION above!
 
         if (upgradeVersion != currentVersion) {
@@ -1282,15 +1339,16 @@
 
     private void moveSettingsToNewTable(SQLiteDatabase db,
             String sourceTable, String destTable,
-            String[] settingsToMove) {
+            String[] settingsToMove, boolean doIgnore) {
         // Copy settings values from the source table to the dest, and remove from the source
         SQLiteStatement insertStmt = null;
         SQLiteStatement deleteStmt = null;
 
         db.beginTransaction();
         try {
-            insertStmt = db.compileStatement("INSERT INTO "
-                    + destTable + " (name,value) SELECT name,value FROM "
+            insertStmt = db.compileStatement("INSERT "
+                    + (doIgnore ? " OR IGNORE " : "")
+                    + " INTO " + destTable + " (name,value) SELECT name,value FROM "
                     + sourceTable + " WHERE name=?");
             deleteStmt = db.compileStatement("DELETE FROM " + sourceTable + " WHERE name=?");
 
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
index 0165977..1096540 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsBackupAgent.java
@@ -563,7 +563,7 @@
                 getContentResolver().insert(contentUri, contentValues);
             }
 
-            if (DEBUG) {
+            if (DEBUG || true) {
                 Log.d(TAG, "Restored setting: " + key + "=" + value);
             }
         }
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index b444eb1..f859f41 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -113,17 +113,22 @@
         // table, shared across all users
         // These must match Settings.Secure.MOVED_TO_GLOBAL
         sSecureGlobalKeys = new HashSet<String>();
+        sSecureGlobalKeys.add(Settings.Secure.ADB_ENABLED);
         sSecureGlobalKeys.add(Settings.Secure.ASSISTED_GPS_ENABLED);
+        sSecureGlobalKeys.add(Settings.Secure.BLUETOOTH_ON);
         sSecureGlobalKeys.add(Settings.Secure.CDMA_CELL_BROADCAST_SMS);
         sSecureGlobalKeys.add(Settings.Secure.CDMA_ROAMING_MODE);
         sSecureGlobalKeys.add(Settings.Secure.CDMA_SUBSCRIPTION_MODE);
         sSecureGlobalKeys.add(Settings.Secure.DATA_ACTIVITY_TIMEOUT_MOBILE);
         sSecureGlobalKeys.add(Settings.Secure.DATA_ACTIVITY_TIMEOUT_WIFI);
+        sSecureGlobalKeys.add(Settings.Secure.DATA_ROAMING);
         sSecureGlobalKeys.add(Settings.Secure.DEVELOPMENT_SETTINGS_ENABLED);
+        sSecureGlobalKeys.add(Settings.Secure.DEVICE_PROVISIONED);
         sSecureGlobalKeys.add(Settings.Secure.DISPLAY_DENSITY_FORCED);
         sSecureGlobalKeys.add(Settings.Secure.DISPLAY_SIZE_FORCED);
         sSecureGlobalKeys.add(Settings.Secure.DOWNLOAD_MAX_BYTES_OVER_MOBILE);
         sSecureGlobalKeys.add(Settings.Secure.DOWNLOAD_RECOMMENDED_MAX_BYTES_OVER_MOBILE);
+        sSecureGlobalKeys.add(Settings.Secure.INSTALL_NON_MARKET_APPS);
         sSecureGlobalKeys.add(Settings.Secure.MOBILE_DATA);
         sSecureGlobalKeys.add(Settings.Secure.NETSTATS_DEV_BUCKET_DURATION);
         sSecureGlobalKeys.add(Settings.Secure.NETSTATS_DEV_DELETE_AGE);
@@ -167,6 +172,7 @@
         sSecureGlobalKeys.add(Settings.Secure.THROTTLE_RESET_DAY);
         sSecureGlobalKeys.add(Settings.Secure.THROTTLE_THRESHOLD_BYTES);
         sSecureGlobalKeys.add(Settings.Secure.THROTTLE_VALUE_KBITSPS);
+        sSecureGlobalKeys.add(Settings.Secure.USB_MASS_STORAGE_ENABLED);
         sSecureGlobalKeys.add(Settings.Secure.USE_GOOGLE_MAIL);
         sSecureGlobalKeys.add(Settings.Secure.WEB_AUTOFILL_QUERY_URL);
         sSecureGlobalKeys.add(Settings.Secure.WIFI_COUNTRY_CODE);
@@ -193,12 +199,6 @@
         // Keys from the 'system' table now moved to 'global'
         // These must match Settings.System.MOVED_TO_GLOBAL
         sSystemGlobalKeys = new HashSet<String>();
-        sSystemGlobalKeys.add(Settings.Secure.ADB_ENABLED);
-        sSystemGlobalKeys.add(Settings.Secure.BLUETOOTH_ON);
-        sSystemGlobalKeys.add(Settings.Secure.DATA_ROAMING);
-        sSystemGlobalKeys.add(Settings.Secure.DEVICE_PROVISIONED);
-        sSystemGlobalKeys.add(Settings.Secure.INSTALL_NON_MARKET_APPS);
-        sSystemGlobalKeys.add(Settings.Secure.USB_MASS_STORAGE_ENABLED);
 
         sSystemGlobalKeys.add(Settings.System.AIRPLANE_MODE_ON);
         sSystemGlobalKeys.add(Settings.System.AIRPLANE_MODE_RADIOS);
@@ -214,6 +214,7 @@
         sSystemGlobalKeys.add(Settings.System.UNLOCK_SOUND);
         sSystemGlobalKeys.add(Settings.System.LOW_BATTERY_SOUND);
         sSystemGlobalKeys.add(Settings.System.POWER_SOUNDS_ENABLED);
+        sSystemGlobalKeys.add(Settings.System.STAY_ON_WHILE_PLUGGED_IN);
         sSystemGlobalKeys.add(Settings.System.WIFI_SLEEP_POLICY);
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
index 54cf73a..6c62680 100755
--- a/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
+++ b/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java
@@ -4152,7 +4152,7 @@
             } catch (ActivityNotFoundException e) {
             }
         }
-        mContext.startActivity(mHomeIntent);
+        mContext.startActivityAsUser(mHomeIntent, UserHandle.CURRENT);
     }
     
     /**
@@ -4181,22 +4181,22 @@
                     Intent dock = createHomeDockIntent();
                     if (dock != null) {
                         int result = ActivityManagerNative.getDefault()
-                                .startActivity(null, dock,
+                                .startActivityAsUser(null, dock,
                                         dock.resolveTypeIfNeeded(mContext.getContentResolver()),
                                         null, null, 0,
                                         ActivityManager.START_FLAG_ONLY_IF_NEEDED,
-                                        null, null, null);
+                                        null, null, null, UserHandle.USER_CURRENT);
                         if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) {
                             return false;
                         }
                     }
                 }
                 int result = ActivityManagerNative.getDefault()
-                        .startActivity(null, mHomeIntent,
+                        .startActivityAsUser(null, mHomeIntent,
                                 mHomeIntent.resolveTypeIfNeeded(mContext.getContentResolver()),
                                 null, null, 0,
                                 ActivityManager.START_FLAG_ONLY_IF_NEEDED,
-                                null, null, null);
+                                null, null, null, UserHandle.USER_CURRENT);
                 if (result == ActivityManager.START_RETURN_INTENT_TO_CALLER) {
                     return false;
                 }
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index 00bc9be..e170ec1 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -62,35 +62,13 @@
     private static final String KEYGUARD_WIDGET_PREFS = "keyguard_widget_prefs";
 
     private static final String TAG = "KeyguardViewHost";
-
-    private static final int SECURITY_SELECTOR_ID = R.id.keyguard_selector_view;
-    private static final int SECURITY_PATTERN_ID = R.id.keyguard_pattern_view;
-    private static final int SECURITY_PASSWORD_ID = R.id.keyguard_password_view;
-    private static final int SECURITY_BIOMETRIC_ID = R.id.keyguard_face_unlock_view;
-    private static final int SECURITY_SIM_PIN_ID = R.id.keyguard_sim_pin_view;
-    private static final int SECURITY_SIM_PUK_ID = R.id.keyguard_sim_puk_view;
-    private static final int SECURITY_ACCOUNT_ID = R.id.keyguard_account_view;
-
     private AppWidgetHost mAppWidgetHost;
     private KeyguardWidgetPager mAppWidgetContainer;
-    private ViewFlipper mViewFlipper;
+    private ViewFlipper mSecurityViewContainer;
     private boolean mEnableMenuKey;
     private boolean mIsVerifyUnlockOnly;
     private boolean mEnableFallback; // TODO: This should get the value from KeyguardPatternView
-    private int mCurrentSecurityId = SECURITY_SELECTOR_ID;
-
-    // KeyguardSecurityViews
-    final private int [] mViewIds = {
-        SECURITY_SELECTOR_ID,
-        SECURITY_PATTERN_ID,
-        SECURITY_PASSWORD_ID,
-        SECURITY_BIOMETRIC_ID,
-        SECURITY_SIM_PIN_ID,
-        SECURITY_SIM_PUK_ID,
-        SECURITY_ACCOUNT_ID,
-    };
-
-    private ArrayList<View> mViews = new ArrayList<View>(mViewIds.length);
+    private SecurityMode mCurrentSecuritySelection = SecurityMode.None;
 
     protected Runnable mLaunchRunnable;
 
@@ -125,33 +103,31 @@
     protected void onFinishInflate() {
         mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container);
         mAppWidgetContainer.setVisibility(VISIBLE);
+        mSecurityViewContainer = (ViewFlipper) findViewById(R.id.view_flipper);
+        updateSecurityViews();
+    }
 
-        // View Flipper
-        mViewFlipper = (ViewFlipper) findViewById(R.id.view_flipper);
+    private void updateSecurityViews() {
+        int children = mSecurityViewContainer.getChildCount();
+        for (int i = 0; i < children; i++) {
+            updateSecurityView(mSecurityViewContainer.getChildAt(i));
+        }
+    }
 
-        // Initialize all security views
-        for (int i = 0; i < mViewIds.length; i++) {
-            View view = findViewById(mViewIds[i]);
-            mViews.add(view);
-            if (view != null) {
-                ((KeyguardSecurityView) view).setKeyguardCallback(mCallback);
-            } else {
-                Log.v("*********", "Can't find view id " + mViewIds[i]);
-            }
+    private void updateSecurityView(View view) {
+        if (view instanceof KeyguardSecurityView) {
+            KeyguardSecurityView ksv = (KeyguardSecurityView) view;
+            ksv.setKeyguardCallback(mCallback);
+            ksv.setLockPatternUtils(mLockPatternUtils);
+        } else {
+            Log.w(TAG, "View " + view + " is not a KeyguardSecurityView");
         }
     }
 
     void setLockPatternUtils(LockPatternUtils utils) {
         mSecurityModel.setLockPatternUtils(utils);
         mLockPatternUtils = utils;
-        for (int i = 0; i < mViews.size(); i++) {
-            KeyguardSecurityView ksv = (KeyguardSecurityView) mViews.get(i);
-            if (ksv != null) {
-                ksv.setLockPatternUtils(utils);
-            } else {
-                Log.w(TAG, "**** ksv was null at " + i);
-            }
-        }
+        updateSecurityViews();
     }
 
     @Override
@@ -313,7 +289,7 @@
                     showTimeout = false; // don't show both dialogs
                 } else if (failedAttempts >= LockPatternUtils.FAILED_ATTEMPTS_BEFORE_RESET) {
                     mLockPatternUtils.setPermanentlyLocked(true);
-                    showSecurityScreen(SECURITY_ACCOUNT_ID);
+                    showSecurityScreen(SecurityMode.Account);
                     // don't show timeout dialog because we show account unlock screen next
                     showTimeout = false;
                 }
@@ -333,56 +309,53 @@
      */
     private void showBackupSecurity() {
         SecurityMode currentMode = mSecurityModel.getAlternateFor(mSecurityModel.getSecurityMode());
-        SecurityMode backup = mSecurityModel.getBackupFor(currentMode);
-        showSecurityScreen(getSecurityViewIdForMode(backup));
+        showSecurityScreen(mSecurityModel.getBackupFor(currentMode));
     }
 
     private void showNextSecurityScreenOrFinish(boolean authenticated) {
         boolean finish = false;
-        if (SECURITY_SELECTOR_ID == mCurrentSecurityId) {
+        if (SecurityMode.None == mCurrentSecuritySelection) {
             SecurityMode securityMode = mSecurityModel.getSecurityMode();
             // Allow an alternate, such as biometric unlock
             securityMode = mSecurityModel.getAlternateFor(securityMode);
-            int realSecurityId = getSecurityViewIdForMode(securityMode);
-            if (SECURITY_SELECTOR_ID == realSecurityId) {
+            if (SecurityMode.None == securityMode) {
                 finish = true; // no security required
             } else {
-                showSecurityScreen(realSecurityId); // switch to the "real" security view
+                showSecurityScreen(securityMode); // switch to the alternate security view
             }
         } else if (authenticated) {
-            switch (mCurrentSecurityId) {
-                case SECURITY_PATTERN_ID:
-                case SECURITY_PASSWORD_ID:
-                case SECURITY_ACCOUNT_ID:
-                case SECURITY_BIOMETRIC_ID:
+            switch (mCurrentSecuritySelection) {
+                case Pattern:
+                case Password:
+                case Account:
+                case Biometric:
                     finish = true;
                     break;
 
-                case SECURITY_SIM_PIN_ID:
-                case SECURITY_SIM_PUK_ID:
+                case SimPin:
+                case SimPuk:
                     // Shortcut for SIM PIN/PUK to go to directly to user's security screen or home
                     SecurityMode securityMode = mSecurityModel.getSecurityMode();
                     if (securityMode != SecurityMode.None) {
-                        showSecurityScreen(getSecurityViewIdForMode(securityMode));
+                        showSecurityScreen(securityMode);
                     } else {
                         finish = true;
                     }
                     break;
 
                 default:
-                    showSecurityScreen(SECURITY_SELECTOR_ID);
+                    showSecurityScreen(SecurityMode.None);
                     break;
             }
         } else {
             // Not authenticated but we were asked to dismiss so go back to selector screen.
-            showSecurityScreen(SECURITY_SELECTOR_ID);
+            showSecurityScreen(SecurityMode.None);
         }
         if (finish) {
             // If there's a pending runnable because the user interacted with a widget
             // and we're leaving keyguard, then run it.
             if (mLaunchRunnable != null) {
                 mLaunchRunnable.run();
-                mViewFlipper.setDisplayedChild(0);
                 mLaunchRunnable = null;
             }
             mViewMediatorCallback.keyguardDone(true);
@@ -438,28 +411,38 @@
         mLaunchRunnable = runnable;
     }
 
-    private KeyguardSecurityView getSecurityView(int securitySelectorId) {
-        final int children = mViewFlipper.getChildCount();
+    private KeyguardSecurityView getSecurityView(SecurityMode securityMode) {
+        final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
+        KeyguardSecurityView view = null;
+        final int children = mSecurityViewContainer.getChildCount();
         for (int child = 0; child < children; child++) {
-            if (mViewFlipper.getChildAt(child).getId() == securitySelectorId) {
-                return ((KeyguardSecurityView)mViewFlipper.getChildAt(child));
+            if (mSecurityViewContainer.getChildAt(child).getId() == securityViewIdForMode) {
+                view = ((KeyguardSecurityView)mSecurityViewContainer.getChildAt(child));
+                break;
             }
         }
-        return null;
+        if (view == null) {
+            final LayoutInflater inflater = LayoutInflater.from(mContext);
+            View v = inflater.inflate(getLayoutIdFor(securityMode), this, false);
+            mSecurityViewContainer.addView(v);
+            updateSecurityView(v);
+            view = (KeyguardSecurityView) v;
+        }
+        return view;
     }
 
     /**
      * Switches to the given security view unless it's already being shown, in which case
      * this is a no-op.
      *
-     * @param securityViewId
+     * @param securityMode
      */
-    private void showSecurityScreen(int securityViewId) {
+    private void showSecurityScreen(SecurityMode securityMode) {
 
-        if (securityViewId == mCurrentSecurityId) return;
+        if (securityMode == mCurrentSecuritySelection) return;
 
-        KeyguardSecurityView oldView = getSecurityView(mCurrentSecurityId);
-        KeyguardSecurityView newView = getSecurityView(securityViewId);
+        KeyguardSecurityView oldView = getSecurityView(mCurrentSecuritySelection);
+        KeyguardSecurityView newView = getSecurityView(securityMode);
 
         // Emulate Activity life cycle
         oldView.onPause();
@@ -468,45 +451,46 @@
         mViewMediatorCallback.setNeedsInput(newView.needsInput());
 
         // Find and show this child.
-        final int childCount = mViewFlipper.getChildCount();
+        final int childCount = mSecurityViewContainer.getChildCount();
 
         // If we're go to/from the selector view, do flip animation, otherwise use fade animation.
-        final boolean doFlip = mCurrentSecurityId == SECURITY_SELECTOR_ID
-                || securityViewId == SECURITY_SELECTOR_ID;
+        final boolean doFlip = mCurrentSecuritySelection == SecurityMode.None
+                || securityMode == SecurityMode.None;
         final int inAnimation = doFlip ? R.anim.keyguard_security_animate_in
                 : R.anim.keyguard_security_fade_in;
         final int outAnimation = doFlip ? R.anim.keyguard_security_animate_out
                 : R.anim.keyguard_security_fade_out;
 
-        mViewFlipper.setInAnimation(AnimationUtils.loadAnimation(mContext, inAnimation));
-        mViewFlipper.setOutAnimation(AnimationUtils.loadAnimation(mContext, outAnimation));
+        mSecurityViewContainer.setInAnimation(AnimationUtils.loadAnimation(mContext, inAnimation));
+        mSecurityViewContainer.setOutAnimation(AnimationUtils.loadAnimation(mContext, outAnimation));
+        final int securityViewIdForMode = getSecurityViewIdForMode(securityMode);
         for (int i = 0; i < childCount; i++) {
-            if (securityViewId == mViewFlipper.getChildAt(i).getId()) {
-                mViewFlipper.setDisplayedChild(i);
+            if (mSecurityViewContainer.getChildAt(i).getId() == securityViewIdForMode) {
+                mSecurityViewContainer.setDisplayedChild(i);
                 break;
             }
         }
 
         // Discard current runnable if we're switching back to the selector view
-        if (securityViewId == SECURITY_SELECTOR_ID) {
+        if (securityMode == SecurityMode.None) {
             setOnDismissRunnable(null);
         }
 
-        mCurrentSecurityId = securityViewId;
+        mCurrentSecuritySelection = securityMode;
     }
 
     @Override
     public void onScreenTurnedOn() {
         if (DEBUG) Log.d(TAG, "screen on");
-        showSecurityScreen(mCurrentSecurityId);
-        getSecurityView(mCurrentSecurityId).onResume();
+        showSecurityScreen(mCurrentSecuritySelection);
+        getSecurityView(mCurrentSecuritySelection).onResume();
     }
 
     @Override
     public void onScreenTurnedOff() {
         if (DEBUG) Log.d(TAG, "screen off");
-        showSecurityScreen(SECURITY_SELECTOR_ID);
-        getSecurityView(mCurrentSecurityId).onPause();
+        showSecurityScreen(SecurityMode.None);
+        getSecurityView(mCurrentSecuritySelection).onPause();
     }
 
     @Override
@@ -537,7 +521,7 @@
         if (DEBUG) Log.d(TAG, "onWakeKey");
         if (keyCode == KeyEvent.KEYCODE_MENU && isSecure()) {
             if (DEBUG) Log.d(TAG, "switching screens to unlock screen because wake key was MENU");
-            showSecurityScreen(SECURITY_SELECTOR_ID);
+            showSecurityScreen(SecurityMode.None);
             mViewMediatorCallback.pokeWakelock();
         } else {
             if (DEBUG) Log.d(TAG, "poking wake lock immediately");
@@ -557,23 +541,37 @@
         } else {
             // otherwise, go to the unlock screen, see if they can verify it
             mIsVerifyUnlockOnly = true;
-            showSecurityScreen(getSecurityViewIdForMode(securityMode));
+            showSecurityScreen(securityMode);
         }
     }
 
     private int getSecurityViewIdForMode(SecurityMode securityMode) {
         switch (securityMode) {
-            case None: return SECURITY_SELECTOR_ID;
-            case Pattern: return SECURITY_PATTERN_ID;
-            case Password: return SECURITY_PASSWORD_ID;
-            case Biometric: return SECURITY_BIOMETRIC_ID;
-            case Account: return SECURITY_ACCOUNT_ID;
-            case SimPin: return SECURITY_SIM_PIN_ID;
-            case SimPuk: return SECURITY_SIM_PUK_ID;
+            case None: return R.id.keyguard_selector_view;
+            case Pattern: return R.id.keyguard_pattern_view;
+            case Password: return R.id.keyguard_password_view;
+            case Biometric: return R.id.keyguard_face_unlock_view;
+            case Account: return R.id.keyguard_account_view;
+            case SimPin: return R.id.keyguard_sim_pin_view;
+            case SimPuk: return R.id.keyguard_sim_puk_view;
         }
         return 0;
     }
 
+    private int getLayoutIdFor(SecurityMode securityMode) {
+        switch (securityMode) {
+            case None: return R.layout.keyguard_selector_view;
+            case Pattern: return R.layout.keyguard_pattern_view;
+            case Password: return R.layout.keyguard_password_view;
+            case Biometric: return R.layout.keyguard_face_unlock_view;
+            case Account: return R.layout.keyguard_account_view;
+            case SimPin: return R.layout.keyguard_sim_pin_view;
+            case SimPuk: return R.layout.keyguard_sim_puk_view;
+            default:
+                throw new RuntimeException("No layout for securityMode " + securityMode);
+        }
+    }
+
     private void addWidget(int appId) {
         AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(mContext);
         AppWidgetProviderInfo appWidgetInfo = appWidgetManager.getAppWidgetInfo(appId);
diff --git a/services/java/com/android/server/BluetoothManagerService.java b/services/java/com/android/server/BluetoothManagerService.java
index 4b43070..9404dce 100755
--- a/services/java/com/android/server/BluetoothManagerService.java
+++ b/services/java/com/android/server/BluetoothManagerService.java
@@ -40,6 +40,8 @@
     private static final String SECURE_SETTINGS_BLUETOOTH_NAME="bluetooth_name";
     private static final int TIMEOUT_BIND_MS = 3000; //Maximum msec to wait for a bind
     private static final int TIMEOUT_SAVE_MS = 500; //Maximum msec to wait for a save
+    //Maximum msec to wait for service restart
+    private static final int SERVICE_RESTART_TIME_MS = 200;
 
     private static final int MESSAGE_ENABLE = 1;
     private static final int MESSAGE_DISABLE = 2;
@@ -49,6 +51,7 @@
     private static final int MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK = 31;
     private static final int MESSAGE_BLUETOOTH_SERVICE_CONNECTED = 40;
     private static final int MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED = 41;
+    private static final int MESSAGE_RESTART_BLUETOOTH_SERVICE = 42;
     private static final int MESSAGE_BLUETOOTH_STATE_CHANGE=60;
     private static final int MESSAGE_TIMEOUT_BIND =100;
     private static final int MESSAGE_TIMEOUT_UNBIND =101;
@@ -660,8 +663,36 @@
                 {
                     if (DBG) Log.d(TAG, "MESSAGE_BLUETOOTH_SERVICE_DISCONNECTED");
                     sendBluetoothServiceDownCallback();
+
+                    // Send BT state broadcast to update
+                    // the BT icon correctly
+                    Message stateChangeMsg = mHandler.obtainMessage(
+                        MESSAGE_BLUETOOTH_STATE_CHANGE);
+                    stateChangeMsg.arg1 = BluetoothAdapter.STATE_ON;
+                    stateChangeMsg.arg2 =
+                        BluetoothAdapter.STATE_TURNING_OFF;
+                    mHandler.sendMessage(stateChangeMsg);
+                    synchronized(mConnection) {
+                        mBluetooth = null;
+                    }
+                    // Send a Bluetooth Restart message
+                    Message restartMsg = mHandler.obtainMessage(
+                        MESSAGE_RESTART_BLUETOOTH_SERVICE);
+                    mHandler.sendMessageDelayed(restartMsg,
+                        SERVICE_RESTART_TIME_MS);
                     break;
                 }
+                case MESSAGE_RESTART_BLUETOOTH_SERVICE:
+                {
+                    Log.d(TAG, "MESSAGE_RESTART_BLUETOOTH_SERVICE:"
+                        +" Restart IBluetooth service");
+                    /* Enable without persisting the setting as
+                     it doesnt change when IBluetooth
+                     service restarts */
+                    handleEnable(false, mQuietEnable);
+                    break;
+                }
+
                 case MESSAGE_TIMEOUT_UNBIND:
                 {
                     Log.e(TAG, "MESSAGE_TIMEOUT_UNBIND");
diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java
index 61517b1..fd6060a 100644
--- a/services/java/com/android/server/DevicePolicyManagerService.java
+++ b/services/java/com/android/server/DevicePolicyManagerService.java
@@ -1628,8 +1628,8 @@
             } else {
                 // Make sure KEEP_SCREEN_ON is disabled, since that
                 // would allow bypassing of the maximum time to lock.
-                Settings.System.putInt(mContext.getContentResolver(),
-                        Settings.System.STAY_ON_WHILE_PLUGGED_IN, 0);
+                Settings.Global.putInt(mContext.getContentResolver(),
+                        Settings.Global.STAY_ON_WHILE_PLUGGED_IN, 0);
             }
 
             mLastMaximumTimeToLock = timeMs;
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index 1eccbbe..f40333d 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -1114,9 +1114,8 @@
                         Slog.e(TAG, "path or description is null in readStorageList");
                     } else {
                         String pathString = path.toString();
-                        StorageVolume volume = new StorageVolume(pathString,
-                                descriptionId, removable, emulated,
-                                mtpReserve, allowMassStorage, maxFileSize);
+                        StorageVolume volume = new StorageVolume(pathString, descriptionId, primary,
+                                removable, emulated, mtpReserve, allowMassStorage, maxFileSize);
                         if (primary) {
                             if (mPrimaryVolume == null) {
                                 mPrimaryVolume = volume;
diff --git a/services/java/com/android/server/UiModeManagerService.java b/services/java/com/android/server/UiModeManagerService.java
index f618263..3e83baa 100644
--- a/services/java/com/android/server/UiModeManagerService.java
+++ b/services/java/com/android/server/UiModeManagerService.java
@@ -463,7 +463,7 @@
 
                 if (homeIntent != null) {
                     try {
-                        mContext.startActivity(homeIntent);
+                        mContext.startActivityAsUser(homeIntent, UserHandle.CURRENT);
                     } catch (ActivityNotFoundException e) {
                     }
                 }
diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java
index bd7f276..78092dd 100644
--- a/services/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -286,7 +286,11 @@
     private PointerCoords[] getTempPointerCoordsWithMinSize(int size) {
         final int oldSize = (mTempPointerCoords != null) ? mTempPointerCoords.length : 0;
         if (oldSize < size) {
+            PointerCoords[] oldTempPointerCoords = mTempPointerCoords;
             mTempPointerCoords = new PointerCoords[size];
+            if (oldTempPointerCoords != null) {
+                System.arraycopy(oldTempPointerCoords, 0, mTempPointerCoords, 0, oldSize);
+            }
         }
         for (int i = oldSize; i < size; i++) {
             mTempPointerCoords[i] = new PointerCoords();
@@ -297,7 +301,11 @@
     private PointerProperties[] getTempPointerPropertiesWithMinSize(int size) {
         final int oldSize = (mTempPointerProperties != null) ? mTempPointerProperties.length : 0;
         if (oldSize < size) {
+            PointerProperties[] oldTempPointerProperties = mTempPointerProperties;
             mTempPointerProperties = new PointerProperties[size];
+            if (oldTempPointerProperties != null) {
+                System.arraycopy(oldTempPointerProperties, 0, mTempPointerProperties, 0, oldSize);
+            }
         }
         for (int i = oldSize; i < size; i++) {
             mTempPointerProperties[i] = new PointerProperties();
@@ -324,7 +332,7 @@
                     Slog.i(LOG_TAG, "mCurrentState: STATE_PANNING");
                 } break;
                 case STATE_DECIDE_PAN_OR_SCALE: {
-                    Slog.i(LOG_TAG, "mCurrentState: STATE_DETECTING_PAN_OR_SCALE");
+                    Slog.i(LOG_TAG, "mCurrentState: STATE_DECIDE_PAN_OR_SCALE");
                 } break;
                 default: {
                     throw new IllegalArgumentException("Unknown state: " + state);
@@ -397,6 +405,7 @@
                     }
                     if (scaleDelta > DETECT_SCALING_THRESHOLD) {
                         performScale(detector, true);
+                        clear();
                         transitionToState(STATE_SCALING);
                         return false;
                     }
@@ -409,8 +418,9 @@
                         Slog.i(LOG_TAG, "panDelta: " + panDelta);
                     }
                     if (panDelta > mScaledDetectPanningThreshold) {
-                        transitionToState(STATE_PANNING);
                         performPan(detector, true);
+                        clear();
+                        transitionToState(STATE_PANNING);
                         return false;
                     }
                 } break;
diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java
index 6a3010b..7144808 100644
--- a/services/java/com/android/server/am/ActiveServices.java
+++ b/services/java/com/android/server/am/ActiveServices.java
@@ -185,7 +185,7 @@
         }
 
         private HashMap<ComponentName, ServiceRecord> getServices(int callingUser) {
-            HashMap map = mServicesByNamePerUser.get(callingUser);
+            HashMap<ComponentName, ServiceRecord> map = mServicesByNamePerUser.get(callingUser);
             if (map == null) {
                 map = new HashMap<ComponentName, ServiceRecord>();
                 mServicesByNamePerUser.put(callingUser, map);
@@ -195,7 +195,8 @@
 
         private HashMap<Intent.FilterComparison, ServiceRecord> getServicesByIntent(
                 int callingUser) {
-            HashMap map = mServicesByIntentPerUser.get(callingUser);
+            HashMap<Intent.FilterComparison, ServiceRecord> map
+                    = mServicesByIntentPerUser.get(callingUser);
             if (map == null) {
                 map = new HashMap<Intent.FilterComparison, ServiceRecord>();
                 mServicesByIntentPerUser.put(callingUser, map);
@@ -1514,10 +1515,12 @@
         }
     }
 
-    boolean forceStopLocked(String name, int userId, boolean evenPersistent, boolean doit) {
+    private boolean collectForceStopServicesLocked(String name, int userId,
+            boolean evenPersistent, boolean doit,
+            HashMap<ComponentName, ServiceRecord> services,
+            ArrayList<ServiceRecord> result) {
         boolean didSomething = false;
-        ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
-        for (ServiceRecord service : mServiceMap.getAllServices(userId)) {
+        for (ServiceRecord service : services.values()) {
             if ((name == null || service.packageName.equals(name))
                     && (service.app == null || evenPersistent || !service.app.persistent)) {
                 if (!doit) {
@@ -1530,9 +1533,27 @@
                 }
                 service.app = null;
                 service.isolatedProc = null;
-                services.add(service);
+                result.add(service);
             }
         }
+        return didSomething;
+    }
+
+    boolean forceStopLocked(String name, int userId, boolean evenPersistent, boolean doit) {
+        boolean didSomething = false;
+        ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>();
+        if (userId == UserHandle.USER_ALL) {
+            for (int i=0; i<mServiceMap.mServicesByNamePerUser.size(); i++) {
+                didSomething |= collectForceStopServicesLocked(name, userId, evenPersistent,
+                        doit, mServiceMap.mServicesByNamePerUser.valueAt(i), services);
+                if (!doit && didSomething) {
+                    return true;
+                }
+            }
+        } else {
+            didSomething = collectForceStopServicesLocked(name, userId, evenPersistent,
+                    doit, mServiceMap.mServicesByNamePerUser.get(userId), services);
+        }
 
         int N = services.size();
         for (int i=0; i<N; i++) {
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java
index 614b93a..ca45946 100644
--- a/services/java/com/android/server/am/ActivityManagerService.java
+++ b/services/java/com/android/server/am/ActivityManagerService.java
@@ -116,13 +116,11 @@
 import android.provider.Settings;
 import android.text.format.Time;
 import android.util.EventLog;
-import android.util.LocaleUtil;
 import android.util.Log;
 import android.util.Pair;
 import android.util.PrintWriterPrinter;
 import android.util.Slog;
 import android.util.SparseArray;
-import android.util.SparseIntArray;
 import android.util.TimeUtils;
 import android.view.Gravity;
 import android.view.LayoutInflater;
@@ -434,6 +432,11 @@
     final SparseArray<UserStartedState> mStartedUsers = new SparseArray<UserStartedState>();
 
     /**
+     * LRU list of history of current users.  Most recently current is at the end.
+     */
+    final ArrayList<Integer> mUserLru = new ArrayList<Integer>();
+
+    /**
      * Packages that the user has asked to have run in screen size
      * compatibility mode instead of filling the screen.
      */
@@ -1094,11 +1097,11 @@
             } break;
             case KILL_APPLICATION_MSG: {
                 synchronized (ActivityManagerService.this) {
-                    int uid = msg.arg1;
+                    int appid = msg.arg1;
                     boolean restart = (msg.arg2 == 1);
                     String pkg = (String) msg.obj;
-                    forceStopPackageLocked(pkg, uid, restart, false, true, false,
-                            UserHandle.getUserId(uid));
+                    forceStopPackageLocked(pkg, appid, restart, false, true, false,
+                            UserHandle.USER_ALL);
                 }
             } break;
             case FINALIZE_PENDING_INTENT_MSG: {
@@ -1526,6 +1529,7 @@
 
         // User 0 is the first and only user that runs at boot.
         mStartedUsers.put(0, new UserStartedState(new UserHandle(0), true));
+        mUserLru.add(Integer.valueOf(0));
 
         GL_ES_VERSION = SystemProperties.getInt("ro.opengles.version",
             ConfigurationInfo.GL_ES_VERSION_UNDEFINED);
@@ -2146,8 +2150,7 @@
             intent.addCategory(Intent.CATEGORY_HOME);
         }
         ActivityInfo aInfo =
-            intent.resolveActivityInfo(mContext.getPackageManager(),
-                    STOCK_PM_FLAGS);
+            resolveActivityInfo(intent, STOCK_PM_FLAGS, userId);
         if (aInfo != null) {
             intent.setComponent(new ComponentName(
                     aInfo.applicationInfo.packageName, aInfo.name));
@@ -2170,6 +2173,29 @@
         return true;
     }
 
+    private ActivityInfo resolveActivityInfo(Intent intent, int flags, int userId) {
+        ActivityInfo ai = null;
+        ComponentName comp = intent.getComponent();
+        try {
+            if (comp != null) {
+                ai = AppGlobals.getPackageManager().getActivityInfo(comp, flags, userId);
+            } else {
+                ResolveInfo info = AppGlobals.getPackageManager().resolveIntent(
+                        intent,
+                        intent.resolveTypeIfNeeded(mContext.getContentResolver()),
+                            flags, userId);
+    
+                if (info != null) {
+                    ai = info.activityInfo;
+                }
+            }
+        } catch (RemoteException e) {
+            // ignore
+        }
+
+        return ai;
+    }
+
     /**
      * Starts the "new version setup screen" if appropriate.
      */
@@ -2366,17 +2392,8 @@
             String resultWho, int requestCode, int startFlags,
             String profileFile, ParcelFileDescriptor profileFd, Bundle options, int userId) {
         enforceNotIsolatedCaller("startActivity");
-        if (userId != UserHandle.getCallingUserId()) {
-            userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), userId,
-                    false, true, "startActivity", null);
-        } else {
-            if (intent.getCategories() != null
-                    && intent.getCategories().contains(Intent.CATEGORY_HOME)) {
-                // Requesting home, set the identity to the current user
-                // HACK!
-                userId = mCurrentUserId;
-            }
-        }
+        userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "startActivity", null);
         return mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
                 resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
                 null, null, options, userId);
@@ -2385,8 +2402,10 @@
     public final WaitResult startActivityAndWait(IApplicationThread caller,
             Intent intent, String resolvedType, IBinder resultTo,
             String resultWho, int requestCode, int startFlags, String profileFile,
-            ParcelFileDescriptor profileFd, Bundle options) {
+            ParcelFileDescriptor profileFd, Bundle options, int userId) {
         enforceNotIsolatedCaller("startActivityAndWait");
+        userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(), userId,
+                false, true, "startActivityAndWait", null);
         WaitResult res = new WaitResult();
         mMainStack.startActivityMayWait(caller, -1, intent, resolvedType,
                 resultTo, resultWho, requestCode, startFlags, profileFile, profileFd,
@@ -3542,16 +3561,15 @@
     }
 
     /*
-     * The pkg name and uid have to be specified.
-     * @see android.app.IActivityManager#killApplicationWithUid(java.lang.String, int)
+     * The pkg name and app id have to be specified.
      */
-    public void killApplicationWithUid(String pkg, int uid) {
+    public void killApplicationWithAppId(String pkg, int appid) {
         if (pkg == null) {
             return;
         }
         // Make sure the uid is valid.
-        if (uid < 0) {
-            Slog.w(TAG, "Invalid uid specified for pkg : " + pkg);
+        if (appid < 0) {
+            Slog.w(TAG, "Invalid appid specified for pkg : " + pkg);
             return;
         }
         int callerUid = Binder.getCallingUid();
@@ -3559,7 +3577,7 @@
         if (callerUid == Process.SYSTEM_UID) {
             // Post an aysnc message to kill the application
             Message msg = mHandler.obtainMessage(KILL_APPLICATION_MSG);
-            msg.arg1 = uid;
+            msg.arg1 = appid;
             msg.arg2 = 0;
             msg.obj = pkg;
             mHandler.sendMessage(msg);
@@ -3691,7 +3709,7 @@
                 MY_PID, Process.SYSTEM_UID, userId);
     }
 
-    private final boolean killPackageProcessesLocked(String packageName, int uid,
+    private final boolean killPackageProcessesLocked(String packageName, int appId,
             int userId, int minOomAdj, boolean callerWillRestart, boolean allowRestart,
             boolean doit, boolean evenPersistent, String reason) {
         ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>();
@@ -3712,32 +3730,41 @@
                     if (doit) {
                         procs.add(app);
                     }
+                    continue;
+                }
+
+                // Skip process if it doesn't meet our oom adj requirement.
+                if (app.setAdj < minOomAdj) {
+                    continue;
+                }
+
                 // If no package is specified, we call all processes under the
                 // give user id.
-                } else if (packageName == null) {
-                    if (app.userId == userId) {
-                        if (app.setAdj >= minOomAdj) {
-                            if (!doit) {
-                                return true;
-                            }
-                            app.removed = true;
-                            procs.add(app);
-                        }
+                if (packageName == null) {
+                    if (app.userId != userId) {
+                        continue;
                     }
-                // If uid is specified and the uid and process name match
-                // Or, the uid is not specified and the process name matches
-                } else if (((uid > 0 && uid != Process.SYSTEM_UID && app.info.uid == uid)
-                            || ((app.processName.equals(packageName)
-                                 || app.processName.startsWith(procNamePrefix))
-                                && uid < 0))) {
-                    if (app.setAdj >= minOomAdj) {
-                        if (!doit) {
-                            return true;
-                        }
-                        app.removed = true;
-                        procs.add(app);
+                // Package has been specified, we want to hit all processes
+                // that match it.  We need to qualify this by the processes
+                // that are running under the specified app and user ID.
+                } else {
+                    if (UserHandle.getAppId(app.uid) != appId) {
+                        continue;
+                    }
+                    if (userId != UserHandle.USER_ALL && app.userId != userId) {
+                        continue;
+                    }
+                    if (!app.pkgList.contains(packageName)) {
+                        continue;
                     }
                 }
+
+                // Process has passed all conditions, kill it!
+                if (!doit) {
+                    return true;
+                }
+                app.removed = true;
+                procs.add(app);
             }
         }
         
@@ -3748,22 +3775,28 @@
         return N > 0;
     }
 
-    private final boolean forceStopPackageLocked(String name, int uid,
+    private final boolean forceStopPackageLocked(String name, int appId,
             boolean callerWillRestart, boolean purgeCache, boolean doit,
             boolean evenPersistent, int userId) {
         int i;
         int N;
 
-        if (uid < 0 && name != null) {
+        if (userId == UserHandle.USER_ALL && name == null) {
+            Slog.w(TAG, "Can't force stop all processes of all users, that is insane!");
+        }
+
+        if (appId < 0 && name != null) {
             try {
-                uid = AppGlobals.getPackageManager().getPackageUid(name, userId);
+                appId = UserHandle.getAppId(
+                        AppGlobals.getPackageManager().getPackageUid(name, 0));
             } catch (RemoteException e) {
             }
         }
 
         if (doit) {
             if (name != null) {
-                Slog.i(TAG, "Force stopping package " + name + " uid=" + uid);
+                Slog.i(TAG, "Force stopping package " + name + " appid=" + appId
+                        + " user=" + userId);
             } else {
                 Slog.i(TAG, "Force stopping user " + userId);
             }
@@ -3775,8 +3808,14 @@
                     boolean remove = false;
                     final int entUid = ba.keyAt(i);
                     if (name != null) {
-                        if (entUid == uid) {
-                            remove = true;
+                        if (userId == UserHandle.USER_ALL) {
+                            if (UserHandle.getAppId(entUid) == appId) {
+                                remove = true;
+                            }
+                        } else {
+                            if (entUid == UserHandle.getUid(userId, appId)) {
+                                remove = true;
+                            }
                         }
                     } else if (UserHandle.getUserId(entUid) == userId) {
                         remove = true;
@@ -3791,9 +3830,8 @@
             }
         }
 
-        boolean didSomething = killPackageProcessesLocked(name, uid,
-                name == null ? userId : -1 , -100, callerWillRestart, false,
-                doit, evenPersistent,
+        boolean didSomething = killPackageProcessesLocked(name, appId, userId,
+                -100, callerWillRestart, false, doit, evenPersistent,
                 name == null ? ("force stop user " + userId) : ("force stop " + name));
         
         TaskRecord lastTask = null;
@@ -3801,7 +3839,7 @@
             ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i);
             final boolean samePackage = r.packageName.equals(name)
                     || (name == null && r.userId == userId);
-            if (r.userId == userId
+            if ((userId == UserHandle.USER_ALL || r.userId == userId)
                     && (samePackage || r.task == lastTask)
                     && (r.app == null || evenPersistent || !r.app.persistent)) {
                 if (!doit) {
@@ -3841,22 +3879,64 @@
         }
 
         ArrayList<ContentProviderRecord> providers = new ArrayList<ContentProviderRecord>();
-        for (ContentProviderRecord provider : mProviderMap.getProvidersByClass(userId).values()) {
-            if ((name == null || provider.info.packageName.equals(name))
-                    && (provider.proc == null || evenPersistent || !provider.proc.persistent)) {
+        if (mProviderMap.collectForceStopProviders(name, appId, doit, evenPersistent,
+                userId, providers)) {
+            if (!doit) {
+                return true;
+            }
+            didSomething = true;
+        }
+        N = providers.size();
+        for (i=0; i<N; i++) {
+            removeDyingProviderLocked(null, providers.get(i), true);
+        }
+
+        if (mIntentSenderRecords.size() > 0) {
+            Iterator<WeakReference<PendingIntentRecord>> it
+                    = mIntentSenderRecords.values().iterator();
+            while (it.hasNext()) {
+                WeakReference<PendingIntentRecord> wpir = it.next();
+                if (wpir == null) {
+                    it.remove();
+                    continue;
+                }
+                PendingIntentRecord pir = wpir.get();
+                if (pir == null) {
+                    it.remove();
+                    continue;
+                }
+                if (name == null) {
+                    // Stopping user, remove all objects for the user.
+                    if (pir.key.userId != userId) {
+                        // Not the same user, skip it.
+                        continue;
+                    }
+                } else {
+                    if (UserHandle.getAppId(pir.uid) != appId) {
+                        // Different app id, skip it.
+                        continue;
+                    }
+                    if (userId != UserHandle.USER_ALL && pir.key.userId != userId) {
+                        // Different user, skip it.
+                        continue;
+                    }
+                    if (!pir.key.packageName.equals(name)) {
+                        // Different package, skip it.
+                        continue;
+                    }
+                }
                 if (!doit) {
                     return true;
                 }
                 didSomething = true;
-                providers.add(provider);
+                it.remove();
+                pir.canceled = true;
+                if (pir.key.activity != null) {
+                    pir.key.activity.pendingResults.remove(pir.ref);
+                }
             }
         }
 
-        N = providers.size();
-        for (i=0; i<N; i++) {
-            removeDyingProviderLocked(null, providers.get(i), true);
-        }
-
         if (doit) {
             if (purgeCache && name != null) {
                 AttributeCache ac = AttributeCache.instance();
@@ -6778,10 +6858,11 @@
      * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/
      *     src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java
      */
-    public String getProviderMimeType(Uri uri) {
+    public String getProviderMimeType(Uri uri, int userId) {
         enforceNotIsolatedCaller("getProviderMimeType");
+        userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, true, "getProviderMimeType", null);
         final String name = uri.getAuthority();
-        final int userId = UserHandle.getCallingUserId();
         final long ident = Binder.clearCallingIdentity();
         ContentProviderHolder holder = null;
 
@@ -7134,7 +7215,8 @@
             mDebugTransient = !persistent;
             if (packageName != null) {
                 final long origId = Binder.clearCallingIdentity();
-                forceStopPackageLocked(packageName, -1, false, false, true, true, 0);
+                forceStopPackageLocked(packageName, -1, false, false, true, true,
+                        UserHandle.USER_ALL);
                 Binder.restoreCallingIdentity(origId);
             }
         }
@@ -9129,9 +9211,14 @@
         for (int i=0; i<mStartedUsers.size(); i++) {
             UserStartedState uss = mStartedUsers.valueAt(i);
             pw.print("    User #"); pw.print(uss.mHandle.getIdentifier());
-                    pw.println(":");
-            uss.dump("      ", pw);
+                    pw.print(": "); uss.dump("", pw);
         }
+        pw.print("  mUserLru: [");
+        for (int i=0; i<mUserLru.size(); i++) {
+            if (i > 0) pw.print(", ");
+            pw.print(mUserLru.get(i));
+        }
+        pw.println("]");
         pw.println("  mHomeProcess: " + mHomeProcess);
         pw.println("  mPreviousProcess: " + mPreviousProcess);
         if (dumpAll) {
@@ -10764,7 +10851,7 @@
                         builder.append("; this requires ");
                         builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL);
                         if (!requireFull) {
-                            builder.append("or");
+                            builder.append(" or ");
                             builder.append(android.Manifest.permission.INTERACT_ACROSS_USERS);
                         }
                         String msg = builder.toString();
@@ -11815,8 +11902,10 @@
 
     public boolean startInstrumentation(ComponentName className,
             String profileFile, int flags, Bundle arguments,
-            IInstrumentationWatcher watcher) {
+            IInstrumentationWatcher watcher, int userId) {
         enforceNotIsolatedCaller("startInstrumentation");
+        userId = handleIncomingUserLocked(Binder.getCallingPid(), Binder.getCallingUid(),
+                userId, false, true, "startInstrumentation", null);
         // Refuse possible leaked file descriptors
         if (arguments != null && arguments.hasFileDescriptors()) {
             throw new IllegalArgumentException("File descriptors passed in Bundle");
@@ -11828,9 +11917,10 @@
             try {
                 ii = mContext.getPackageManager().getInstrumentationInfo(
                     className, STOCK_PM_FLAGS);
-                ai = mContext.getPackageManager().getApplicationInfo(
-                        ii.targetPackage, STOCK_PM_FLAGS);
+                ai = AppGlobals.getPackageManager().getApplicationInfo(
+                        ii.targetPackage, STOCK_PM_FLAGS, userId);
             } catch (PackageManager.NameNotFoundException e) {
+            } catch (RemoteException e) {
             }
             if (ii == null) {
                 reportStartInstrumentationFailure(watcher, className,
@@ -11857,7 +11947,6 @@
                 throw new SecurityException(msg);
             }
 
-            int userId = UserHandle.getCallingUserId();
             final long origId = Binder.clearCallingIdentity();
             // Instrumentation can kill and relaunch even persistent processes
             forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId);
@@ -13780,6 +13869,9 @@
             }
 
             mCurrentUserId = userId;
+            Integer userIdInt = Integer.valueOf(userId);
+            mUserLru.remove(userIdInt);
+            mUserLru.add(userIdInt);
             boolean haveActivities = mMainStack.switchUser(userId);
             if (!haveActivities) {
                 startHomeActivityLocked(userId, mStartedUsers.get(userId));
@@ -13928,6 +14020,23 @@
         }
     }
 
+    @Override
+    public boolean isUserRunning(int userId) {
+        if (checkCallingPermission(android.Manifest.permission.INTERACT_ACROSS_USERS)
+                != PackageManager.PERMISSION_GRANTED) {
+            String msg = "Permission Denial: isUserRunning() from pid="
+                    + Binder.getCallingPid()
+                    + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.INTERACT_ACROSS_USERS;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+        synchronized (this) {
+            UserStartedState state = mStartedUsers.get(userId);
+            return state != null && state.mState != UserStartedState.STATE_STOPPING;
+        }
+    }
+
     private boolean userExists(int userId) {
         UserInfo user = getUserManager().getUserInfo(userId);
         return user != null;
diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java
index 399ea59..895b52a 100755
--- a/services/java/com/android/server/am/ActivityStack.java
+++ b/services/java/com/android/server/am/ActivityStack.java
@@ -422,11 +422,10 @@
     }
     
     final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) {
-        // TODO: Don't look for any tasks from other users
         int i = mHistory.size()-1;
         while (i >= 0) {
             ActivityRecord r = mHistory.get(i);
-            if (!r.finishing && r != notTop) {
+            if (!r.finishing && r != notTop && r.userId == mCurrentUser) {
                 return r;
             }
             i--;
@@ -435,11 +434,10 @@
     }
 
     final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) {
-        // TODO: Don't look for any tasks from other users
         int i = mHistory.size()-1;
         while (i >= 0) {
             ActivityRecord r = mHistory.get(i);
-            if (!r.finishing && !r.delayedResume && r != notTop) {
+            if (!r.finishing && !r.delayedResume && r != notTop && r.userId == mCurrentUser) {
                 return r;
             }
             i--;
@@ -457,12 +455,12 @@
      * @return Returns the HistoryRecord of the next activity on the stack.
      */
     final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) {
-        // TODO: Don't look for any tasks from other users
         int i = mHistory.size()-1;
         while (i >= 0) {
             ActivityRecord r = mHistory.get(i);
             // Note: the taskId check depends on real taskId fields being non-zero
-            if (!r.finishing && (token != r.appToken) && (taskId != r.task.taskId)) {
+            if (!r.finishing && (token != r.appToken) && (taskId != r.task.taskId)
+                    && r.userId == mCurrentUser) {
                 return r;
             }
             i--;
@@ -1400,7 +1398,7 @@
             // Launcher...
             if (mMainStack) {
                 ActivityOptions.abort(options);
-                return mService.startHomeActivityLocked(0, null);
+                return mService.startHomeActivityLocked(mCurrentUser, null);
             }
         }
 
diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java
index c80d63a..de306b5 100644
--- a/services/java/com/android/server/am/ContentProviderRecord.java
+++ b/services/java/com/android/server/am/ContentProviderRecord.java
@@ -85,7 +85,7 @@
 
     public boolean canRunHere(ProcessRecord app) {
         return (info.multiprocess || info.processName.equals(app.processName))
-                && (uid == Process.SYSTEM_UID || uid == app.info.uid);
+                && uid == app.info.uid;
     }
 
     public void addExternalProcessHandleLocked(IBinder token) {
diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java
index 0f72409..c61f13c 100644
--- a/services/java/com/android/server/am/PendingIntentRecord.java
+++ b/services/java/com/android/server/am/PendingIntentRecord.java
@@ -25,7 +25,6 @@
 import android.os.Bundle;
 import android.os.IBinder;
 import android.os.RemoteException;
-import android.os.UserHandle;
 import android.util.Slog;
 
 import java.io.PrintWriter;
diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java
index 7a4fef6..2d7167b 100644
--- a/services/java/com/android/server/am/ProviderMap.java
+++ b/services/java/com/android/server/am/ProviderMap.java
@@ -180,6 +180,49 @@
         }
     }
 
+    private boolean collectForceStopProvidersLocked(String name, int appId,
+            boolean doit, boolean evenPersistent, int userId,
+            HashMap<ComponentName, ContentProviderRecord> providers,
+            ArrayList<ContentProviderRecord> result) {
+        boolean didSomething = false;
+        for (ContentProviderRecord provider : providers.values()) {
+            if ((name == null || provider.info.packageName.equals(name))
+                    && (provider.proc == null || evenPersistent || !provider.proc.persistent)) {
+                if (!doit) {
+                    return true;
+                }
+                didSomething = true;
+                result.add(provider);
+            }
+        }
+        return didSomething;
+    }
+
+    boolean collectForceStopProviders(String name, int appId,
+            boolean doit, boolean evenPersistent, int userId,
+            ArrayList<ContentProviderRecord> result) {
+        boolean didSomething = collectForceStopProvidersLocked(name, appId, doit,
+                evenPersistent, userId, mSingletonByClass, result);
+        if (!doit && didSomething) {
+            return true;
+        }
+        if (userId == UserHandle.USER_ALL) {
+            for (int i=0; i<mProvidersByClassPerUser.size(); i++) {
+                if (collectForceStopProvidersLocked(name, appId, doit, evenPersistent,
+                        userId, mProvidersByClassPerUser.valueAt(i), result)) {
+                    if (!doit) {
+                        return true;
+                    }
+                    didSomething = true;
+                }
+            }
+        } else {
+            didSomething |= collectForceStopProvidersLocked(name, appId, doit, evenPersistent,
+                    userId, getProvidersByClass(userId), result);
+        }
+        return didSomething;
+    }
+
     private void dumpProvidersByClassLocked(PrintWriter pw, boolean dumpAll,
             HashMap<ComponentName, ContentProviderRecord> map) {
         Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it = map.entrySet().iterator();
diff --git a/services/java/com/android/server/display/DisplayDevice.java b/services/java/com/android/server/display/DisplayDevice.java
index bdc87f9..995c553 100644
--- a/services/java/com/android/server/display/DisplayDevice.java
+++ b/services/java/com/android/server/display/DisplayDevice.java
@@ -17,7 +17,6 @@
 package com.android.server.display;
 
 import android.graphics.Rect;
-import android.graphics.SurfaceTexture;
 import android.os.IBinder;
 import android.view.Surface;
 
@@ -41,9 +40,9 @@
     private Rect mCurrentLayerStackRect;
     private Rect mCurrentDisplayRect;
 
-    // The display device does own its surface texture, but it should only set it
+    // The display device owns its surface, but it should only set it
     // within a transaction from performTraversalInTransactionLocked.
-    private SurfaceTexture mCurrentSurfaceTexture;
+    private Surface mCurrentSurface;
 
     public DisplayDevice(DisplayAdapter displayAdapter, IBinder displayToken) {
         mDisplayAdapter = displayAdapter;
@@ -109,11 +108,10 @@
      * Sets the display layer stack while in a transaction.
      */
     public final void setLayerStackInTransactionLocked(int layerStack) {
-        if (mCurrentLayerStack == layerStack) {
-            return;
+        if (mCurrentLayerStack != layerStack) {
+            mCurrentLayerStack = layerStack;
+            Surface.setDisplayLayerStack(mDisplayToken, layerStack);
         }
-        mCurrentLayerStack = layerStack;
-        Surface.setDisplayLayerStack(mDisplayToken, layerStack);
     }
 
     /**
@@ -126,28 +124,35 @@
      *            mapped to. displayRect is specified post-orientation, that is
      *            it uses the orientation seen by the end-user
      */
-    public final void setProjectionInTransactionLocked(int orientation, Rect layerStackRect, Rect displayRect) {
-        mCurrentOrientation = orientation;
-        if (mCurrentLayerStackRect == null) {
-            mCurrentLayerStackRect = new Rect();
+    public final void setProjectionInTransactionLocked(int orientation,
+            Rect layerStackRect, Rect displayRect) {
+        if (mCurrentOrientation != orientation
+                || mCurrentLayerStackRect == null
+                || !mCurrentLayerStackRect.equals(layerStackRect)
+                || mCurrentDisplayRect == null
+                || !mCurrentDisplayRect.equals(displayRect)) {
+            mCurrentOrientation = orientation;
+            if (mCurrentLayerStackRect == null) {
+                mCurrentLayerStackRect = new Rect();
+            }
+            mCurrentLayerStackRect.set(layerStackRect);
+            if (mCurrentDisplayRect == null) {
+                mCurrentDisplayRect = new Rect();
+            }
+            mCurrentDisplayRect.set(displayRect);
+            Surface.setDisplayProjection(mDisplayToken,
+                    orientation, layerStackRect, displayRect);
         }
-        mCurrentLayerStackRect.set(layerStackRect);
-        if (mCurrentDisplayRect == null) {
-            mCurrentDisplayRect = new Rect();
-        }
-        mCurrentDisplayRect.set(displayRect);
-        Surface.setDisplayProjection(mDisplayToken, orientation, layerStackRect, displayRect);
     }
 
     /**
-     * Sets the surface texture while in a transaction.
+     * Sets the display surface while in a transaction.
      */
-    public final void setSurfaceTextureInTransactionLocked(SurfaceTexture surfaceTexture) {
-        if (mCurrentSurfaceTexture == surfaceTexture) {
-            return;
+    public final void setSurfaceInTransactionLocked(Surface surface) {
+        if (mCurrentSurface != surface) {
+            mCurrentSurface = surface;
+            Surface.setDisplaySurface(mDisplayToken, surface);
         }
-        mCurrentSurfaceTexture = surfaceTexture;
-        Surface.setDisplaySurface(mDisplayToken, surfaceTexture);
     }
 
     /**
@@ -156,10 +161,11 @@
      */
     public void dumpLocked(PrintWriter pw) {
         pw.println("mAdapter=" + mDisplayAdapter.getName());
+        pw.println("mDisplayToken=" + mDisplayToken);
         pw.println("mCurrentLayerStack=" + mCurrentLayerStack);
         pw.println("mCurrentOrientation=" + mCurrentOrientation);
-        pw.println("mCurrentViewport=" + mCurrentLayerStackRect);
-        pw.println("mCurrentFrame=" + mCurrentDisplayRect);
-        pw.println("mCurrentSurfaceTexture=" + mCurrentSurfaceTexture);
+        pw.println("mCurrentLayerStackRect=" + mCurrentLayerStackRect);
+        pw.println("mCurrentDisplayRect=" + mCurrentDisplayRect);
+        pw.println("mCurrentSurface=" + mCurrentSurface);
     }
 }
diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java
index 6f82119..c90a1c6 100644
--- a/services/java/com/android/server/display/DisplayDeviceInfo.java
+++ b/services/java/com/android/server/display/DisplayDeviceInfo.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import android.util.DisplayMetrics;
+
 import libcore.util.Objects;
 
 /**
@@ -58,13 +60,44 @@
      */
     public int height;
 
+    /**
+     * The refresh rate of the display.
+     */
     public float refreshRate;
+
+    /**
+     * The nominal apparent density of the display in DPI used for layout calculations.
+     * This density is sensitive to the viewing distance.  A big TV and a tablet may have
+     * the same apparent density even though the pixels on the TV are much bigger than
+     * those on the tablet.
+     */
     public int densityDpi;
+
+    /**
+     * The physical density of the display in DPI in the X direction.
+     * This density should specify the physical size of each pixel.
+     */
     public float xDpi;
+
+    /**
+     * The physical density of the display in DPI in the X direction.
+     * This density should specify the physical size of each pixel.
+     */
     public float yDpi;
 
+    /**
+     * Display flags.
+     */
     public int flags;
 
+    public void setAssumedDensityForExternalDisplay(int width, int height) {
+        densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080;
+        // Technically, these values should be smaller than the apparent density
+        // but we don't know the physical size of the display.
+        xDpi = densityDpi;
+        yDpi = densityDpi;
+    }
+
     @Override
     public boolean equals(Object o) {
         return o instanceof DisplayDeviceInfo && equals((DisplayDeviceInfo)o);
diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java
index 706007a..dc85d3f 100644
--- a/services/java/com/android/server/display/DisplayManagerService.java
+++ b/services/java/com/android/server/display/DisplayManagerService.java
@@ -58,7 +58,7 @@
  * </p><p>
  * Display adapters are only weakly coupled to the display manager service.
  * Display adapters communicate changes in display device state to the display manager
- * service asynchronously via a {@link DisplayAdapter.DisplayAdapterListener} registered
+ * service asynchronously via a {@link DisplayAdapter.Listener} registered
  * by the display manager service.  This separation of concerns is important for
  * two main reasons.  First, it neatly encapsulates the responsibilities of these
  * two classes: display adapters handle individual display devices whereas
@@ -254,8 +254,8 @@
      * Returns information about the specified logical display.
      *
      * @param displayId The logical display id.
-     * @param The logical display info, or null if the display does not exist.
-     * This object must be treated as immutable.
+     * @return The logical display info, or null if the display does not exist.  The
+     * returned object must be treated as immutable.
      */
     @Override // Binder call
     public DisplayInfo getDisplayInfo(int displayId) {
@@ -333,6 +333,8 @@
             if (shouldRegisterNonEssentialDisplayAdaptersLocked()) {
                 registerDisplayAdapterLocked(new OverlayDisplayAdapter(
                         mSyncRoot, mContext, mHandler, mDisplayAdapterListener, mUiHandler));
+                registerDisplayAdapterLocked(new WifiDisplayAdapter(
+                        mSyncRoot, mContext, mHandler, mDisplayAdapterListener));
             }
         }
     }
@@ -479,14 +481,34 @@
         }
     }
 
-    private void configureDisplayInTransactionLocked(DisplayDevice device) {
-        // TODO: add a proper per-display mirroring control
-        boolean isMirroring = SystemProperties.getBoolean("debug.display.mirror", true);
+    /**
+     * Tells the display manager whether there is interesting unique content on the
+     * specified logical display.  This is used to control automatic mirroring.
+     * <p>
+     * If the display has unique content, then the display manager arranges for it
+     * to be presented on a physical display if appropriate.  Otherwise, the display manager
+     * may choose to make the physical display mirror some other logical display.
+     * </p>
+     *
+     * @param displayId The logical display id to update.
+     * @param hasContent True if the logical display has content.
+     */
+    public void setDisplayHasContent(int displayId, boolean hasContent) {
+        synchronized (mSyncRoot) {
+            LogicalDisplay display = mLogicalDisplays.get(displayId);
+            if (display != null && display.hasContentLocked() != hasContent) {
+                display.setHasContentLocked(hasContent);
+                scheduleTraversalLocked();
+            }
+        }
 
+    }
+
+    private void configureDisplayInTransactionLocked(DisplayDevice device) {
         // Find the logical display that the display device is showing.
-        LogicalDisplay display = null;
-        if (!isMirroring) {
-            display = findLogicalDisplayForDeviceLocked(device);
+        LogicalDisplay display = findLogicalDisplayForDeviceLocked(device);
+        if (display != null && !display.hasContentLocked()) {
+            display = null;
         }
         if (display == null) {
             display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY);
@@ -609,8 +631,9 @@
      */
     public interface WindowManagerFuncs {
         /**
-         * Request that the window manager call {@link #performTraversalInTransaction}
-         * within a surface transaction at a later time.
+         * Request that the window manager call
+         * {@link #performTraversalInTransactionFromWindowManager} within a surface
+         * transaction at a later time.
          */
         void requestTraversal();
     }
diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java
index 4a8829a..4962753 100644
--- a/services/java/com/android/server/display/LocalDisplayAdapter.java
+++ b/services/java/com/android/server/display/LocalDisplayAdapter.java
@@ -120,19 +120,20 @@
                 mInfo.width = mPhys.width;
                 mInfo.height = mPhys.height;
                 mInfo.refreshRate = mPhys.refreshRate;
-                mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f);
-                mInfo.xDpi = mPhys.xDpi;
-                mInfo.yDpi = mPhys.yDpi;
                 if (mBuiltInDisplayId == Surface.BUILT_IN_DISPLAY_ID_MAIN) {
                     mInfo.name = getContext().getResources().getString(
                             com.android.internal.R.string.display_manager_built_in_display_name);
                     mInfo.flags = DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY
                             | DisplayDeviceInfo.FLAG_SECURE
                             | DisplayDeviceInfo.FLAG_SUPPORTS_ROTATION;
+                    mInfo.densityDpi = (int)(mPhys.density * 160 + 0.5f);
+                    mInfo.xDpi = mPhys.xDpi;
+                    mInfo.yDpi = mPhys.yDpi;
                 } else {
                     mInfo.name = getContext().getResources().getString(
                             com.android.internal.R.string.display_manager_hdmi_display_name);
                     mInfo.flags = DisplayDeviceInfo.FLAG_SECURE;
+                    mInfo.setAssumedDensityForExternalDisplay(mPhys.width, mPhys.height);
                 }
             }
             return mInfo;
diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java
index c864189..e0f63dd 100644
--- a/services/java/com/android/server/display/LogicalDisplay.java
+++ b/services/java/com/android/server/display/LogicalDisplay.java
@@ -63,6 +63,9 @@
     private DisplayDevice mPrimaryDisplayDevice;
     private DisplayDeviceInfo mPrimaryDisplayDeviceInfo;
 
+    // True if the logical display has unique content.
+    private boolean mHasContent;
+
     // Temporary rectangle used when needed.
     private final Rect mTempLayerStackRect = new Rect();
     private final Rect mTempDisplayRect = new Rect();
@@ -126,7 +129,7 @@
 
     /**
      * Returns true if the logical display is in a valid state.
-     * This method should be checked after calling {@link #update} to handle the
+     * This method should be checked after calling {@link #updateLocked} to handle the
      * case where a logical display should be removed because all of its associated
      * display devices are gone or if it is otherwise no longer needed.
      *
@@ -256,6 +259,29 @@
         device.setProjectionInTransactionLocked(orientation, mTempLayerStackRect, mTempDisplayRect);
     }
 
+    /**
+     * Returns true if the logical display has unique content.
+     * <p>
+     * If the display has unique content then we will try to ensure that it is
+     * visible on at least its primary display device.  Otherwise we will ignore the
+     * logical display and perhaps show mirrored content on the primary display device.
+     * </p>
+     *
+     * @return True if the display has unique content.
+     */
+    public boolean hasContentLocked() {
+        return mHasContent;
+    }
+
+    /**
+     * Sets whether the logical display has unique content.
+     *
+     * @param hasContent True if the display has unique content.
+     */
+    public void setHasContentLocked(boolean hasContent) {
+        mHasContent = hasContent;
+    }
+
     public void dumpLocked(PrintWriter pw) {
         pw.println("mLayerStack=" + mLayerStack);
         pw.println("mPrimaryDisplayDevice=" + (mPrimaryDisplayDevice != null ?
diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java
index ea7e88de..e2d3059 100644
--- a/services/java/com/android/server/display/OverlayDisplayAdapter.java
+++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java
@@ -16,9 +16,11 @@
 
 package com.android.server.display;
 
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
 import android.content.Context;
 import android.database.ContentObserver;
-import android.graphics.SurfaceTexture;
 import android.os.Handler;
 import android.os.IBinder;
 import android.provider.Settings;
@@ -28,7 +30,6 @@
 import android.view.Surface;
 
 import java.io.PrintWriter;
-import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -71,6 +72,7 @@
     @Override
     public void dumpLocked(PrintWriter pw) {
         super.dumpLocked(pw);
+
         pw.println("mCurrentOverlaySetting=" + mCurrentOverlaySetting);
         pw.println("mOverlays: size=" + mOverlays.size());
         for (OverlayDisplayHandle overlay : mOverlays) {
@@ -81,17 +83,19 @@
     @Override
     public void registerLocked() {
         super.registerLocked();
-        getContext().getContentResolver().registerContentObserver(
-                Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES), true,
-                new ContentObserver(getHandler()) {
-                    @Override
-                    public void onChange(boolean selfChange) {
-                        synchronized (getSyncRoot()) {
-                            updateOverlayDisplayDevicesLocked();
-                        }
-                    }
-                });
-        updateOverlayDisplayDevicesLocked();
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                getContext().getContentResolver().registerContentObserver(
+                        Settings.System.getUriFor(Settings.Secure.OVERLAY_DISPLAY_DEVICES),
+                        true, new SettingsObserver(getHandler()));
+
+                synchronized (getSyncRoot()) {
+                    updateOverlayDisplayDevicesLocked();
+                }
+            }
+        });
     }
 
     private void updateOverlayDisplayDevicesLocked() {
@@ -167,6 +171,19 @@
         }
     }
 
+    private final class SettingsObserver extends ContentObserver {
+        public SettingsObserver(Handler handler) {
+            super(handler);
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            synchronized (getSyncRoot()) {
+                updateOverlayDisplayDevicesLocked();
+            }
+        }
+    }
+
     private final class OverlayDisplayDevice extends DisplayDevice {
         private final String mName;
         private final int mWidth;
@@ -174,35 +191,29 @@
         private final float mRefreshRate;
         private final int mDensityDpi;
 
-        private SurfaceTexture mSurfaceTexture;
-        private boolean mSurfaceTextureChanged;
-
+        private Surface mSurface;
         private DisplayDeviceInfo mInfo;
 
         public OverlayDisplayDevice(IBinder displayToken, String name,
-                int width, int height, float refreshRate, int densityDpi) {
+                int width, int height, float refreshRate, int densityDpi,
+                Surface surface) {
             super(OverlayDisplayAdapter.this, displayToken);
             mName = name;
             mWidth = width;
             mHeight = height;
             mRefreshRate = refreshRate;
             mDensityDpi = densityDpi;
+            mSurface = surface;
         }
 
-        public void setSurfaceTextureLocked(SurfaceTexture surfaceTexture) {
-            if (surfaceTexture != mSurfaceTexture) {
-                mSurfaceTexture = surfaceTexture;
-                mSurfaceTextureChanged = true;
-                sendTraversalRequestLocked();
-            }
+        public void clearSurfaceLocked() {
+            mSurface = null;
+            sendTraversalRequestLocked();
         }
 
         @Override
         public void performTraversalInTransactionLocked() {
-            if (mSurfaceTextureChanged) {
-                setSurfaceTextureInTransactionLocked(mSurfaceTexture);
-                mSurfaceTextureChanged = false;
-            }
+            setSurfaceInTransactionLocked(mSurface);
         }
 
         @Override
@@ -256,12 +267,11 @@
 
         // Called on the UI thread.
         @Override
-        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate) {
+        public void onWindowCreated(Surface surface, float refreshRate) {
             synchronized (getSyncRoot()) {
                 IBinder displayToken = Surface.createDisplay(mName);
                 mDevice = new OverlayDisplayDevice(displayToken, mName,
-                        mWidth, mHeight, refreshRate, mDensityDpi);
-                mDevice.setSurfaceTextureLocked(surfaceTexture);
+                        mWidth, mHeight, refreshRate, mDensityDpi, surface);
 
                 sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
             }
@@ -272,40 +282,24 @@
         public void onWindowDestroyed() {
             synchronized (getSyncRoot()) {
                 if (mDevice != null) {
-                    mDevice.setSurfaceTextureLocked(null);
-
+                    mDevice.clearSurfaceLocked();
                     sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
                 }
             }
         }
 
         public void dumpLocked(PrintWriter pw) {
-            pw.println("  " + mName + ": ");
+            pw.println("  " + mName + ":");
             pw.println("    mWidth=" + mWidth);
             pw.println("    mHeight=" + mHeight);
             pw.println("    mDensityDpi=" + mDensityDpi);
             pw.println("    mGravity=" + mGravity);
 
             // Try to dump the window state.
-            // This call may hang if the UI thread is waiting to acquire our lock so
-            // we use a short timeout to recover just in case.
             if (mWindow != null) {
-                final StringWriter sw = new StringWriter();
-                final OverlayDisplayWindow window = mWindow;
-                if (mUiHandler.runWithScissors(new Runnable() {
-                    @Override
-                    public void run() {
-                        PrintWriter lpw = new PrintWriter(sw);
-                        window.dump(lpw);
-                        lpw.close();
-                    }
-                }, 200)) {
-                    for (String line : sw.toString().split("\n")) {
-                        pw.println(line);
-                    }
-                } else {
-                    pw.println("    ... timed out while attempting to dump window state");
-                }
+                final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "    ");
+                ipw.increaseIndent();
+                DumpUtils.dumpAsync(mUiHandler, mWindow, ipw, 200);
             }
         }
 
diff --git a/services/java/com/android/server/display/OverlayDisplayWindow.java b/services/java/com/android/server/display/OverlayDisplayWindow.java
index 6adfa0f..d08f65f 100644
--- a/services/java/com/android/server/display/OverlayDisplayWindow.java
+++ b/services/java/com/android/server/display/OverlayDisplayWindow.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import com.android.internal.util.DumpUtils;
+
 import android.content.Context;
 import android.graphics.SurfaceTexture;
 import android.hardware.display.DisplayManager;
@@ -27,6 +29,7 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.ScaleGestureDetector;
+import android.view.Surface;
 import android.view.TextureView;
 import android.view.View;
 import android.view.WindowManager;
@@ -42,7 +45,7 @@
  * No locks are held by this object and locks must not be held while making called into it.
  * </p>
  */
-final class OverlayDisplayWindow {
+final class OverlayDisplayWindow implements DumpUtils.Dump {
     private static final String TAG = "OverlayDisplayWindow";
     private static final boolean DEBUG = false;
 
@@ -144,18 +147,18 @@
     }
 
     public void dump(PrintWriter pw) {
-        pw.println("    mWindowVisible=" + mWindowVisible);
-        pw.println("    mWindowX=" + mWindowX);
-        pw.println("    mWindowY=" + mWindowY);
-        pw.println("    mWindowScale=" + mWindowScale);
-        pw.println("    mWindowParams=" + mWindowParams);
+        pw.println("mWindowVisible=" + mWindowVisible);
+        pw.println("mWindowX=" + mWindowX);
+        pw.println("mWindowY=" + mWindowY);
+        pw.println("mWindowScale=" + mWindowScale);
+        pw.println("mWindowParams=" + mWindowParams);
         if (mTextureView != null) {
-            pw.println("    mTextureView.getScaleX()=" + mTextureView.getScaleX());
-            pw.println("    mTextureView.getScaleY()=" + mTextureView.getScaleY());
+            pw.println("mTextureView.getScaleX()=" + mTextureView.getScaleX());
+            pw.println("mTextureView.getScaleY()=" + mTextureView.getScaleY());
         }
-        pw.println("    mLiveTranslationX=" + mLiveTranslationX);
-        pw.println("    mLiveTranslationY=" + mLiveTranslationY);
-        pw.println("    mLiveScale=" + mLiveScale);
+        pw.println("mLiveTranslationX=" + mLiveTranslationX);
+        pw.println("mLiveTranslationY=" + mLiveTranslationY);
+        pw.println("mLiveScale=" + mLiveScale);
     }
 
     private boolean updateDefaultDisplayInfo() {
@@ -286,22 +289,25 @@
     private final SurfaceTextureListener mSurfaceTextureListener =
             new SurfaceTextureListener() {
         @Override
-        public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
-            mListener.onWindowCreated(surface, mDefaultDisplayInfo.refreshRate);
+        public void onSurfaceTextureAvailable(SurfaceTexture surfaceTexture,
+                int width, int height) {
+            mListener.onWindowCreated(new Surface(surfaceTexture),
+                    mDefaultDisplayInfo.refreshRate);
         }
 
         @Override
-        public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+        public boolean onSurfaceTextureDestroyed(SurfaceTexture surfaceTexture) {
             mListener.onWindowDestroyed();
             return true;
         }
 
         @Override
-        public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+        public void onSurfaceTextureSizeChanged(SurfaceTexture surfaceTexture,
+                int width, int height) {
         }
 
         @Override
-        public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+        public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) {
         }
     };
 
@@ -355,7 +361,7 @@
      * Watches for significant changes in the overlay display window lifecycle.
      */
     public interface Listener {
-        public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate);
+        public void onWindowCreated(Surface surface, float refreshRate);
         public void onWindowDestroyed();
     }
 }
\ No newline at end of file
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
new file mode 100644
index 0000000..38007af
--- /dev/null
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -0,0 +1,259 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.internal.util.DumpUtils;
+import com.android.internal.util.IndentingPrintWriter;
+
+import android.content.Context;
+import android.media.RemoteDisplay;
+import android.os.Handler;
+import android.os.IBinder;
+import android.util.Slog;
+import android.view.Surface;
+
+import java.io.PrintWriter;
+
+/**
+ * Connects to Wifi displays that implement the Miracast protocol.
+ * <p>
+ * The Wifi display protocol relies on Wifi direct for discovering and pairing
+ * with the display.  Once connected, the Media Server opens an RTSP socket and accepts
+ * a connection from the display.  After session negotiation, the Media Server
+ * streams encoded buffers to the display.
+ * </p><p>
+ * This class is responsible for connecting to Wifi displays and mediating
+ * the interactions between Media Server, Surface Flinger and the Display Manager Service.
+ * </p><p>
+ * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock.
+ * </p>
+ */
+final class WifiDisplayAdapter extends DisplayAdapter {
+    private static final String TAG = "WifiDisplayAdapter";
+
+    private WifiDisplayHandle mDisplayHandle;
+    private WifiDisplayController mDisplayController;
+
+    public WifiDisplayAdapter(DisplayManagerService.SyncRoot syncRoot,
+            Context context, Handler handler, Listener listener) {
+        super(syncRoot, context, handler, listener, TAG);
+    }
+
+    @Override
+    public void dumpLocked(PrintWriter pw) {
+        super.dumpLocked(pw);
+
+        if (mDisplayHandle == null) {
+            pw.println("mDisplayHandle=null");
+        } else {
+            pw.println("mDisplayHandle:");
+            mDisplayHandle.dumpLocked(pw);
+        }
+
+        // Try to dump the controller state.
+        if (mDisplayController == null) {
+            pw.println("mDisplayController=null");
+        } else {
+            pw.println("mDisplayController:");
+            final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, "  ");
+            ipw.increaseIndent();
+            DumpUtils.dumpAsync(getHandler(), mDisplayController, ipw, 200);
+        }
+    }
+
+    @Override
+    public void registerLocked() {
+        super.registerLocked();
+
+        getHandler().post(new Runnable() {
+            @Override
+            public void run() {
+                mDisplayController = new WifiDisplayController(
+                        getContext(), getHandler(), mWifiDisplayListener);
+            }
+        });
+    }
+
+    private void connectLocked(String deviceName, String iface) {
+        disconnectLocked();
+
+        String name = getContext().getResources().getString(
+                com.android.internal.R.string.display_manager_wifi_display_name,
+                deviceName);
+        mDisplayHandle = new WifiDisplayHandle(name, iface);
+    }
+
+    private void disconnectLocked() {
+        if (mDisplayHandle != null) {
+            mDisplayHandle.disposeLocked();
+            mDisplayHandle = null;
+        }
+    }
+
+    private final WifiDisplayController.Listener mWifiDisplayListener =
+            new WifiDisplayController.Listener() {
+        @Override
+        public void onDisplayConnected(String deviceName, String iface) {
+            synchronized (getSyncRoot()) {
+                connectLocked(deviceName, iface);
+            }
+        }
+
+        @Override
+        public void onDisplayDisconnected() {
+            // Stop listening.
+            synchronized (getSyncRoot()) {
+                disconnectLocked();
+            }
+        }
+    };
+
+    private final class WifiDisplayDevice extends DisplayDevice {
+        private final String mName;
+        private final int mWidth;
+        private final int mHeight;
+        private final float mRefreshRate;
+        private final int mFlags;
+
+        private Surface mSurface;
+        private DisplayDeviceInfo mInfo;
+
+        public WifiDisplayDevice(IBinder displayToken, String name,
+                int width, int height, float refreshRate, int flags,
+                Surface surface) {
+            super(WifiDisplayAdapter.this, displayToken);
+            mName = name;
+            mWidth = width;
+            mHeight = height;
+            mRefreshRate = refreshRate;
+            mFlags = flags;
+            mSurface = surface;
+        }
+
+        public void clearSurfaceLocked() {
+            mSurface = null;
+            sendTraversalRequestLocked();
+        }
+
+        @Override
+        public void performTraversalInTransactionLocked() {
+            setSurfaceInTransactionLocked(mSurface);
+        }
+
+        @Override
+        public DisplayDeviceInfo getDisplayDeviceInfoLocked() {
+            if (mInfo == null) {
+                mInfo = new DisplayDeviceInfo();
+                mInfo.name = mName;
+                mInfo.width = mWidth;
+                mInfo.height = mHeight;
+                mInfo.refreshRate = mRefreshRate;
+                mInfo.flags = mFlags;
+                mInfo.setAssumedDensityForExternalDisplay(mWidth, mHeight);
+            }
+            return mInfo;
+        }
+    }
+
+    private final class WifiDisplayHandle implements RemoteDisplay.Listener {
+        private final String mName;
+        private final String mIface;
+        private final RemoteDisplay mRemoteDisplay;
+
+        private WifiDisplayDevice mDevice;
+        private int mLastError;
+
+        public WifiDisplayHandle(String name, String iface) {
+            mName = name;
+            mIface = iface;
+            mRemoteDisplay = RemoteDisplay.listen(iface, this, getHandler());
+
+            Slog.i(TAG, "Listening for Wifi display connections on " + iface
+                    + " from " + mName);
+        }
+
+        public void disposeLocked() {
+            Slog.i(TAG, "Stopped listening for Wifi display connections on " + mIface
+                    + " from " + mName);
+
+            removeDisplayLocked();
+            mRemoteDisplay.dispose();
+        }
+
+        public void dumpLocked(PrintWriter pw) {
+            pw.println("  " + mName + ": " + (mDevice != null ? "connected" : "disconnected"));
+            pw.println("    mIface=" + mIface);
+            pw.println("    mLastError=" + mLastError);
+        }
+
+        // Called on the handler thread.
+        @Override
+        public void onDisplayConnected(Surface surface, int width, int height, int flags) {
+            synchronized (getSyncRoot()) {
+                mLastError = 0;
+                removeDisplayLocked();
+                addDisplayLocked(surface, width, height, flags);
+
+                Slog.i(TAG, "Wifi display connected: " + mName);
+            }
+        }
+
+        // Called on the handler thread.
+        @Override
+        public void onDisplayDisconnected() {
+            synchronized (getSyncRoot()) {
+                mLastError = 0;
+                removeDisplayLocked();
+
+                Slog.i(TAG, "Wifi display disconnected: " + mName);
+            }
+        }
+
+        // Called on the handler thread.
+        @Override
+        public void onDisplayError(int error) {
+            synchronized (getSyncRoot()) {
+                mLastError = error;
+                removeDisplayLocked();
+
+                Slog.i(TAG, "Wifi display disconnected due to error " + error + ": " + mName);
+            }
+        }
+
+        private void addDisplayLocked(Surface surface, int width, int height, int flags) {
+            int deviceFlags = 0;
+            if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
+                deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
+            }
+
+            float refreshRate = 60.0f; // TODO: get this for real
+
+            IBinder displayToken = Surface.createDisplay(mName);
+            mDevice = new WifiDisplayDevice(displayToken, mName, width, height,
+                    refreshRate, deviceFlags, surface);
+            sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
+        }
+
+        private void removeDisplayLocked() {
+            if (mDevice != null) {
+                mDevice.clearSurfaceLocked();
+                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
+                mDevice = null;
+            }
+        }
+    }
+}
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
new file mode 100644
index 0000000..b446e66
--- /dev/null
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -0,0 +1,555 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.display;
+
+import com.android.internal.util.DumpUtils;
+
+import android.content.BroadcastReceiver;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.net.NetworkInfo;
+import android.net.wifi.p2p.WifiP2pConfig;
+import android.net.wifi.p2p.WifiP2pDevice;
+import android.net.wifi.p2p.WifiP2pDeviceList;
+import android.net.wifi.p2p.WifiP2pGroup;
+import android.net.wifi.p2p.WifiP2pManager;
+import android.net.wifi.p2p.WifiP2pWfdInfo;
+import android.net.wifi.p2p.WifiP2pManager.ActionListener;
+import android.net.wifi.p2p.WifiP2pManager.Channel;
+import android.net.wifi.p2p.WifiP2pManager.GroupInfoListener;
+import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
+import android.os.Handler;
+import android.util.Slog;
+
+import java.io.PrintWriter;
+import java.net.Inet4Address;
+import java.net.InetAddress;
+import java.net.NetworkInterface;
+import java.net.SocketException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+
+/**
+ * Manages all of the various asynchronous interactions with the {@link WifiP2pManager}
+ * on behalf of {@link WifiDisplayAdapter}.
+ * <p>
+ * This code is isolated from {@link WifiDisplayAdapter} so that we can avoid
+ * accidentally introducing any deadlocks due to the display manager calling
+ * outside of itself while holding its lock.  It's also way easier to write this
+ * asynchronous code if we can assume that it is single-threaded.
+ * </p><p>
+ * The controller must be instantiated on the handler thread.
+ * </p>
+ */
+final class WifiDisplayController implements DumpUtils.Dump {
+    private static final String TAG = "WifiDisplayController";
+    private static final boolean DEBUG = true;
+
+    private static final int DEFAULT_CONTROL_PORT = 7236;
+    private static final int MAX_THROUGHPUT = 50;
+    private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+
+    private final Context mContext;
+    private final Handler mHandler;
+    private final Listener mListener;
+    private final WifiP2pManager mWifiP2pManager;
+    private final Channel mWifiP2pChannel;
+
+    private boolean mWifiP2pEnabled;
+    private boolean mWfdEnabled;
+    private boolean mWfdEnabling;
+    private NetworkInfo mNetworkInfo;
+
+    private final ArrayList<WifiP2pDevice> mKnownWifiDisplayPeers =
+            new ArrayList<WifiP2pDevice>();
+
+    // The device to which we want to connect, or null if we want to be disconnected.
+    private WifiP2pDevice mDesiredDevice;
+
+    // The device to which we are currently connecting, or null if we have already connected
+    // or are not trying to connect.
+    private WifiP2pDevice mConnectingDevice;
+
+    // The device to which we are currently connected, which means we have an active P2P group.
+    private WifiP2pDevice mConnectedDevice;
+
+    // The group info obtained after connecting.
+    private WifiP2pGroup mConnectedDeviceGroupInfo;
+
+    // The device that we announced to the rest of the system.
+    private WifiP2pDevice mPublishedDevice;
+
+    public WifiDisplayController(Context context, Handler handler, Listener listener) {
+        mContext = context;
+        mHandler = handler;
+        mListener = listener;
+
+        mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE);
+        mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null);
+
+        IntentFilter intentFilter = new IntentFilter();
+        intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION);
+        intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION);
+        intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
+        context.registerReceiver(mWifiP2pReceiver, intentFilter);
+    }
+
+    public void dump(PrintWriter pw) {
+        pw.println("mWifiP2pEnabled=" + mWifiP2pEnabled);
+        pw.println("mWfdEnabled=" + mWfdEnabled);
+        pw.println("mWfdEnabling=" + mWfdEnabling);
+        pw.println("mNetworkInfo=" + mNetworkInfo);
+        pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
+        pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
+        pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
+        pw.println("mPublishedDevice=" + describeWifiP2pDevice(mPublishedDevice));
+
+        pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size());
+        for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
+            pw.println("  " + describeWifiP2pDevice(device));
+        }
+    }
+
+    private void enableWfd() {
+        if (!mWfdEnabled && !mWfdEnabling) {
+            mWfdEnabling = true;
+
+            WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo();
+            wfdInfo.setWfdEnabled(true);
+            wfdInfo.setDeviceType(WifiP2pWfdInfo.WFD_SOURCE);
+            wfdInfo.setSessionAvailable(true);
+            wfdInfo.setControlPort(DEFAULT_CONTROL_PORT);
+            wfdInfo.setMaxThroughput(MAX_THROUGHPUT);
+            mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() {
+                @Override
+                public void onSuccess() {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Successfully set WFD info.");
+                    }
+                    if (mWfdEnabling) {
+                        mWfdEnabled = true;
+                        mWfdEnabling = false;
+                        discoverPeers();
+                    }
+                }
+
+                @Override
+                public void onFailure(int reason) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "Failed to set WFD info with reason " + reason + ".");
+                    }
+                    mWfdEnabling = false;
+                }
+            });
+        }
+    }
+
+    private void discoverPeers() {
+        mWifiP2pManager.discoverPeers(mWifiP2pChannel, new ActionListener() {
+            @Override
+            public void onSuccess() {
+                if (DEBUG) {
+                    Slog.d(TAG, "Discover peers succeeded.  Requesting peers now.");
+                }
+
+                requestPeers();
+            }
+
+            @Override
+            public void onFailure(int reason) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Discover peers failed with reason " + reason + ".");
+                }
+            }
+        });
+    }
+
+    private void requestPeers() {
+        mWifiP2pManager.requestPeers(mWifiP2pChannel, new PeerListListener() {
+            @Override
+            public void onPeersAvailable(WifiP2pDeviceList peers) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Received list of peers.");
+                }
+
+                mKnownWifiDisplayPeers.clear();
+                for (WifiP2pDevice device : peers.getDeviceList()) {
+                    if (DEBUG) {
+                        Slog.d(TAG, "  " + describeWifiP2pDevice(device));
+                    }
+
+                    if (isWifiDisplay(device)) {
+                        mKnownWifiDisplayPeers.add(device);
+                    }
+                }
+
+                // TODO: shouldn't auto-connect like this, let UI do it explicitly
+                if (!mKnownWifiDisplayPeers.isEmpty()) {
+                    final WifiP2pDevice device = mKnownWifiDisplayPeers.get(0);
+
+                    if (device.status == WifiP2pDevice.AVAILABLE) {
+                        connect(device);
+                    }
+                }
+
+                // TODO: publish this information to applications
+            }
+        });
+    }
+
+    private void connect(final WifiP2pDevice device) {
+        if (mDesiredDevice != null
+                && !mDesiredDevice.deviceAddress.equals(device.deviceAddress)) {
+            if (DEBUG) {
+                Slog.d(TAG, "connect: nothing to do, already connecting to "
+                        + describeWifiP2pDevice(device));
+            }
+            return;
+        }
+
+        if (mConnectedDevice != null
+                && !mConnectedDevice.deviceAddress.equals(device.deviceAddress)
+                && mDesiredDevice == null) {
+            if (DEBUG) {
+                Slog.d(TAG, "connect: nothing to do, already connected to "
+                        + describeWifiP2pDevice(device) + " and not part way through "
+                        + "connecting to a different device.");
+            }
+        }
+
+        mDesiredDevice = device;
+        updateConnection();
+    }
+
+    private void disconnect() {
+        mDesiredDevice = null;
+        updateConnection();
+    }
+
+    /**
+     * This function is called repeatedly after each asynchronous operation
+     * until all preconditions for the connection have been satisfied and the
+     * connection is established (or not).
+     */
+    private void updateConnection() {
+        // Step 1. Before we try to connect to a new device, tell the system we
+        // have disconnected from the old one.
+        if (mPublishedDevice != null && mPublishedDevice != mDesiredDevice) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onDisplayDisconnected();
+                }
+            });
+            mPublishedDevice = null;
+
+            // continue to next step
+        }
+
+        // Step 2. Before we try to connect to a new device, disconnect from the old one.
+        if (mConnectedDevice != null && mConnectedDevice != mDesiredDevice) {
+            Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName);
+
+            final WifiP2pDevice oldDevice = mConnectedDevice;
+            mWifiP2pManager.removeGroup(mWifiP2pChannel, new ActionListener() {
+                @Override
+                public void onSuccess() {
+                    Slog.i(TAG, "Disconnected from Wifi display: " + oldDevice.deviceName);
+                    next();
+                }
+
+                @Override
+                public void onFailure(int reason) {
+                    Slog.i(TAG, "Failed to disconnect from Wifi display: "
+                            + oldDevice.deviceName + ", reason=" + reason);
+                    next();
+                }
+
+                private void next() {
+                    if (mConnectedDevice == oldDevice) {
+                        mConnectedDevice = null;
+                        updateConnection();
+                    }
+                }
+            });
+            return; // wait for asynchronous callback
+        }
+
+        // Step 3. Before we try to connect to a new device, stop trying to connect
+        // to the old one.
+        if (mConnectingDevice != null && mConnectingDevice != mDesiredDevice) {
+            Slog.i(TAG, "Canceling connection to Wifi display: " + mConnectingDevice.deviceName);
+
+            mHandler.removeCallbacks(mConnectionTimeout);
+
+            final WifiP2pDevice oldDevice = mConnectingDevice;
+            mWifiP2pManager.cancelConnect(mWifiP2pChannel, new ActionListener() {
+                @Override
+                public void onSuccess() {
+                    Slog.i(TAG, "Canceled connection to Wifi display: " + oldDevice.deviceName);
+                    next();
+                }
+
+                @Override
+                public void onFailure(int reason) {
+                    Slog.i(TAG, "Failed to cancel connection to Wifi display: "
+                            + oldDevice.deviceName + ", reason=" + reason);
+                    next();
+                }
+
+                private void next() {
+                    if (mConnectingDevice == oldDevice) {
+                        mConnectingDevice = null;
+                        updateConnection();
+                    }
+                }
+            });
+            return; // wait for asynchronous callback
+        }
+
+        // Step 4. If we wanted to disconnect, then mission accomplished.
+        if (mDesiredDevice == null) {
+            return; // done
+        }
+
+        // Step 5. Try to connect.
+        if (mConnectedDevice == null && mConnectingDevice == null) {
+            Slog.i(TAG, "Connecting to Wifi display: " + mDesiredDevice.deviceName);
+
+            mConnectingDevice = mDesiredDevice;
+            WifiP2pConfig config = new WifiP2pConfig();
+            config.deviceAddress = mConnectingDevice.deviceAddress;
+
+            final WifiP2pDevice newDevice = mDesiredDevice;
+            mWifiP2pManager.connect(mWifiP2pChannel, config, new ActionListener() {
+                @Override
+                public void onSuccess() {
+                    // The connection may not yet be established.  We still need to wait
+                    // for WIFI_P2P_CONNECTION_CHANGED_ACTION.  However, we might never
+                    // get that broadcast, so we register a timeout.
+                    Slog.i(TAG, "Initiated connection to Wifi display: " + newDevice.deviceName);
+
+                    mHandler.postDelayed(mConnectionTimeout, CONNECTION_TIMEOUT_SECONDS * 1000);
+                }
+
+                @Override
+                public void onFailure(int reason) {
+                    Slog.i(TAG, "Failed to initiate connection to Wifi display: "
+                            + newDevice.deviceName + ", reason=" + reason);
+                    if (mConnectingDevice == newDevice) {
+                        mConnectingDevice = null;
+                        handleConnectionFailure();
+                    }
+                }
+            });
+            return; // wait for asynchronous callback
+        }
+
+        // Step 6. Publish the new connection.
+        if (mConnectedDevice != null && mPublishedDevice == null) {
+            Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
+            if (addr == null) {
+                Slog.i(TAG, "Failed to get local interface address for communicating "
+                        + "with Wifi display: " + mConnectedDevice.deviceName);
+                handleConnectionFailure();
+                return; // done
+            }
+
+            WifiP2pWfdInfo wfdInfo = mConnectedDevice.wfdInfo;
+            int port = (wfdInfo != null ? wfdInfo.getControlPort() : DEFAULT_CONTROL_PORT);
+            final String name = mConnectedDevice.deviceName;
+            final String iface = addr.getHostAddress() + ":" + port;
+
+            mPublishedDevice = mConnectedDevice;
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mListener.onDisplayConnected(name, iface);
+                }
+            });
+        }
+    }
+
+    private void handleStateChanged(boolean enabled) {
+        if (mWifiP2pEnabled != enabled) {
+            mWifiP2pEnabled = enabled;
+            if (enabled) {
+                if (mWfdEnabled) {
+                    discoverPeers();
+                } else {
+                    enableWfd();
+                }
+            } else {
+                mWfdEnabled = false;
+                disconnect();
+            }
+        }
+    }
+
+    private void handlePeersChanged() {
+        if (mWifiP2pEnabled) {
+            if (mWfdEnabled) {
+                requestPeers();
+            } else {
+                enableWfd();
+            }
+        }
+    }
+
+    private void handleConnectionChanged(NetworkInfo networkInfo) {
+        mNetworkInfo = networkInfo;
+        if (mWifiP2pEnabled && mWfdEnabled && networkInfo.isConnected()) {
+            if (mDesiredDevice != null) {
+                mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() {
+                    @Override
+                    public void onGroupInfoAvailable(WifiP2pGroup info) {
+                        if (DEBUG) {
+                            Slog.d(TAG, "Received group info: " + describeWifiP2pGroup(info));
+                        }
+
+                        if (mConnectingDevice != null && !info.contains(mConnectingDevice)) {
+                            Slog.i(TAG, "Aborting connection to Wifi display because "
+                                    + "the current P2P group does not contain the device "
+                                    + "we expected to find: " + mConnectingDevice.deviceName);
+                            handleConnectionFailure();
+                            return;
+                        }
+
+                        if (mDesiredDevice != null && !info.contains(mDesiredDevice)) {
+                            disconnect();
+                            return;
+                        }
+
+                        if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
+                            Slog.i(TAG, "Connected to Wifi display: " + mConnectingDevice.deviceName);
+
+                            mHandler.removeCallbacks(mConnectionTimeout);
+                            mConnectedDeviceGroupInfo = info;
+                            mConnectedDevice = mConnectingDevice;
+                            mConnectingDevice = null;
+                            updateConnection();
+                        }
+                    }
+                });
+            }
+        } else {
+            disconnect();
+        }
+    }
+
+    private final Runnable mConnectionTimeout = new Runnable() {
+        @Override
+        public void run() {
+            if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) {
+                Slog.i(TAG, "Timed out waiting for Wifi display connection after "
+                        + CONNECTION_TIMEOUT_SECONDS + " seconds: "
+                        + mConnectingDevice.deviceName);
+                handleConnectionFailure();
+            }
+        }
+    };
+
+    private void handleConnectionFailure() {
+        if (mDesiredDevice != null) {
+            Slog.i(TAG, "Wifi display connection failed!");
+            disconnect();
+        }
+    }
+
+    private static Inet4Address getInterfaceAddress(WifiP2pGroup info) {
+        NetworkInterface iface;
+        try {
+            iface = NetworkInterface.getByName(info.getInterface());
+        } catch (SocketException ex) {
+            Slog.w(TAG, "Could not obtain address of network interface "
+                    + info.getInterface(), ex);
+            return null;
+        }
+
+        Enumeration<InetAddress> addrs = iface.getInetAddresses();
+        while (addrs.hasMoreElements()) {
+            InetAddress addr = addrs.nextElement();
+            if (addr instanceof Inet4Address) {
+                return (Inet4Address)addr;
+            }
+        }
+
+        Slog.w(TAG, "Could not obtain address of network interface "
+                + info.getInterface() + " because it had no IPv4 addresses.");
+        return null;
+    }
+
+    private static boolean isWifiDisplay(WifiP2pDevice device) {
+        // FIXME: the wfdInfo API doesn't work yet
+        return false;
+        //return device.deviceName.equals("DWD-300-22ACC2");
+        //return device.deviceName.startsWith("DWD-")
+        //        || device.deviceName.startsWith("DIRECT-")
+        //        || device.deviceName.startsWith("CAVM-");
+        //return device.wfdInfo != null && device.wfdInfo.isWfdEnabled();
+    }
+
+    private static String describeWifiP2pDevice(WifiP2pDevice device) {
+        return device != null ? device.toString().replace('\n', ',') : "null";
+    }
+
+    private static String describeWifiP2pGroup(WifiP2pGroup group) {
+        return group != null ? group.toString().replace('\n', ',') : "null";
+    }
+
+    private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (action.equals(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)) {
+                boolean enabled = (intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE,
+                        WifiP2pManager.WIFI_P2P_STATE_DISABLED)) ==
+                        WifiP2pManager.WIFI_P2P_STATE_ENABLED;
+                if (DEBUG) {
+                    Slog.d(TAG, "Received WIFI_P2P_STATE_CHANGED_ACTION: enabled="
+                            + enabled);
+                }
+
+                handleStateChanged(enabled);
+            } else if (action.equals(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)) {
+                if (DEBUG) {
+                    Slog.d(TAG, "Received WIFI_P2P_PEERS_CHANGED_ACTION.");
+                }
+
+                handlePeersChanged();
+            } else if (action.equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
+                NetworkInfo networkInfo = (NetworkInfo)intent.getParcelableExtra(
+                        WifiP2pManager.EXTRA_NETWORK_INFO);
+                if (DEBUG) {
+                    Slog.d(TAG, "Received WIFI_P2P_CONNECTION_CHANGED_ACTION: networkInfo="
+                            + networkInfo);
+                }
+
+                handleConnectionChanged(networkInfo);
+            }
+        }
+    };
+
+    /**
+     * Called on the handler thread when displays are connected or disconnected.
+     */
+    public interface Listener {
+        void onDisplayConnected(String deviceName, String iface);
+        void onDisplayDisconnected();
+    }
+}
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index 55da11f..133d926 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -1193,7 +1193,7 @@
                         if (mSettings.isDisabledSystemPackageLPr(ps.name)) {
                             Slog.i(TAG, "Expecting better updatd system app for " + ps.name
                                     + "; removing system app");
-                            removePackageLI(scannedPkg, true);
+                            removePackageLI(ps, true);
                         }
 
                         continue;
@@ -1822,7 +1822,7 @@
                 }
                 pkg = new PackageParser.Package(packageName);
                 pkg.applicationInfo.packageName = packageName;
-                pkg.applicationInfo.flags = ps.pkgFlags;
+                pkg.applicationInfo.flags = ps.pkgFlags | ApplicationInfo.FLAG_IS_DATA_ONLY;
                 pkg.applicationInfo.publicSourceDir = ps.resourcePathString;
                 pkg.applicationInfo.sourceDir = ps.codePathString;
                 pkg.applicationInfo.dataDir =
@@ -4110,8 +4110,13 @@
                     NativeLibraryHelper.copyNativeBinariesIfNeededLI(scanFile, nativeLibraryDir);
                 } else {
                     Slog.i(TAG, "Linking native library dir for " + path);
-                    mInstaller.linkNativeLibraryDirectory(dataPathString,
+                    int ret = mInstaller.linkNativeLibraryDirectory(dataPathString,
                             pkg.applicationInfo.nativeLibraryDir);
+                    if (ret < 0) {
+                        Slog.w(TAG, "Failed linking native library dir for " + path);
+                        mLastScanError = PackageManager.MOVE_FAILED_INSUFFICIENT_STORAGE;
+                        return null;
+                    }
                 }
             } catch (IOException ioe) {
                 Log.e(TAG, "Unable to get canonical file " + ioe.toString());
@@ -4429,20 +4434,40 @@
         return pkg;
     }
 
-    private void killApplication(String pkgName, int uid) {
+    private void killApplication(String pkgName, int appId) {
         // Request the ActivityManager to kill the process(only for existing packages)
         // so that we do not end up in a confused state while the user is still using the older
         // version of the application while the new one gets installed.
         IActivityManager am = ActivityManagerNative.getDefault();
         if (am != null) {
             try {
-                am.killApplicationWithUid(pkgName, uid);
+                am.killApplicationWithAppId(pkgName, appId);
             } catch (RemoteException e) {
             }
         }
     }
 
-    void removePackageLI(PackageParser.Package pkg, boolean chatty) {
+    void removePackageLI(PackageSetting ps, boolean chatty) {
+        if (DEBUG_INSTALL) {
+            if (chatty)
+                Log.d(TAG, "Removing package " + ps.name);
+        }
+
+        // writer
+        synchronized (mPackages) {
+            mPackages.remove(ps.name);
+            if (ps.codePathString != null) {
+                mAppDirs.remove(ps.codePathString);
+            }
+
+            final PackageParser.Package pkg = ps.pkg;
+            if (pkg != null) {
+                cleanPackageDataStructuresLILPw(pkg, chatty);
+            }
+        }
+    }
+
+    void removeInstalledPackageLI(PackageParser.Package pkg, boolean chatty) {
         if (DEBUG_INSTALL) {
             if (chatty)
                 Log.d(TAG, "Removing package " + pkg.applicationInfo.packageName);
@@ -4454,34 +4479,115 @@
             if (pkg.mPath != null) {
                 mAppDirs.remove(pkg.mPath);
             }
+            cleanPackageDataStructuresLILPw(pkg, chatty);
+        }
+    }
 
-            int N = pkg.providers.size();
-            StringBuilder r = null;
-            int i;
-            for (i=0; i<N; i++) {
-                PackageParser.Provider p = pkg.providers.get(i);
-                mProvidersByComponent.remove(new ComponentName(p.info.packageName,
-                        p.info.name));
-                if (p.info.authority == null) {
+    void cleanPackageDataStructuresLILPw(PackageParser.Package pkg, boolean chatty) {
+        int N = pkg.providers.size();
+        StringBuilder r = null;
+        int i;
+        for (i=0; i<N; i++) {
+            PackageParser.Provider p = pkg.providers.get(i);
+            mProvidersByComponent.remove(new ComponentName(p.info.packageName,
+                    p.info.name));
+            if (p.info.authority == null) {
 
-                    /* The is another ContentProvider with this authority when
-                     * this app was installed so this authority is null,
-                     * Ignore it as we don't have to unregister the provider.
-                     */
-                    continue;
-                }
-                String names[] = p.info.authority.split(";");
-                for (int j = 0; j < names.length; j++) {
-                    if (mProviders.get(names[j]) == p) {
-                        mProviders.remove(names[j]);
-                        if (DEBUG_REMOVE) {
-                            if (chatty)
-                                Log.d(TAG, "Unregistered content provider: " + names[j]
-                                        + ", className = " + p.info.name + ", isSyncable = "
-                                        + p.info.isSyncable);
-                        }
+                /* There was another ContentProvider with this authority when
+                 * this app was installed so this authority is null,
+                 * Ignore it as we don't have to unregister the provider.
+                 */
+                continue;
+            }
+            String names[] = p.info.authority.split(";");
+            for (int j = 0; j < names.length; j++) {
+                if (mProviders.get(names[j]) == p) {
+                    mProviders.remove(names[j]);
+                    if (DEBUG_REMOVE) {
+                        if (chatty)
+                            Log.d(TAG, "Unregistered content provider: " + names[j]
+                                    + ", className = " + p.info.name + ", isSyncable = "
+                                    + p.info.isSyncable);
                     }
                 }
+            }
+            if (chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
+                }
+                r.append(p.info.name);
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Providers: " + r);
+        }
+
+        N = pkg.services.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Service s = pkg.services.get(i);
+            mServices.removeService(s);
+            if (chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
+                }
+                r.append(s.info.name);
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Services: " + r);
+        }
+
+        N = pkg.receivers.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Activity a = pkg.receivers.get(i);
+            mReceivers.removeActivity(a, "receiver");
+            if (chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
+                }
+                r.append(a.info.name);
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Receivers: " + r);
+        }
+
+        N = pkg.activities.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Activity a = pkg.activities.get(i);
+            mActivities.removeActivity(a, "activity");
+            if (chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
+                }
+                r.append(a.info.name);
+            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Activities: " + r);
+        }
+
+        N = pkg.permissions.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Permission p = pkg.permissions.get(i);
+            BasePermission bp = mSettings.mPermissions.get(p.info.name);
+            if (bp == null) {
+                bp = mSettings.mPermissionTrees.get(p.info.name);
+            }
+            if (bp != null && bp.perm == p) {
+                bp.perm = null;
                 if (chatty) {
                     if (r == null) {
                         r = new StringBuilder(256);
@@ -4491,105 +4597,27 @@
                     r.append(p.info.name);
                 }
             }
-            if (r != null) {
-                if (DEBUG_REMOVE) Log.d(TAG, "  Providers: " + r);
-            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
+        }
 
-            N = pkg.services.size();
-            r = null;
-            for (i=0; i<N; i++) {
-                PackageParser.Service s = pkg.services.get(i);
-                mServices.removeService(s);
-                if (chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append(s.info.name);
+        N = pkg.instrumentation.size();
+        r = null;
+        for (i=0; i<N; i++) {
+            PackageParser.Instrumentation a = pkg.instrumentation.get(i);
+            mInstrumentation.remove(a.getComponentName());
+            if (chatty) {
+                if (r == null) {
+                    r = new StringBuilder(256);
+                } else {
+                    r.append(' ');
                 }
+                r.append(a.info.name);
             }
-            if (r != null) {
-                if (DEBUG_REMOVE) Log.d(TAG, "  Services: " + r);
-            }
-
-            N = pkg.receivers.size();
-            r = null;
-            for (i=0; i<N; i++) {
-                PackageParser.Activity a = pkg.receivers.get(i);
-                mReceivers.removeActivity(a, "receiver");
-                if (chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append(a.info.name);
-                }
-            }
-            if (r != null) {
-                if (DEBUG_REMOVE) Log.d(TAG, "  Receivers: " + r);
-            }
-
-            N = pkg.activities.size();
-            r = null;
-            for (i=0; i<N; i++) {
-                PackageParser.Activity a = pkg.activities.get(i);
-                mActivities.removeActivity(a, "activity");
-                if (chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append(a.info.name);
-                }
-            }
-            if (r != null) {
-                if (DEBUG_REMOVE) Log.d(TAG, "  Activities: " + r);
-            }
-
-            N = pkg.permissions.size();
-            r = null;
-            for (i=0; i<N; i++) {
-                PackageParser.Permission p = pkg.permissions.get(i);
-                BasePermission bp = mSettings.mPermissions.get(p.info.name);
-                if (bp == null) {
-                    bp = mSettings.mPermissionTrees.get(p.info.name);
-                }
-                if (bp != null && bp.perm == p) {
-                    bp.perm = null;
-                    if (chatty) {
-                        if (r == null) {
-                            r = new StringBuilder(256);
-                        } else {
-                            r.append(' ');
-                        }
-                        r.append(p.info.name);
-                    }
-                }
-            }
-            if (r != null) {
-                if (DEBUG_REMOVE) Log.d(TAG, "  Permissions: " + r);
-            }
-
-            N = pkg.instrumentation.size();
-            r = null;
-            for (i=0; i<N; i++) {
-                PackageParser.Instrumentation a = pkg.instrumentation.get(i);
-                mInstrumentation.remove(a.getComponentName());
-                if (chatty) {
-                    if (r == null) {
-                        r = new StringBuilder(256);
-                    } else {
-                        r.append(' ');
-                    }
-                    r.append(a.info.name);
-                }
-            }
-            if (r != null) {
-                if (DEBUG_REMOVE) Log.d(TAG, "  Instrumentation: " + r);
-            }
+        }
+        if (r != null) {
+            if (DEBUG_REMOVE) Log.d(TAG, "  Instrumentation: " + r);
         }
     }
 
@@ -5424,10 +5452,10 @@
 
         public void onEvent(int event, String path) {
             String removedPackage = null;
-            int removedUid = -1;
+            int removedAppId = -1;
             int[] removedUsers = null;
             String addedPackage = null;
-            int addedUid = -1;
+            int addedAppId = -1;
             int[] addedUsers = null;
 
             // TODO post a message to the handler to obtain serial ordering
@@ -5454,11 +5482,12 @@
                     return;
                 }
                 PackageParser.Package p = null;
+                PackageSetting ps = null;
                 // reader
                 synchronized (mPackages) {
                     p = mAppDirs.get(fullPathStr);
                     if (p != null) {
-                        PackageSetting ps = mSettings.mPackages.get(p.applicationInfo.packageName);
+                        ps = mSettings.mPackages.get(p.applicationInfo.packageName);
                         if (ps != null) {
                             removedUsers = ps.queryInstalledUsers(sUserManager.getUserIds(), true);
                         } else {
@@ -5468,10 +5497,10 @@
                     addedUsers = sUserManager.getUserIds();
                 }
                 if ((event&REMOVE_EVENTS) != 0) {
-                    if (p != null) {
-                        removePackageLI(p, true);
-                        removedPackage = p.applicationInfo.packageName;
-                        removedUid = p.applicationInfo.uid;
+                    if (ps != null) {
+                        removePackageLI(ps, true);
+                        removedPackage = ps.name;
+                        removedAppId = ps.appId;
                     }
                 }
 
@@ -5496,7 +5525,7 @@
                                         p.permissions.size() > 0 ? UPDATE_PERMISSIONS_ALL : 0);
                             }
                             addedPackage = p.applicationInfo.packageName;
-                            addedUid = p.applicationInfo.uid;
+                            addedAppId = UserHandle.getAppId(p.applicationInfo.uid);
                         }
                     }
                 }
@@ -5509,14 +5538,14 @@
 
             if (removedPackage != null) {
                 Bundle extras = new Bundle(1);
-                extras.putInt(Intent.EXTRA_UID, removedUid);
+                extras.putInt(Intent.EXTRA_UID, removedAppId);
                 extras.putBoolean(Intent.EXTRA_DATA_REMOVED, false);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_REMOVED, removedPackage,
                         extras, null, null, removedUsers);
             }
             if (addedPackage != null) {
                 Bundle extras = new Bundle(1);
-                extras.putInt(Intent.EXTRA_UID, addedUid);
+                extras.putInt(Intent.EXTRA_UID, addedAppId);
                 sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, addedPackage,
                         extras, null, null, addedUsers);
             }
@@ -5791,8 +5820,8 @@
      * @return verification timeout in milliseconds
      */
     private long getVerificationTimeout() {
-        return android.provider.Settings.Secure.getLong(mContext.getContentResolver(),
-                android.provider.Settings.Secure.PACKAGE_VERIFIER_TIMEOUT,
+        return android.provider.Settings.Global.getLong(mContext.getContentResolver(),
+                android.provider.Settings.Global.PACKAGE_VERIFIER_TIMEOUT,
                 DEFAULT_VERIFICATION_TIMEOUT);
     }
 
@@ -5802,8 +5831,8 @@
      * @return default verification response code
      */
     private int getDefaultVerificationResponse() {
-        return android.provider.Settings.Secure.getInt(mContext.getContentResolver(),
-                android.provider.Settings.Secure.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
+        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+                android.provider.Settings.Global.PACKAGE_VERIFIER_DEFAULT_RESPONSE,
                 DEFAULT_VERIFICATION_RESPONSE);
     }
 
@@ -5813,8 +5842,8 @@
      * @return true if verification should be performed
      */
     private boolean isVerificationEnabled() {
-        return android.provider.Settings.Secure.getInt(mContext.getContentResolver(),
-                android.provider.Settings.Secure.PACKAGE_VERIFIER_ENABLE,
+        return android.provider.Settings.Global.getInt(mContext.getContentResolver(),
+                android.provider.Settings.Global.PACKAGE_VERIFIER_ENABLE,
                 DEFAULT_VERIFY_ENABLE ? 1 : 0) == 1 ? true : false;
     }
 
@@ -7527,7 +7556,7 @@
         res.removedInfo.uid = oldPkg.applicationInfo.uid;
         res.removedInfo.removedPackage = packageName;
         // Remove existing system package
-        removePackageLI(oldPkg, true);
+        removePackageLI(oldPkgSetting, true);
         // writer
         synchronized (mPackages) {
             if (!mSettings.disableSystemPackageLPw(packageName) && deletedPackage != null) {
@@ -7566,7 +7595,7 @@
             // Re installation failed. Restore old information
             // Remove new pkg information
             if (newPackage != null) {
-                removePackageLI(newPackage, true);
+                removeInstalledPackageLI(newPackage, true);
             }
             // Add back the old system package
             scanPackageLI(oldPkg, parseFlags, SCAN_MONITOR | SCAN_UPDATE_SIGNATURE, 0, user);
@@ -7967,10 +7996,10 @@
      * make sure this flag is set for partially installed apps. If not its meaningless to
      * delete a partially installed application.
      */
-    private void removePackageDataLI(PackageParser.Package p, PackageRemovedInfo outInfo,
+    private void removePackageDataLI(PackageSetting ps, PackageRemovedInfo outInfo,
             int flags, boolean writeSettings) {
-        String packageName = p.packageName;
-        removePackageLI(p, (flags&REMOVE_CHATTY) != 0);
+        String packageName = ps.name;
+        removePackageLI(ps, (flags&REMOVE_CHATTY) != 0);
         // Retrieve object to delete permissions for shared user later on
         final PackageSetting deletedPs;
         // reader
@@ -8015,38 +8044,32 @@
     /*
      * Tries to delete system package.
      */
-    private boolean deleteSystemPackageLI(PackageParser.Package p,
+    private boolean deleteSystemPackageLI(PackageSetting newPs,
             int flags, PackageRemovedInfo outInfo, boolean writeSettings) {
-        ApplicationInfo applicationInfo = p.applicationInfo;
-        //applicable for non-partially installed applications only
-        if (applicationInfo == null) {
-            Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo.");
-            return false;
-        }
-        PackageSetting ps = null;
+        PackageSetting disabledPs = null;
         // Confirm if the system package has been updated
         // An updated system app can be deleted. This will also have to restore
         // the system pkg from system partition
         // reader
         synchronized (mPackages) {
-            ps = mSettings.getDisabledSystemPkgLPr(p.packageName);
+            disabledPs = mSettings.getDisabledSystemPkgLPr(newPs.name);
         }
-        if (ps == null) {
-            Slog.w(TAG, "Attempt to delete unknown system package "+ p.packageName);
+        if (disabledPs == null) {
+            Slog.w(TAG, "Attempt to delete unknown system package "+ newPs.name);
             return false;
         } else {
             Log.i(TAG, "Deleting system pkg from data partition");
         }
         // Delete the updated package
         outInfo.isRemovedPackageSystemUpdate = true;
-        if (ps.versionCode < p.mVersionCode) {
+        if (disabledPs.versionCode < newPs.versionCode) {
             // Delete data for downgrades
             flags &= ~PackageManager.DELETE_KEEP_DATA;
         } else {
             // Preserve data by setting flag
             flags |= PackageManager.DELETE_KEEP_DATA;
         }
-        boolean ret = deleteInstalledPackageLI(p, true, flags, outInfo,
+        boolean ret = deleteInstalledPackageLI(newPs, true, flags, outInfo,
                 writeSettings);
         if (!ret) {
             return false;
@@ -8054,17 +8077,18 @@
         // writer
         synchronized (mPackages) {
             // Reinstate the old system package
-            mSettings.enableSystemPackageLPw(p.packageName);
+            mSettings.enableSystemPackageLPw(newPs.name);
             // Remove any native libraries from the upgraded package.
-            NativeLibraryHelper.removeNativeBinariesLI(p.applicationInfo.nativeLibraryDir);
+            NativeLibraryHelper.removeNativeBinariesLI(newPs.nativeLibraryPathString);
         }
         // Install the system package
-        PackageParser.Package newPkg = scanPackageLI(ps.codePath,
+        PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath,
                 PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM,
                 SCAN_MONITOR | SCAN_NO_PATHS, 0, null);
 
         if (newPkg == null) {
-            Slog.w(TAG, "Failed to restore system package:"+p.packageName+" with error:" + mLastScanError);
+            Slog.w(TAG, "Failed to restore system package:" + newPs.name
+                    + " with error:" + mLastScanError);
             return false;
         }
         // writer
@@ -8079,28 +8103,23 @@
         return true;
     }
 
-    private boolean deleteInstalledPackageLI(PackageParser.Package p,
+    private boolean deleteInstalledPackageLI(PackageSetting ps,
             boolean deleteCodeAndResources, int flags, PackageRemovedInfo outInfo,
             boolean writeSettings) {
-        ApplicationInfo applicationInfo = p.applicationInfo;
-        if (applicationInfo == null) {
-            Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo.");
-            return false;
-        }
         if (outInfo != null) {
-            outInfo.uid = applicationInfo.uid;
+            outInfo.uid = ps.appId;
         }
 
         // Delete package data from internal structures and also remove data if flag is set
-        removePackageDataLI(p, outInfo, flags, writeSettings);
+        removePackageDataLI(ps, outInfo, flags, writeSettings);
 
         // Delete application code and resources
         if (deleteCodeAndResources) {
             // TODO can pick up from PackageSettings as well
-            int installFlags = isExternal(p) ? PackageManager.INSTALL_EXTERNAL : 0;
-            installFlags |= isForwardLocked(p) ? PackageManager.INSTALL_FORWARD_LOCK : 0;
-            outInfo.args = createInstallArgs(installFlags, applicationInfo.sourceDir,
-                    applicationInfo.publicSourceDir, applicationInfo.nativeLibraryDir);
+            int installFlags = isExternal(ps) ? PackageManager.INSTALL_EXTERNAL : 0;
+            installFlags |= isForwardLocked(ps) ? PackageManager.INSTALL_FORWARD_LOCK : 0;
+            outInfo.args = createInstallArgs(installFlags, ps.codePathString,
+                    ps.resourcePathString, ps.nativeLibraryPathString);
         }
         return true;
     }
@@ -8115,28 +8134,17 @@
             Slog.w(TAG, "Attempt to delete null packageName.");
             return false;
         }
-        PackageParser.Package p;
+        PackageSetting ps;
         boolean dataOnly = false;
         int removeUser = -1;
         int appId = -1;
         synchronized (mPackages) {
-            p = mPackages.get(packageName);
-            if (p == null) {
-                //this retrieves partially installed apps
-                dataOnly = true;
-                PackageSetting ps = mSettings.mPackages.get(packageName);
-                if (ps == null) {
-                    Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
-                    return false;
-                }
-                p = ps.pkg;
-            }
-            if (p == null) {
+            ps = mSettings.mPackages.get(packageName);
+            if (ps == null) {
                 Slog.w(TAG, "Package named '" + packageName + "' doesn't exist.");
                 return false;
             }
-            final PackageSetting ps = (PackageSetting)p.mExtras;
-            if (!isSystemApp(p) && ps != null && user != null
+            if (!isSystemApp(ps) && user != null
                     && user.getIdentifier() != UserHandle.USER_ALL) {
                 // The caller is asking that the package only be deleted for a single
                 // user.  To do this, we just mark its uninstalled state and delete
@@ -8177,25 +8185,20 @@
 
         if (dataOnly) {
             // Delete application data first
-            removePackageDataLI(p, outInfo, flags, writeSettings);
+            removePackageDataLI(ps, outInfo, flags, writeSettings);
             return true;
         }
-        // At this point the package should have ApplicationInfo associated with it
-        if (p.applicationInfo == null) {
-            Slog.w(TAG, "Package " + p.packageName + " has no applicationInfo.");
-            return false;
-        }
         boolean ret = false;
-        if (isSystemApp(p)) {
-            Log.i(TAG, "Removing system package:"+p.packageName);
+        if (isSystemApp(ps)) {
+            Log.i(TAG, "Removing system package:" + ps.name);
             // When an updated system application is deleted we delete the existing resources as well and
             // fall back to existing code in system partition
-            ret = deleteSystemPackageLI(p, flags, outInfo, writeSettings);
+            ret = deleteSystemPackageLI(ps, flags, outInfo, writeSettings);
         } else {
-            Log.i(TAG, "Removing non-system package:"+p.packageName);
+            Log.i(TAG, "Removing non-system package:" + ps.name);
             // Kill application pre-emptively especially for apps on sd.
-            killApplication(packageName, p.applicationInfo.uid);
-            ret = deleteInstalledPackageLI(p, deleteCodeAndResources, flags, outInfo,
+            killApplication(packageName, ps.appId);
+            ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags, outInfo,
                     writeSettings);
         }
         return ret;
diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java
index 5f10d44..b85353c 100644
--- a/services/java/com/android/server/pm/Settings.java
+++ b/services/java/com/android/server/pm/Settings.java
@@ -32,7 +32,6 @@
 import org.xmlpull.v1.XmlPullParserException;
 import org.xmlpull.v1.XmlSerializer;
 
-import android.app.AppGlobals;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -50,7 +49,6 @@
 import android.os.Environment;
 import android.os.FileUtils;
 import android.os.Process;
-import android.os.RemoteException;
 import android.os.UserHandle;
 import android.util.Log;
 import android.util.Slog;
@@ -1261,7 +1259,7 @@
                     final ArrayList<PackageCleanItem> pkgs = mPackagesToBeCleaned.valueAt(i);
                     for (int j=0; j<pkgs.size(); j++) {
                         serializer.startTag(null, "cleaning-package");
-                        PackageCleanItem item = pkgs.get(i);
+                        PackageCleanItem item = pkgs.get(j);
                         serializer.attribute(null, ATTR_NAME, item.packageName);
                         serializer.attribute(null, ATTR_CODE, item.andCode ? "true" : "false");
                         serializer.attribute(null, ATTR_USER, userStr);
@@ -2603,8 +2601,8 @@
                     first = false;
                     pw.print("anyDensity");
                 }
+                pw.println("]");
             }
-            pw.println("]");
             pw.print("    timeStamp=");
                 date.setTime(ps.timeStamp);
                 pw.println(sdf.format(date));
diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java
index 59d0954..fda619c 100644
--- a/services/java/com/android/server/power/PowerManagerService.java
+++ b/services/java/com/android/server/power/PowerManagerService.java
@@ -372,8 +372,8 @@
                     Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP), false, mSettingsObserver);
             resolver.registerContentObserver(Settings.System.getUriFor(
                     Settings.System.SCREEN_OFF_TIMEOUT), false, mSettingsObserver);
-            resolver.registerContentObserver(Settings.System.getUriFor(
-                    Settings.System.STAY_ON_WHILE_PLUGGED_IN), false, mSettingsObserver);
+            resolver.registerContentObserver(Settings.Global.getUriFor(
+                    Settings.Global.STAY_ON_WHILE_PLUGGED_IN), false, mSettingsObserver);
             resolver.registerContentObserver(Settings.System.getUriFor(
                     Settings.System.SCREEN_BRIGHTNESS), false, mSettingsObserver);
             resolver.registerContentObserver(Settings.System.getUriFor(
@@ -405,8 +405,8 @@
                 Settings.Secure.SCREENSAVER_ACTIVATE_ON_SLEEP, 0) != 0);
         mScreenOffTimeoutSetting = Settings.System.getInt(resolver,
                 Settings.System.SCREEN_OFF_TIMEOUT, DEFAULT_SCREEN_OFF_TIMEOUT);
-        mStayOnWhilePluggedInSetting = Settings.System.getInt(resolver,
-                Settings.System.STAY_ON_WHILE_PLUGGED_IN,
+        mStayOnWhilePluggedInSetting = Settings.Global.getInt(resolver,
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN,
                 BatteryManager.BATTERY_PLUGGED_AC);
 
         final int oldScreenBrightnessSetting = mScreenBrightnessSetting;
@@ -1585,8 +1585,8 @@
     }
 
     private void setStayOnSettingInternal(int val) {
-        Settings.System.putInt(mContext.getContentResolver(),
-                Settings.System.STAY_ON_WHILE_PLUGGED_IN, val);
+        Settings.Global.putInt(mContext.getContentResolver(),
+                Settings.Global.STAY_ON_WHILE_PLUGGED_IN, val);
     }
 
     /**
diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java
index 0ea051f..4df692b 100644
--- a/services/java/com/android/server/wm/DisplayContent.java
+++ b/services/java/com/android/server/wm/DisplayContent.java
@@ -94,10 +94,13 @@
     }
 
     DisplayInfo getDisplayInfo() {
-        // TODO: Add a listener for changes to Display and update mDisplayInfo when appropriate.
         return mDisplayInfo;
     }
 
+    public void updateDisplayInfo() {
+        mDisplay.getDisplayInfo(mDisplayInfo);
+    }
+
     public void dump(PrintWriter pw) {
         pw.print("  Display: mDisplayId="); pw.println(mDisplayId);
         pw.print("  init="); pw.print(mInitialDisplayWidth); pw.print("x");
@@ -121,7 +124,7 @@
         pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth);
         pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight);
         pw.print("  layoutNeeded="); pw.println(layoutNeeded);
-        pw.print("magnificationSpec="); pw.println(mMagnificationSpec.toString());
+        pw.print("magnificationSpec="); pw.println(mMagnificationSpec);
         pw.println();
     }
 }
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 9a0d280..18e793d 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -165,7 +165,7 @@
 /** {@hide} */
 public class WindowManagerService extends IWindowManager.Stub
         implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs,
-                DisplayManagerService.WindowManagerFuncs {
+                DisplayManagerService.WindowManagerFuncs, DisplayManager.DisplayListener {
     static final String TAG = "WindowManager";
     static final boolean DEBUG = false;
     static final boolean DEBUG_ADD_REMOVE = false;
@@ -782,9 +782,15 @@
         mLimitedAlphaCompositing = context.getResources().getBoolean(
                 com.android.internal.R.bool.config_sf_limitedAlpha);
         mDisplayManagerService = displayManager;
-        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
         mHeadless = displayManager.isHeadless();
 
+        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);
+        mDisplayManager.registerDisplayListener(this, null);
+        Display[] displays = mDisplayManager.getDisplays();
+        for (Display display : displays) {
+            createDisplayContent(display);
+        }
+
         mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy);
 
         mPowerManager = pm;
@@ -887,6 +893,12 @@
         return -1;
     }
 
+    /**
+     * Return the list of Windows from the passed token on the given Display.
+     * @param token The token with all the windows.
+     * @param displayContent The display we are interested in.
+     * @return List of windows from token that are on displayContent.
+     */
     WindowList getTokenWindowsOnDisplay(WindowToken token, DisplayContent displayContent) {
         final WindowList windowList = new WindowList();
         final int count = token.windows.size();
@@ -928,7 +940,7 @@
                         WindowState lastWindow = tokenWindowList.get(index);
                         if (atoken != null && lastWindow == atoken.startingWindow) {
                             placeWindowBefore(lastWindow, win);
-                            tokenWindowsPos = token.windows.indexOf(lastWindow) - 1;
+                            tokenWindowsPos = token.windows.indexOf(lastWindow);
                         } else {
                             int newIdx = findIdxBasedOnAppTokens(win);
                             //there is a window above this one associated with the same
@@ -937,10 +949,15 @@
                             //windows associated with this token.
                             if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) {
                                 Slog.v(TAG, "Adding window " + win + " at "
-                                        + (newIdx+1) + " of " + N);
+                                        + (newIdx + 1) + " of " + N);
                             }
-                            windows.add(newIdx+1, win);
-                            tokenWindowsPos = token.windows.indexOf(windows.get(newIdx)) + 1;
+                            windows.add(newIdx + 1, win);
+                            if (newIdx < 0) {
+                                // No window from token found on win's display.
+                                tokenWindowsPos = 0;
+                            } else {
+                                tokenWindowsPos = token.windows.indexOf(windows.get(newIdx)) + 1;
+                            }
                             mWindowsChanged = true;
                         }
                     }
@@ -1109,6 +1126,10 @@
         if (win.mAppToken != null && addToToken) {
             win.mAppToken.allAppWindows.add(win);
         }
+
+        if (windows.size() == 1) {
+            mDisplayManagerService.setDisplayHasContent(win.getDisplayId(), true);
+        }
     }
 
     /** TODO(cmautner): Is this the same as {@link WindowState#canReceiveKeys()} */
@@ -2397,6 +2418,9 @@
 
         final WindowList windows = win.getWindowList();
         windows.remove(win);
+        if (windows.isEmpty()) {
+            mDisplayManagerService.setDisplayHasContent(win.getDisplayId(), false);
+        }
         mPendingRemove.remove(win);
         mWindowsChanged = true;
         if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Final remove of window: " + win);
@@ -7183,6 +7207,10 @@
         public static final int NOTIFY_WINDOW_TRANSITION = 29;
         public static final int NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED = 30;
 
+        public static final int DO_DISPLAY_ADDED = 31;
+        public static final int DO_DISPLAY_REMOVED = 32;
+        public static final int DO_DISPLAY_CHANGED = 33;
+
         public static final int ANIMATOR_WHAT_OFFSET = 100000;
         public static final int SET_TRANSPARENT_REGION = ANIMATOR_WHAT_OFFSET + 1;
         public static final int CLEAR_PENDING_ACTIONS = ANIMATOR_WHAT_OFFSET + 2;
@@ -7620,18 +7648,21 @@
                     }
                     break;
                 }
+
                 case NOTIFY_ROTATION_CHANGED: {
                     final int displayId = msg.arg1;
                     final int rotation = msg.arg2;
                     handleNotifyRotationChanged(displayId, rotation);
                     break;
                 }
+
                 case NOTIFY_WINDOW_TRANSITION: {
                     final int transition = msg.arg1;
                     WindowInfo info = (WindowInfo) msg.obj;
                     handleNotifyWindowTranstion(transition, info);
                     break;
                 }
+
                 case NOTIFY_RECTANGLE_ON_SCREEN_REQUESTED: {
                     final int displayId = msg.arg1;
                     final boolean immediate = (msg.arg2 == 1);
@@ -7639,6 +7670,24 @@
                     handleNotifyRectangleOnScreenRequested(displayId, rectangle, immediate);
                     break;
                 }
+
+                case DO_DISPLAY_ADDED:
+                    synchronized (mWindowMap) {
+                        handleDisplayAddedLocked(msg.arg1);
+                    }
+                    break;
+
+                case DO_DISPLAY_REMOVED:
+                    synchronized (mWindowMap) {
+                        handleDisplayRemovedLocked(msg.arg1);
+                    }
+                    break;
+
+                case DO_DISPLAY_CHANGED:
+                    synchronized (mWindowMap) {
+                        handleDisplayChangedLocked(msg.arg1);
+                    }
+                    break;
             }
             if (DEBUG_WINDOW_TRACE) {
                 Slog.v(TAG, "handleMessage: exit");
@@ -8855,8 +8904,6 @@
         final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo();
         final int defaultDw = defaultInfo.logicalWidth;
         final int defaultDh = defaultInfo.logicalHeight;
-        final int defaultInnerDw = defaultInfo.appWidth;
-        final int defaultInnerDh = defaultInfo.appHeight;
 
         if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                 ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
@@ -9430,6 +9477,7 @@
         }
     }
 
+    @Override
     public void requestTraversal() {
         synchronized (mWindowMap) {
             requestTraversalLocked();
@@ -10454,7 +10502,7 @@
 
     boolean dumpWindows(PrintWriter pw, String name, String[] args,
             int opti, boolean dumpAll) {
-        ArrayList<WindowState> windows = new ArrayList<WindowState>();
+        WindowList windows = new WindowList();
         if ("visible".equals(name)) {
             synchronized(mWindowMap) {
                 final AllWindowsIterator iterator = new AllWindowsIterator(REVERSE_ITERATOR);
@@ -10662,6 +10710,14 @@
         }
     }
 
+    public void createDisplayContent(final Display display) {
+        if (display == null) {
+            throw new IllegalArgumentException("getDisplayContent: display must not be null");
+        }
+        final DisplayContent displayContent = new DisplayContent(display);
+        mDisplayContents.put(display.getDisplayId(), displayContent);
+    }
+
     public DisplayContent getDisplayContent(final int displayId) {
         DisplayContent displayContent = mDisplayContents.get(displayId);
         if (displayContent == null) {
@@ -10769,4 +10825,40 @@
     public WindowList getWindowList(final Display display) {
         return getDisplayContent(display.getDisplayId()).getWindowList();
     }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_ADDED, displayId, 0));
+    }
+
+    private void handleDisplayAddedLocked(int displayId) {
+        createDisplayContent(mDisplayManager.getDisplay(displayId));
+    }
+
+    @Override
+    public void onDisplayRemoved(int displayId) {
+        mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_REMOVED, displayId, 0));
+    }
+
+    private void handleDisplayRemovedLocked(int displayId) {
+        final DisplayContent displayContent = getDisplayContent(displayId);
+        mDisplayContents.delete(displayId);
+        WindowList windows = displayContent.getWindowList();
+        for (int i = windows.size() - 1; i >= 0; --i) {
+            final WindowState win = windows.get(i);
+            removeWindowLocked(win.mSession, win);
+        }
+    }
+
+    @Override
+    public void onDisplayChanged(int displayId) {
+        mH.sendMessage(mH.obtainMessage(H.DO_DISPLAY_CHANGED, displayId, 0));
+    }
+
+    private void handleDisplayChangedLocked(int displayId) {
+        final DisplayContent displayContent = getDisplayContent(displayId);
+        if (displayContent != null) {
+            displayContent.updateDisplayInfo();
+        }
+    }
 }
diff --git a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
index f0abb50..eed0ec8 100644
--- a/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
+++ b/tests/HwAccelerationTest/src/com/android/test/hwui/LinesActivity.java
@@ -153,6 +153,14 @@
             canvas.drawLine(10.0f, 45.0f, 20.0f, 55.0f, mSmallPaint);
             canvas.drawLine(10.0f, 60.0f, 50.0f, 60.0f, mHairLinePaint);
             canvas.restore();
+
+            canvas.save();
+            canvas.scale(10.0f, 50.0f);
+            mSmallPaint.setStrokeWidth(0.0f);
+            canvas.drawLine(20.0f, 9.0f, 30.0f, 11.0f, mSmallPaint);
+            mSmallPaint.setStrokeWidth(1.0f);
+            canvas.drawLine(30.0f, 9.0f, 40.0f, 11.0f, mSmallPaint);
+            canvas.restore();
         }
     }
 }
diff --git a/tests/RenderScriptTests/ImageProcessing/res/drawable-nodpi/city.png b/tests/RenderScriptTests/ImageProcessing/res/drawable-nodpi/city.png
deleted file mode 100644
index 856eeff..0000000
--- a/tests/RenderScriptTests/ImageProcessing/res/drawable-nodpi/city.png
+++ /dev/null
Binary files differ
diff --git a/tests/RenderScriptTests/ImageProcessing/res/drawable-nodpi/img1600x1067.jpg b/tests/RenderScriptTests/ImageProcessing/res/drawable-nodpi/img1600x1067.jpg
new file mode 100644
index 0000000..05d3ee2
--- /dev/null
+++ b/tests/RenderScriptTests/ImageProcessing/res/drawable-nodpi/img1600x1067.jpg
Binary files differ
diff --git a/tests/RenderScriptTests/ImageProcessing/res/drawable-nodpi/img640x427.jpg b/tests/RenderScriptTests/ImageProcessing/res/drawable-nodpi/img640x427.jpg
new file mode 100644
index 0000000..5bce392
--- /dev/null
+++ b/tests/RenderScriptTests/ImageProcessing/res/drawable-nodpi/img640x427.jpg
Binary files differ
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ColorMatrix.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ColorMatrix.java
index 87a2de1..2ac40a1 100644
--- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ColorMatrix.java
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ColorMatrix.java
@@ -33,9 +33,11 @@
     private ScriptC_colormatrix mScript;
     private ScriptIntrinsicColorMatrix mIntrinsic;
     private boolean mUseIntrinsic;
+    private boolean mUseGrey;
 
-    public ColorMatrix(boolean useIntrinsic) {
+    public ColorMatrix(boolean useIntrinsic, boolean useGrey) {
         mUseIntrinsic = useIntrinsic;
+        mUseGrey = useGrey;
     }
 
     public void createTest(android.content.res.Resources res) {
@@ -46,7 +48,11 @@
 
         if (mUseIntrinsic) {
             mIntrinsic = ScriptIntrinsicColorMatrix.create(mRS, Element.U8_4(mRS));
-            mIntrinsic.setColorMatrix(m);
+            if (mUseGrey) {
+                mIntrinsic.setGreyscale();
+            } else {
+                mIntrinsic.setColorMatrix(m);
+            }
         } else {
             mScript = new ScriptC_colormatrix(mRS, res, R.raw.colormatrix);
             mScript.invoke_setMatrix(m);
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
index 001dea8..37577eb 100644
--- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
@@ -180,12 +180,15 @@
             mTest = new Convolve3x3(true);
             break;
         case 19:
-            mTest = new ColorMatrix(false);
+            mTest = new ColorMatrix(false, false);
             break;
         case 20:
-            mTest = new ColorMatrix(true);
+            mTest = new ColorMatrix(true, false);
             break;
         case 21:
+            mTest = new ColorMatrix(true, true);
+            break;
+        case 22:
             mTest = new Copy();
             break;
         }
@@ -200,7 +203,7 @@
     }
 
     void setupTests() {
-        mTestNames = new String[22];
+        mTestNames = new String[23];
         mTestNames[0] = "Levels Vec3 Relaxed";
         mTestNames[1] = "Levels Vec4 Relaxed";
         mTestNames[2] = "Levels Vec3 Full";
@@ -222,7 +225,8 @@
         mTestNames[18] = "Intrinsics Convolve 3x3";
         mTestNames[19] = "ColorMatrix";
         mTestNames[20] = "Intrinsics ColorMatrix";
-        mTestNames[21] = "Copy";
+        mTestNames[21] = "Intrinsics ColorMatrix Grey";
+        mTestNames[22] = "Copy";
         mTestSpinner.setAdapter(new ArrayAdapter<String>(
             this, R.layout.spinner_layout, mTestNames));
     }
@@ -243,8 +247,8 @@
         super.onCreate(savedInstanceState);
         setContentView(R.layout.main);
 
-        mBitmapIn = loadBitmap(R.drawable.city);
-        mBitmapOut = loadBitmap(R.drawable.city);
+        mBitmapIn = loadBitmap(R.drawable.img1600x1067);
+        mBitmapOut = loadBitmap(R.drawable.img1600x1067);
 
         mSurfaceView = (SurfaceView) findViewById(R.id.surface);
 
diff --git a/tools/aapt/AaptAssets.cpp b/tools/aapt/AaptAssets.cpp
index 438a670..5b71adc 100644
--- a/tools/aapt/AaptAssets.cpp
+++ b/tools/aapt/AaptAssets.cpp
@@ -995,12 +995,12 @@
                 (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
                 | ResTable_config::LAYOUTDIR_ANY;
         return true;
-    } else if (strcmp(name, "ltr") == 0) {
+    } else if (strcmp(name, "ldltr") == 0) {
         if (out) out->screenLayout =
                 (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
                 | ResTable_config::LAYOUTDIR_LTR;
         return true;
-    } else if (strcmp(name, "rtl") == 0) {
+    } else if (strcmp(name, "ldrtl") == 0) {
         if (out) out->screenLayout =
                 (out->screenLayout&~ResTable_config::MASK_LAYOUTDIR)
                 | ResTable_config::LAYOUTDIR_RTL;