Merge "Maybe fix issue #7211766: bindService() to User u0 While u10 is..." into jb-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index c732c3e..a123620 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -20332,7 +20332,6 @@
     method public boolean isInteractive();
     method public boolean isLowProfile();
     method public boolean isScreenBright();
-    method protected deprecated void lightsOut();
     method public void onActionModeFinished(android.view.ActionMode);
     method public void onActionModeStarted(android.view.ActionMode);
     method public void onAttachedToWindow();
@@ -20360,7 +20359,7 @@
     field public static final java.lang.String ACTION_DREAMING_STARTED = "android.intent.action.DREAMING_STARTED";
     field public static final java.lang.String ACTION_DREAMING_STOPPED = "android.intent.action.DREAMING_STOPPED";
     field public static final java.lang.String CATEGORY_DREAM = "android.intent.category.DREAM";
-    field public static final java.lang.String METADATA_NAME_CONFIG_ACTIVITY = "android.service.dreams.config_activity";
+    field public static final java.lang.String DREAM_META_DATA = "android.service.dream";
   }
 
 }
diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java
index c095280..0acad75 100644
--- a/core/java/android/app/NotificationManager.java
+++ b/core/java/android/app/NotificationManager.java
@@ -124,6 +124,9 @@
         int[] idOut = new int[1];
         INotificationManager service = getService();
         String pkg = mContext.getPackageName();
+        if (notification.sound != null) {
+            notification.sound = notification.sound.getCanonicalUri();
+        }
         if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
         try {
             service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
@@ -143,6 +146,9 @@
         int[] idOut = new int[1];
         INotificationManager service = getService();
         String pkg = mContext.getPackageName();
+        if (notification.sound != null) {
+            notification.sound = notification.sound.getCanonicalUri();
+        }
         if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")");
         try {
             service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut,
diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java
index 3b990e3..cc6903d 100644
--- a/core/java/android/net/Uri.java
+++ b/core/java/android/net/Uri.java
@@ -16,10 +16,13 @@
 
 package android.net;
 
+import android.os.Environment;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.Environment.UserEnvironment;
 import android.util.Log;
 import java.io.File;
+import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.URLEncoder;
 import java.nio.charset.Charsets;
@@ -2288,4 +2291,39 @@
         builder = builder.appendEncodedPath(pathSegment);
         return builder.build();
     }
+
+    /**
+     * If this {@link Uri} is {@code file://}, then resolve and return its
+     * canonical path. Also fixes legacy emulated storage paths so they are
+     * usable across user boundaries. Should always be called from the app
+     * process before sending elsewhere.
+     *
+     * @hide
+     */
+    public Uri getCanonicalUri() {
+        if ("file".equals(getScheme())) {
+            final String canonicalPath;
+            try {
+                canonicalPath = new File(getPath()).getCanonicalPath();
+            } catch (IOException e) {
+                return this;
+            }
+
+            if (Environment.isExternalStorageEmulated()) {
+                final String legacyPath = Environment.getLegacyExternalStorageDirectory()
+                        .toString();
+
+                // Splice in user-specific path when legacy path is found
+                if (canonicalPath.startsWith(legacyPath)) {
+                    return Uri.fromFile(new File(
+                            Environment.getExternalStorageDirectory().toString(),
+                            canonicalPath.substring(legacyPath.length() + 1)));
+                }
+            }
+
+            return Uri.fromFile(new File(canonicalPath));
+        } else {
+            return this;
+        }
+    }
 }
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 5c4c036..3315566 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -31,6 +31,7 @@
     private static final String TAG = "Environment";
 
     private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
+    private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
     private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
     private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
 
@@ -134,6 +135,10 @@
             return mExternalStorage;
         }
 
+        public File getExternalStorageObbDirectory() {
+            return mExternalStorageAndroidObb;
+        }
+
         public File getExternalStoragePublicDirectory(String type) {
             return new File(mExternalStorage, type);
         }
@@ -302,6 +307,23 @@
         return new File(System.getenv(ENV_EXTERNAL_STORAGE));
     }
 
+    /** {@hide} */
+    public static File getLegacyExternalStorageObbDirectory() {
+        return buildPath(getLegacyExternalStorageDirectory(), DIRECTORY_ANDROID, "obb");
+    }
+
+    /** {@hide} */
+    public static File getEmulatedStorageSource(int userId) {
+        // /mnt/shell/emulated/0
+        return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId));
+    }
+
+    /** {@hide} */
+    public static File getEmulatedStorageObbSource() {
+        // /mnt/shell/emulated/obb
+        return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), "obb");
+    }
+
     /**
      * Standard directory in which to place any audio files that should be
      * in the regular list of music for the user.
diff --git a/core/java/android/os/UserHandle.aidl b/core/java/android/os/UserHandle.aidl
new file mode 100644
index 0000000..4892d32
--- /dev/null
+++ b/core/java/android/os/UserHandle.aidl
@@ -0,0 +1,19 @@
+/**
+ * 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.os;
+
+parcelable UserHandle;
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 0b16316..fc18617 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -489,13 +489,14 @@
              * IObbActionListener to inform it of the terminal state of the
              * call.
              */
-            public void mountObb(String filename, String key, IObbActionListener token, int nonce)
-                    throws RemoteException {
+            public void mountObb(String rawPath, String canonicalPath, String key,
+                    IObbActionListener token, int nonce) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
-                    _data.writeString(filename);
+                    _data.writeString(rawPath);
+                    _data.writeString(canonicalPath);
                     _data.writeString(key);
                     _data.writeStrongBinder((token != null ? token.asBinder() : null));
                     _data.writeInt(nonce);
@@ -514,13 +515,14 @@
              * IObbActionListener to inform it of the terminal state of the
              * call.
              */
-            public void unmountObb(String filename, boolean force, IObbActionListener token,
-                    int nonce) throws RemoteException {
+            public void unmountObb(
+                    String rawPath, boolean force, IObbActionListener token, int nonce)
+                    throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
-                    _data.writeString(filename);
+                    _data.writeString(rawPath);
                     _data.writeInt((force ? 1 : 0));
                     _data.writeStrongBinder((token != null ? token.asBinder() : null));
                     _data.writeInt(nonce);
@@ -536,13 +538,13 @@
              * Checks whether the specified Opaque Binary Blob (OBB) is mounted
              * somewhere.
              */
-            public boolean isObbMounted(String filename) throws RemoteException {
+            public boolean isObbMounted(String rawPath) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 boolean _result;
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
-                    _data.writeString(filename);
+                    _data.writeString(rawPath);
                     mRemote.transact(Stub.TRANSACTION_isObbMounted, _data, _reply, 0);
                     _reply.readException();
                     _result = 0 != _reply.readInt();
@@ -556,13 +558,13 @@
             /**
              * Gets the path to the mounted Opaque Binary Blob (OBB).
              */
-            public String getMountedObbPath(String filename) throws RemoteException {
+            public String getMountedObbPath(String rawPath) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 String _result;
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
-                    _data.writeString(filename);
+                    _data.writeString(rawPath);
                     mRemote.transact(Stub.TRANSACTION_getMountedObbPath, _data, _reply, 0);
                     _reply.readException();
                     _result = _reply.readString();
@@ -1042,15 +1044,14 @@
                 }
                 case TRANSACTION_mountObb: {
                     data.enforceInterface(DESCRIPTOR);
-                    String filename;
-                    filename = data.readString();
-                    String key;
-                    key = data.readString();
+                    final String rawPath = data.readString();
+                    final String canonicalPath = data.readString();
+                    final String key = data.readString();
                     IObbActionListener observer;
                     observer = IObbActionListener.Stub.asInterface(data.readStrongBinder());
                     int nonce;
                     nonce = data.readInt();
-                    mountObb(filename, key, observer, nonce);
+                    mountObb(rawPath, canonicalPath, key, observer, nonce);
                     reply.writeNoException();
                     return true;
                 }
@@ -1194,7 +1195,7 @@
     /**
      * Gets the path to the mounted Opaque Binary Blob (OBB).
      */
-    public String getMountedObbPath(String filename) throws RemoteException;
+    public String getMountedObbPath(String rawPath) throws RemoteException;
 
     /**
      * Gets an Array of currently known secure container IDs
@@ -1220,7 +1221,7 @@
      * Checks whether the specified Opaque Binary Blob (OBB) is mounted
      * somewhere.
      */
-    public boolean isObbMounted(String filename) throws RemoteException;
+    public boolean isObbMounted(String rawPath) throws RemoteException;
 
     /*
      * Returns true if the specified container is mounted
@@ -1243,8 +1244,8 @@
      * MountService will call back to the supplied IObbActionListener to inform
      * it of the terminal state of the call.
      */
-    public void mountObb(String filename, String key, IObbActionListener token, int nonce)
-            throws RemoteException;
+    public void mountObb(String rawPath, String canonicalPath, String key,
+            IObbActionListener token, int nonce) throws RemoteException;
 
     /*
      * Mount a secure container with the specified key and owner UID. Returns an
@@ -1287,7 +1288,7 @@
      * MountService will call back to the supplied IObbActionListener to inform
      * it of the terminal state of the call.
      */
-    public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce)
+    public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce)
             throws RemoteException;
 
     /*
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 54c8709d..862a95c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -28,6 +28,10 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import com.android.internal.util.Preconditions;
+
+import java.io.File;
+import java.io.IOException;
 import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.List;
@@ -443,25 +447,23 @@
      * That is, shared UID applications can attempt to mount any other
      * application's OBB that shares its UID.
      * 
-     * @param filename the path to the OBB file
+     * @param rawPath the path to the OBB file
      * @param key secret used to encrypt the OBB; may be <code>null</code> if no
      *            encryption was used on the OBB.
      * @param listener will receive the success or failure of the operation
      * @return whether the mount call was successfully queued or not
      */
-    public boolean mountObb(String filename, String key, OnObbStateChangeListener listener) {
-        if (filename == null) {
-            throw new IllegalArgumentException("filename cannot be null");
-        }
-
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
+    public boolean mountObb(String rawPath, String key, OnObbStateChangeListener listener) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+        Preconditions.checkNotNull(listener, "listener cannot be null");
 
         try {
+            final String canonicalPath = new File(rawPath).getCanonicalPath();
             final int nonce = mObbActionListener.addListener(listener);
-            mMountService.mountObb(filename, key, mObbActionListener, nonce);
+            mMountService.mountObb(rawPath, canonicalPath, key, mObbActionListener, nonce);
             return true;
+        } catch (IOException e) {
+            throw new IllegalArgumentException("Failed to resolve path: " + rawPath, e);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to mount OBB", e);
         }
@@ -483,24 +485,19 @@
      * application's OBB that shares its UID.
      * <p>
      * 
-     * @param filename path to the OBB file
+     * @param rawPath path to the OBB file
      * @param force whether to kill any programs using this in order to unmount
      *            it
      * @param listener will receive the success or failure of the operation
      * @return whether the unmount call was successfully queued or not
      */
-    public boolean unmountObb(String filename, boolean force, OnObbStateChangeListener listener) {
-        if (filename == null) {
-            throw new IllegalArgumentException("filename cannot be null");
-        }
-
-        if (listener == null) {
-            throw new IllegalArgumentException("listener cannot be null");
-        }
+    public boolean unmountObb(String rawPath, boolean force, OnObbStateChangeListener listener) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+        Preconditions.checkNotNull(listener, "listener cannot be null");
 
         try {
             final int nonce = mObbActionListener.addListener(listener);
-            mMountService.unmountObb(filename, force, mObbActionListener, nonce);
+            mMountService.unmountObb(rawPath, force, mObbActionListener, nonce);
             return true;
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to mount OBB", e);
@@ -512,16 +509,14 @@
     /**
      * Check whether an Opaque Binary Blob (OBB) is mounted or not.
      * 
-     * @param filename path to OBB image
+     * @param rawPath path to OBB image
      * @return true if OBB is mounted; false if not mounted or on error
      */
-    public boolean isObbMounted(String filename) {
-        if (filename == null) {
-            throw new IllegalArgumentException("filename cannot be null");
-        }
+    public boolean isObbMounted(String rawPath) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
 
         try {
-            return mMountService.isObbMounted(filename);
+            return mMountService.isObbMounted(rawPath);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to check if OBB is mounted", e);
         }
@@ -534,17 +529,15 @@
      * give you the path to where you can obtain access to the internals of the
      * OBB.
      * 
-     * @param filename path to OBB image
+     * @param rawPath path to OBB image
      * @return absolute path to mounted OBB image data or <code>null</code> if
      *         not mounted or exception encountered trying to read status
      */
-    public String getMountedObbPath(String filename) {
-        if (filename == null) {
-            throw new IllegalArgumentException("filename cannot be null");
-        }
+    public String getMountedObbPath(String rawPath) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
 
         try {
-            return mMountService.getMountedObbPath(filename);
+            return mMountService.getMountedObbPath(rawPath);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to find mounted path for OBB", e);
         }
diff --git a/core/java/android/service/dreams/Dream.java b/core/java/android/service/dreams/Dream.java
index dedfb0c..473414c 100644
--- a/core/java/android/service/dreams/Dream.java
+++ b/core/java/android/service/dreams/Dream.java
@@ -59,10 +59,10 @@
  *         <category android:name="android.intent.category.DREAM" />
  *     </intent-filter>
  *
- *     <!-- Point to configuration activity for this dream (optional) -->
+ *     <!-- Point to additional information for this dream (optional) -->
  *     <meta-data
- *         android:name="android.service.dreams.config_activity"
- *         android:value="com.example.mypackage/com.example.mypackage.MyDreamSettingsActivity" />
+ *         android:name="android.service.dream"
+ *         android:resource="@xml/my_dream" />
  * </service>
  * }
  * </pre>
@@ -81,12 +81,12 @@
             "android.intent.category.DREAM";
 
     /**
-     * Service meta-data key for declaring an optional configuration activity.
-     *
-     * @see Dream
-     * */
-    public static final String METADATA_NAME_CONFIG_ACTIVITY =
-            "android.service.dreams.config_activity";
+     * Name under which a Dream publishes information about itself.
+     * This meta-data must reference an XML resource containing
+     * a <code>&lt;{@link android.R.styleable#Dream dream}&gt;</code>
+     * tag.
+     */
+    public static final String DREAM_META_DATA = "android.service.dream";
 
     /**
      * Broadcast Action: Sent after the system starts dreaming.
@@ -361,13 +361,6 @@
         return getWindow().findViewById(id);
     }
 
-    /** FIXME remove once platform dreams are updated */
-    @Deprecated
-    protected void lightsOut() {
-        setLowProfile(true);
-        setFullscreen(true);
-    }
-
     /**
      * Marks this dream as interactive to receive input events.
      *
diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java
index da925c7..28763b3 100644
--- a/core/java/android/view/HardwareRenderer.java
+++ b/core/java/android/view/HardwareRenderer.java
@@ -21,6 +21,7 @@
 import android.graphics.Paint;
 import android.graphics.Rect;
 import android.graphics.SurfaceTexture;
+import android.opengl.EGL14;
 import android.opengl.GLUtils;
 import android.opengl.ManagedEGLContext;
 import android.os.Handler;
@@ -608,12 +609,6 @@
 
     @SuppressWarnings({"deprecation"})
     static abstract class GlRenderer extends HardwareRenderer {
-        // These values are not exposed in our EGL APIs
-        static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098;
-        static final int EGL_OPENGL_ES2_BIT = 4;
-        static final int EGL_SURFACE_TYPE = 0x3033;
-        static final int EGL_SWAP_BEHAVIOR_PRESERVED_BIT = 0x0400;        
-
         static final int SURFACE_STATE_ERROR = 0;
         static final int SURFACE_STATE_SUCCESS = 1;
         static final int SURFACE_STATE_UPDATED = 2;
@@ -953,19 +948,8 @@
                 return null;
             }
 
-            /*
-             * Before we can issue GL commands, we need to make sure
-             * the context is current and bound to a surface.
-             */
-            if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
-                throw new Surface.OutOfResourcesException("eglMakeCurrent failed "
-                        + GLUtils.getEGLErrorString(sEgl.eglGetError()));
-            }
-            
             initCaches();
 
-            enableDirtyRegions();
-
             return mEglContext.getGL();
         }
 
@@ -990,7 +974,7 @@
         abstract void initCaches();
 
         EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) {
-            int[] attribs = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
+            int[] attribs = { EGL14.EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE };
 
             EGLContext context = egl.eglCreateContext(eglDisplay, eglConfig, EGL_NO_CONTEXT,
                     mGlVersion != 0 ? attribs : null);
@@ -1066,6 +1050,14 @@
                 throw new RuntimeException("createWindowSurface failed "
                         + GLUtils.getEGLErrorString(error));
             }
+
+            if (!sEgl.eglMakeCurrent(sEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
+                throw new IllegalStateException("eglMakeCurrent failed " +
+                        GLUtils.getEGLErrorString(sEgl.eglGetError()));
+            }
+
+            enableDirtyRegions();
+
             return true;
         }
 
@@ -1430,7 +1422,7 @@
         @Override
         int[] getConfig(boolean dirtyRegions) {
             return new int[] {
-                    EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
+                    EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
                     EGL_RED_SIZE, 8,
                     EGL_GREEN_SIZE, 8,
                     EGL_BLUE_SIZE, 8,
@@ -1439,7 +1431,7 @@
                     // TODO: Find a better way to choose the stencil size
                     EGL_STENCIL_SIZE, mShowOverdraw ? GLES20Canvas.getStencilSize() : 0,
                     EGL_SURFACE_TYPE, EGL_WINDOW_BIT |
-                            (dirtyRegions ? EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
+                            (dirtyRegions ? EGL14.EGL_SWAP_BEHAVIOR_PRESERVED_BIT : 0),
                     EGL_NONE
             };
         }
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index fa03139..123ce2a 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -17,8 +17,11 @@
 package android.view;
 
 import android.content.Context;
+import android.os.SystemClock;
 import android.util.FloatMath;
 
+import java.util.Arrays;
+
 /**
  * Detects scaling transformation gestures using the supplied {@link MotionEvent}s.
  * The {@link OnScaleGestureListener} callback will notify users when a particular
@@ -139,6 +142,12 @@
     private int mSpanSlop;
     private int mMinSpan;
 
+    private float[] mTouchHistoryLastAccepted;
+    private int[] mTouchHistoryDirection;
+    private long[] mTouchHistoryLastAcceptedTime;
+
+    private static final long TOUCH_STABILIZE_TIME = 128; // ms
+
     /**
      * Consistency verifier for debugging purposes.
      */
@@ -155,6 +164,120 @@
     }
 
     /**
+     * The touchMajor/touchMinor elements of a MotionEvent can flutter/jitter on
+     * some hardware/driver combos. Smooth it out to get kinder, gentler behavior.
+     * @param ev MotionEvent to add to the ongoing history
+     */
+    private void addTouchHistory(MotionEvent ev) {
+        final long currentTime = SystemClock.uptimeMillis();
+        final int count = ev.getPointerCount();
+        for (int i = 0; i < count; i++) {
+            final int id = ev.getPointerId(i);
+            ensureTouchHistorySize(id);
+
+            final boolean hasLastAccepted = !Float.isNaN(mTouchHistoryLastAccepted[id]);
+            boolean accept = true;
+            final int historySize = ev.getHistorySize();
+            for (int h = 0; h < historySize + 1; h++) {
+                final float major;
+                final float minor;
+                if (h < historySize) {
+                    major = ev.getHistoricalTouchMajor(i, h);
+                    minor = ev.getHistoricalTouchMinor(i, h);
+                } else {
+                    major = ev.getTouchMajor(i);
+                    minor = ev.getTouchMinor(i);
+                }
+                final float avg = (major + minor) / 2;
+
+                if (hasLastAccepted) {
+                    final int directionSig = (int) Math.signum(avg - mTouchHistoryLastAccepted[id]);
+                    if (directionSig != mTouchHistoryDirection[id] ||
+                            (directionSig == 0 && mTouchHistoryDirection[id] == 0)) {
+                        mTouchHistoryDirection[id] = directionSig;
+                        final long time = h < historySize ? ev.getHistoricalEventTime(h)
+                                : ev.getEventTime();
+                        mTouchHistoryLastAcceptedTime[id] = time;
+                        accept = false;
+                    }
+                    if (currentTime - mTouchHistoryLastAcceptedTime[id] < TOUCH_STABILIZE_TIME) {
+                        accept = false;
+                    }
+                }
+            }
+
+            if (accept) {
+                float newAccepted = (ev.getTouchMajor(i) + ev.getTouchMinor(i)) / 2;
+                if (hasLastAccepted) {
+                    newAccepted = (mTouchHistoryLastAccepted[id] + newAccepted) / 2;
+                }
+                mTouchHistoryLastAccepted[id] = newAccepted;
+                mTouchHistoryDirection[id] = 0;
+                mTouchHistoryLastAcceptedTime[id] = ev.getEventTime();
+            }
+        }
+    }
+
+    /**
+     * Clear out the touch history for a given pointer id.
+     * @param id pointer id to clear
+     * @see #addTouchHistory(MotionEvent)
+     */
+    private void removeTouchHistoryForId(int id) {
+        mTouchHistoryLastAccepted[id] = Float.NaN;
+        mTouchHistoryDirection[id] = 0;
+        mTouchHistoryLastAcceptedTime[id] = 0;
+    }
+
+    /**
+     * Get the adjusted combined touchMajor/touchMinor value for a given pointer id
+     * @param id the pointer id of the data to obtain
+     * @return the adjusted major/minor value for the point at id
+     * @see #addTouchHistory(MotionEvent)
+     */
+    private float getAdjustedTouchHistory(int id) {
+        return mTouchHistoryLastAccepted[id];
+    }
+
+    /**
+     * Clear all touch history tracking. Useful in ACTION_CANCEL or ACTION_UP.
+     * @see #addTouchHistory(MotionEvent)
+     */
+    private void clearTouchHistory() {
+        Arrays.fill(mTouchHistoryLastAccepted, Float.NaN);
+        Arrays.fill(mTouchHistoryDirection, 0);
+        Arrays.fill(mTouchHistoryLastAcceptedTime, 0);
+    }
+
+    private void ensureTouchHistorySize(int id) {
+        final int requiredSize = id + 1;
+        if (mTouchHistoryLastAccepted == null || mTouchHistoryLastAccepted.length < requiredSize) {
+            final float[] newLastAccepted = new float[requiredSize];
+            final int[] newDirection = new int[requiredSize];
+            final long[] newLastAcceptedTime = new long[requiredSize];
+
+            int oldLength = 0;
+            if (mTouchHistoryLastAccepted != null) {
+                System.arraycopy(mTouchHistoryLastAccepted, 0, newLastAccepted, 0,
+                        mTouchHistoryLastAccepted.length);
+                System.arraycopy(mTouchHistoryDirection, 0, newDirection, 0,
+                        mTouchHistoryDirection.length);
+                System.arraycopy(mTouchHistoryLastAcceptedTime, 0, newLastAcceptedTime, 0,
+                        mTouchHistoryLastAcceptedTime.length);
+                oldLength = mTouchHistoryLastAccepted.length;
+            }
+
+            Arrays.fill(newLastAccepted, oldLength, newLastAccepted.length, Float.NaN);
+            Arrays.fill(newDirection, oldLength, newDirection.length, 0);
+            Arrays.fill(newLastAcceptedTime, oldLength, newLastAcceptedTime.length, 0);
+
+            mTouchHistoryLastAccepted = newLastAccepted;
+            mTouchHistoryDirection = newDirection;
+            mTouchHistoryLastAcceptedTime = newLastAcceptedTime;
+        }
+    }
+
+    /**
      * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
      * when appropriate.
      *
@@ -186,6 +309,7 @@
             }
 
             if (streamComplete) {
+                clearTouchHistory();
                 return true;
             }
         }
@@ -208,13 +332,19 @@
         final float focusX = sumX / div;
         final float focusY = sumY / div;
 
+        if (pointerUp) {
+            removeTouchHistoryForId(event.getPointerId(event.getActionIndex()));
+        } else {
+            addTouchHistory(event);
+        }
+
         // Determine average deviation from focal point
         float devSumX = 0, devSumY = 0;
         for (int i = 0; i < count; i++) {
             if (skipIndex == i) continue;
 
             // Average touch major and touch minor and convert the resulting diameter into a radius.
-            final float touchSize = (event.getTouchMajor(i) + event.getTouchMinor(i)) / 4;
+            final float touchSize = getAdjustedTouchHistory(event.getPointerId(i));
             devSumX += Math.abs(event.getX(i) - focusX) + touchSize;
             devSumY += Math.abs(event.getY(i) - focusY) + touchSize;
         }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 3be63d5..0d16dd3 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -46,13 +46,18 @@
  * 
  * <p>The surface is Z ordered so that it is behind the window holding its
  * SurfaceView; the SurfaceView punches a hole in its window to allow its
- * surface to be displayed.  The view hierarchy will take care of correctly
+ * surface to be displayed. The view hierarchy will take care of correctly
  * compositing with the Surface any siblings of the SurfaceView that would
- * normally appear on top of it.  This can be used to place overlays such as
+ * normally appear on top of it. This can be used to place overlays such as
  * buttons on top of the Surface, though note however that it can have an
  * impact on performance since a full alpha-blended composite will be performed
  * each time the Surface changes.
  * 
+ * <p> The transparent region that makes the surface visible is based on the
+ * layout positions in the view hierarchy. If the post-layout transform
+ * properties are used to draw a sibling view on top of the SurfaceView, the
+ * view may not be properly composited with the surface.
+ *
  * <p>Access to the underlying surface is provided via the SurfaceHolder interface,
  * which can be retrieved by calling {@link #getHolder}.
  * 
@@ -62,14 +67,14 @@
  * Surface is created and destroyed as the window is shown and hidden.
  * 
  * <p>One of the purposes of this class is to provide a surface in which a
- * secondary thread can render into the screen.  If you are going to use it
+ * secondary thread can render into the screen. If you are going to use it
  * this way, you need to be aware of some threading semantics:
  * 
  * <ul>
  * <li> All SurfaceView and
  * {@link SurfaceHolder.Callback SurfaceHolder.Callback} methods will be called
  * from the thread running the SurfaceView's window (typically the main thread
- * of the application).  They thus need to correctly synchronize with any
+ * of the application). They thus need to correctly synchronize with any
  * state that is also touched by the drawing thread.
  * <li> You must ensure that the drawing thread only touches the underlying
  * Surface while it is valid -- between
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index ecacaca..12eb800 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -2141,6 +2141,17 @@
      */
     static final int PFLAG2_PADDING_RESOLVED = 0x20000000;
 
+    /**
+     * Flag indicating that the start/end drawables has been resolved into left/right ones.
+     */
+    static final int PFLAG2_DRAWABLE_RESOLVED = 0x40000000;
+
+    /**
+     * Group of bits indicating that RTL properties resolution is done.
+     */
+    static final int ALL_RTL_PROPERTIES_RESOLVED = PFLAG2_LAYOUT_DIRECTION_RESOLVED |
+            PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+
     // There are a couple of flags left in mPrivateFlags2
 
     /* End of masks for mPrivateFlags2 */
@@ -3199,9 +3210,12 @@
         mResources = context != null ? context.getResources() : null;
         mViewFlags = SOUND_EFFECTS_ENABLED | HAPTIC_FEEDBACK_ENABLED;
         // Set layout and text direction defaults
-        mPrivateFlags2 = (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
+        mPrivateFlags2 =
+                (LAYOUT_DIRECTION_DEFAULT << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) |
                 (TEXT_DIRECTION_DEFAULT << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) |
+                (PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT) |
                 (TEXT_ALIGNMENT_DEFAULT << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) |
+                (PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT) |
                 (IMPORTANT_FOR_ACCESSIBILITY_DEFAULT << PFLAG2_IMPORTANT_FOR_ACCESSIBILITY_SHIFT);
         mTouchSlop = ViewConfiguration.get(context).getScaledTouchSlop();
         setOverScrollMode(OVER_SCROLL_IF_CONTENT_SCROLLS);
@@ -3419,7 +3433,8 @@
                     break;
                 case com.android.internal.R.styleable.View_layoutDirection:
                     // Clear any layout direction flags (included resolved bits) already set
-                    mPrivateFlags2 &= ~(PFLAG2_LAYOUT_DIRECTION_MASK | PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK);
+                    mPrivateFlags2 &=
+                            ~(PFLAG2_LAYOUT_DIRECTION_MASK | PFLAG2_LAYOUT_DIRECTION_RESOLVED_MASK);
                     // Set the layout direction flags depending on the value of the attribute
                     final int layoutDirection = a.getInt(attr, -1);
                     final int value = (layoutDirection != -1) ?
@@ -5772,6 +5787,8 @@
      *   {@link #LAYOUT_DIRECTION_INHERIT} or
      *   {@link #LAYOUT_DIRECTION_LOCALE}.
      * @attr ref android.R.styleable#View_layoutDirection
+     *
+     * @hide
      */
     @ViewDebug.ExportedProperty(category = "layout", mapping = {
         @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR,     to = "LTR"),
@@ -5779,7 +5796,7 @@
         @ViewDebug.IntToString(from = LAYOUT_DIRECTION_INHERIT, to = "INHERIT"),
         @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LOCALE,  to = "LOCALE")
     })
-    private int getRawLayoutDirection() {
+    public int getRawLayoutDirection() {
         return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_MASK) >> PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT;
     }
 
@@ -5787,10 +5804,16 @@
      * Set the layout direction for this view. This will propagate a reset of layout direction
      * resolution to the view's children and resolve layout direction for this view.
      *
-     * @param layoutDirection One of {@link #LAYOUT_DIRECTION_LTR},
-     *   {@link #LAYOUT_DIRECTION_RTL},
-     *   {@link #LAYOUT_DIRECTION_INHERIT} or
-     *   {@link #LAYOUT_DIRECTION_LOCALE}.
+     * @param layoutDirection the layout direction to set. Should be one of:
+     *
+     * {@link #LAYOUT_DIRECTION_LTR},
+     * {@link #LAYOUT_DIRECTION_RTL},
+     * {@link #LAYOUT_DIRECTION_INHERIT},
+     * {@link #LAYOUT_DIRECTION_LOCALE}.
+     *
+     * Resolution will be done if the value is set to LAYOUT_DIRECTION_INHERIT. The resolution
+     * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+     * will return the default {@link #LAYOUT_DIRECTION_LTR}.
      *
      * @attr ref android.R.styleable#View_layoutDirection
      */
@@ -5803,11 +5826,8 @@
             // Set the new layout direction (filtered)
             mPrivateFlags2 |=
                     ((layoutDirection << PFLAG2_LAYOUT_DIRECTION_MASK_SHIFT) & PFLAG2_LAYOUT_DIRECTION_MASK);
-            resolveRtlProperties();
-            // Notify changes
-            onRtlPropertiesChanged();
-            // ... and ask for a layout pass
-            requestLayout();
+            // We need to resolve all RTL properties as they all depend on layout direction
+            resolveRtlPropertiesIfNeeded();
         }
     }
 
@@ -5816,6 +5836,9 @@
      *
      * @return {@link #LAYOUT_DIRECTION_RTL} if the layout direction is RTL or returns
      * {@link #LAYOUT_DIRECTION_LTR} if the layout direction is not RTL.
+     *
+     * For compatibility, this will return {@link #LAYOUT_DIRECTION_LTR} if API version
+     * is lower than {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}.
      */
     @ViewDebug.ExportedProperty(category = "layout", mapping = {
         @ViewDebug.IntToString(from = LAYOUT_DIRECTION_LTR, to = "RESOLVED_DIRECTION_LTR"),
@@ -5827,12 +5850,8 @@
             mPrivateFlags2 |= PFLAG2_LAYOUT_DIRECTION_RESOLVED;
             return LAYOUT_DIRECTION_LTR;
         }
-        // The layout direction will be resolved only if needed
-        if ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) != PFLAG2_LAYOUT_DIRECTION_RESOLVED) {
-            resolveLayoutDirection();
-        }
-        return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) == PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ?
-                LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
+        return ((mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ==
+                PFLAG2_LAYOUT_DIRECTION_RESOLVED_RTL) ? LAYOUT_DIRECTION_RTL : LAYOUT_DIRECTION_LTR;
     }
 
     /**
@@ -11474,10 +11493,6 @@
 
         jumpDrawablesToCurrentState();
 
-        resolveRtlProperties();
-        // Notify changes
-        onRtlPropertiesChanged();
-
         clearAccessibilityFocus();
         if (isFocused()) {
             InputMethodManager imm = InputMethodManager.peekInstance();
@@ -11490,25 +11505,41 @@
     }
 
     /**
-     * Resolve all RTL related properties
+     * Resolve all RTL related properties.
      */
-    void resolveRtlProperties() {
-        // Order is important here: LayoutDirection MUST be resolved first...
-        resolveLayoutDirection();
+    void resolveRtlPropertiesIfNeeded() {
+        if (!needRtlPropertiesResolution()) return;
+
+        // Order is important here: LayoutDirection MUST be resolved first
+        if (!isLayoutDirectionResolved()) {
+            resolveLayoutDirection();
+            resolveLayoutParams();
+        }
         // ... then we can resolve the others properties depending on the resolved LayoutDirection.
-        resolveTextDirection();
-        resolveTextAlignment();
-        resolvePadding();
-        resolveLayoutParams();
-        resolveDrawables();
+        if (!isTextDirectionResolved()) {
+            resolveTextDirection();
+        }
+        if (!isTextAlignmentResolved()) {
+            resolveTextAlignment();
+        }
+        if (!isPaddingResolved()) {
+            resolvePadding();
+        }
+        if (!isDrawablesResolved()) {
+            resolveDrawables();
+        }
+        requestLayout();
+        invalidate(true);
+        onRtlPropertiesChanged();
     }
 
-    // Reset resolution of all RTL related properties
+    // Reset resolution of all RTL related properties.
     void resetRtlProperties() {
         resetResolvedLayoutDirection();
         resetResolvedTextDirection();
         resetResolvedTextAlignment();
         resetResolvedPadding();
+        resetResolvedDrawables();
     }
 
     /**
@@ -11538,6 +11569,13 @@
     }
 
     /**
+     * @return true if RTL properties need resolution.
+     */
+    private boolean needRtlPropertiesResolution() {
+        return (mPrivateFlags2 & ALL_RTL_PROPERTIES_RESOLVED) != ALL_RTL_PROPERTIES_RESOLVED;
+    }
+
+    /**
      * Called when any RTL property (layout direction or text direction or text alignment) has
      * been changed.
      *
@@ -11614,7 +11652,8 @@
     }
 
     /**
-     * Reset the resolved layout direction.
+     * Reset the resolved layout direction. Layout direction will be resolved during a call to
+     * {@link #onMeasure(int, int)}.
      *
      * @hide
      */
@@ -11624,6 +11663,8 @@
     }
 
     /**
+     * @return true if the layout direction is inherited.
+     *
      * @hide
      */
     public boolean isLayoutDirectionInherited() {
@@ -11631,12 +11672,19 @@
     }
 
     /**
+     * @return true if layout direction has been resolved.
+     */
+    private boolean isLayoutDirectionResolved() {
+        return (mPrivateFlags2 & PFLAG2_LAYOUT_DIRECTION_RESOLVED) == PFLAG2_LAYOUT_DIRECTION_RESOLVED;
+    }
+
+    /**
      * Return if padding has been resolved
      *
      * @hide
      */
     boolean isPaddingResolved() {
-        return (mPrivateFlags2 & PFLAG2_PADDING_RESOLVED) != 0;
+        return (mPrivateFlags2 & PFLAG2_PADDING_RESOLVED) == PFLAG2_PADDING_RESOLVED;
     }
 
     /**
@@ -14116,6 +14164,7 @@
         if (mBackground != null) {
             mBackground.setLayoutDirection(getLayoutDirection());
         }
+        mPrivateFlags2 |= PFLAG2_DRAWABLE_RESOLVED;
         onResolveDrawables(getLayoutDirection());
     }
 
@@ -14134,6 +14183,14 @@
     public void onResolveDrawables(int layoutDirection) {
     }
 
+    private void resetResolvedDrawables() {
+        mPrivateFlags2 &= ~PFLAG2_DRAWABLE_RESOLVED;
+    }
+
+    private boolean isDrawablesResolved() {
+        return (mPrivateFlags2 & PFLAG2_DRAWABLE_RESOLVED) == PFLAG2_DRAWABLE_RESOLVED;
+    }
+
     /**
      * If your view subclass is displaying its own Drawable objects, it should
      * override this function and return true for any Drawable it is
@@ -14403,6 +14460,7 @@
                 padding = new Rect();
                 sThreadLocal.set(padding);
             }
+            resetResolvedDrawables();
             background.setLayoutDirection(getLayoutDirection());
             if (background.getPadding(padding)) {
                 resetResolvedPadding();
@@ -15379,9 +15437,7 @@
             // first clears the measured dimension flag
             mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
 
-            if (!isPaddingResolved()) {
-                resolvePadding();
-            }
+            resolveRtlPropertiesIfNeeded();
 
             // measure ourselves, this should set the measured dimension flag back
             onMeasure(widthMeasureSpec, heightMeasureSpec);
@@ -16526,6 +16582,10 @@
      * {@link #TEXT_DIRECTION_LTR},
      * {@link #TEXT_DIRECTION_RTL},
      * {@link #TEXT_DIRECTION_LOCALE}
+     *
+     * Resolution will be done if the value is set to TEXT_DIRECTION_INHERIT. The resolution
+     * proceeds up the parent chain of the view to get the value. If there is no parent, then it will
+     * return the default {@link #TEXT_DIRECTION_FIRST_STRONG}.
      */
     public void setTextDirection(int textDirection) {
         if (getRawTextDirection() != textDirection) {
@@ -16534,6 +16594,8 @@
             resetResolvedTextDirection();
             // Set the new text direction
             mPrivateFlags2 |= ((textDirection << PFLAG2_TEXT_DIRECTION_MASK_SHIFT) & PFLAG2_TEXT_DIRECTION_MASK);
+            // Do resolution
+            resolveTextDirection();
             // Notify change
             onRtlPropertiesChanged();
             // Refresh
@@ -16545,11 +16607,6 @@
     /**
      * Return the resolved text direction.
      *
-     * This needs resolution if the value is TEXT_DIRECTION_INHERIT. The resolution matches what has
-     * been set by {@link #setTextDirection(int)} if it is not TEXT_DIRECTION_INHERIT, otherwise the
-     * resolution proceeds up the parent chain of the view. If there is no parent, then it will
-     * return the default {@link #TEXT_DIRECTION_FIRST_STRONG}.
-     *
      * @return the resolved text direction. Returns one of:
      *
      * {@link #TEXT_DIRECTION_FIRST_STRONG}
@@ -16559,10 +16616,6 @@
      * {@link #TEXT_DIRECTION_LOCALE}
      */
     public int getTextDirection() {
-        // The text direction will be resolved only if needed
-        if ((mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) != PFLAG2_TEXT_DIRECTION_RESOLVED) {
-            resolveTextDirection();
-        }
         return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED_MASK) >> PFLAG2_TEXT_DIRECTION_RESOLVED_MASK_SHIFT;
     }
 
@@ -16601,6 +16654,8 @@
                     } else {
                         // We cannot do the resolution if there is no parent, so use the default one
                         mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
+                        // Resolution will need to happen again later
+                        return;
                     }
                     break;
                 case TEXT_DIRECTION_FIRST_STRONG:
@@ -16639,16 +16694,21 @@
     }
 
     /**
-     * Reset resolved text direction. Text direction can be resolved with a call to
-     * getTextDirection().
+     * Reset resolved text direction. Text direction will be resolved during a call to
+     * {@link #onMeasure(int, int)}.
      *
      * @hide
      */
     public void resetResolvedTextDirection() {
+        // Reset any previous text direction resolution
         mPrivateFlags2 &= ~(PFLAG2_TEXT_DIRECTION_RESOLVED | PFLAG2_TEXT_DIRECTION_RESOLVED_MASK);
+        // Set to default value
+        mPrivateFlags2 |= PFLAG2_TEXT_DIRECTION_RESOLVED_DEFAULT;
     }
 
     /**
+     * @return true if text direction is inherited.
+     *
      * @hide
      */
     public boolean isTextDirectionInherited() {
@@ -16656,6 +16716,13 @@
     }
 
     /**
+     * @return true if text direction is resolved.
+     */
+    private boolean isTextDirectionResolved() {
+        return (mPrivateFlags2 & PFLAG2_TEXT_DIRECTION_RESOLVED) == PFLAG2_TEXT_DIRECTION_RESOLVED;
+    }
+
+    /**
      * Return the value specifying the text alignment or policy that was set with
      * {@link #setTextAlignment(int)}.
      *
@@ -16697,6 +16764,10 @@
      * {@link #TEXT_ALIGNMENT_VIEW_START},
      * {@link #TEXT_ALIGNMENT_VIEW_END}
      *
+     * Resolution will be done if the value is set to TEXT_ALIGNMENT_INHERIT. The resolution
+     * proceeds up the parent chain of the view to get the value. If there is no parent, then it
+     * will return the default {@link #TEXT_ALIGNMENT_GRAVITY}.
+     *
      * @attr ref android.R.styleable#View_textAlignment
      */
     public void setTextAlignment(int textAlignment) {
@@ -16705,7 +16776,10 @@
             mPrivateFlags2 &= ~PFLAG2_TEXT_ALIGNMENT_MASK;
             resetResolvedTextAlignment();
             // Set the new text alignment
-            mPrivateFlags2 |= ((textAlignment << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) & PFLAG2_TEXT_ALIGNMENT_MASK);
+            mPrivateFlags2 |=
+                    ((textAlignment << PFLAG2_TEXT_ALIGNMENT_MASK_SHIFT) & PFLAG2_TEXT_ALIGNMENT_MASK);
+            // Do resolution
+            resolveTextAlignment();
             // Notify change
             onRtlPropertiesChanged();
             // Refresh
@@ -16717,10 +16791,6 @@
     /**
      * Return the resolved text alignment.
      *
-     * The resolved text alignment. This needs resolution if the value is
-     * TEXT_ALIGNMENT_INHERIT. The resolution matches {@link #setTextAlignment(int)}  if it is
-     * not TEXT_ALIGNMENT_INHERIT, otherwise resolution proceeds up the parent chain of the view.
-     *
      * @return the resolved text alignment. Returns one of:
      *
      * {@link #TEXT_ALIGNMENT_GRAVITY},
@@ -16740,11 +16810,8 @@
             @ViewDebug.IntToString(from = TEXT_ALIGNMENT_VIEW_END, to = "VIEW_END")
     })
     public int getTextAlignment() {
-        // If text alignment is not resolved, then resolve it
-        if ((mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) != PFLAG2_TEXT_ALIGNMENT_RESOLVED) {
-            resolveTextAlignment();
-        }
-        return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >> PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
+        return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK) >>
+                PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK_SHIFT;
     }
 
     /**
@@ -16786,6 +16853,8 @@
                     else {
                         // We cannot do the resolution if there is no parent so use the default
                         mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
+                        // Resolution will need to happen again later
+                        return;
                     }
                     break;
                 case TEXT_ALIGNMENT_GRAVITY:
@@ -16825,16 +16894,21 @@
     }
 
     /**
-     * Reset resolved text alignment.
+     * Reset resolved text alignment. Text alignment will be resolved during a call to
+     * {@link #onMeasure(int, int)}.
      *
      * @hide
      */
     public void resetResolvedTextAlignment() {
         // Reset any previous text alignment resolution
         mPrivateFlags2 &= ~(PFLAG2_TEXT_ALIGNMENT_RESOLVED | PFLAG2_TEXT_ALIGNMENT_RESOLVED_MASK);
+        // Set to default
+        mPrivateFlags2 |= PFLAG2_TEXT_ALIGNMENT_RESOLVED_DEFAULT;
     }
 
     /**
+     * @return true if text alignment is inherited.
+     *
      * @hide
      */
     public boolean isTextAlignmentInherited() {
@@ -16842,6 +16916,13 @@
     }
 
     /**
+     * @return true if text alignment is resolved.
+     */
+    private boolean isTextAlignmentResolved() {
+        return (mPrivateFlags2 & PFLAG2_TEXT_ALIGNMENT_RESOLVED) == PFLAG2_TEXT_ALIGNMENT_RESOLVED;
+    }
+
+    /**
      * Generate a value suitable for use in {@link #setId(int)}.
      * This value will not collide with ID values generated at build time by aapt for R.id.
      *
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 34411ea..41890d6 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -3391,11 +3391,6 @@
         if (child.hasTransientState()) {
             childHasTransientStateChanged(child, true);
         }
-
-        if (child.isLayoutDirectionInherited()) {
-            child.resetResolvedLayoutDirection();
-            child.resolveRtlProperties();
-        }
     }
 
     private void addInArray(View child, int index) {
@@ -3621,7 +3616,7 @@
             childHasTransientStateChanged(view, false);
         }
 
-        view.resetResolvedLayoutDirection();
+        view.resetRtlProperties();
 
         onViewRemoved(view);
 
@@ -5261,19 +5256,92 @@
      * @hide
      */
     @Override
+    public void resolveLayoutDirection() {
+        super.resolveLayoutDirection();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isLayoutDirectionInherited()) {
+                child.resolveLayoutDirection();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void resolveTextDirection() {
+        super.resolveTextDirection();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isTextDirectionInherited()) {
+                child.resolveTextDirection();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void resolveTextAlignment() {
+        super.resolveTextAlignment();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
+            if (child.isTextAlignmentInherited()) {
+                child.resolveTextAlignment();
+            }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
     public void resetResolvedLayoutDirection() {
         super.resetResolvedLayoutDirection();
 
-        // Take care of resetting the children resolution too
         int count = getChildCount();
         for (int i = 0; i < count; i++) {
             final View child = getChildAt(i);
             if (child.isLayoutDirectionInherited()) {
                 child.resetResolvedLayoutDirection();
             }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void resetResolvedTextDirection() {
+        super.resetResolvedTextDirection();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
             if (child.isTextDirectionInherited()) {
                 child.resetResolvedTextDirection();
             }
+        }
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    public void resetResolvedTextAlignment() {
+        super.resetResolvedTextAlignment();
+
+        int count = getChildCount();
+        for (int i = 0; i < count; i++) {
+            final View child = getChildAt(i);
             if (child.isTextAlignmentInherited()) {
                 child.resetResolvedTextAlignment();
             }
diff --git a/core/java/android/widget/AnalogClock.java b/core/java/android/widget/AnalogClock.java
index 63a0870..c7da818 100644
--- a/core/java/android/widget/AnalogClock.java
+++ b/core/java/android/widget/AnalogClock.java
@@ -36,6 +36,10 @@
 /**
  * This widget display an analogic clock with two hands for hours and
  * minutes.
+ *
+ * @attr ref android.R.styleable#AnalogClock_dial
+ * @attr ref android.R.styleable#AnalogClock_hand_hour
+ * @attr ref android.R.styleable#AnalogClock_hand_minute
  */
 @RemoteView
 public class AnalogClock extends View {
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index 90f55bf..4b5dfb8 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -2166,6 +2166,8 @@
      * @param value The value to pass to the method.
      */
     public void setUri(int viewId, String methodName, Uri value) {
+        // Resolve any filesystem path before sending remotely
+        value = value.getCanonicalUri();
         addAction(new ReflectionAction(viewId, methodName, ReflectionAction.URI, value));
     }
 
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 09457cc..84e1d95 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -256,9 +256,13 @@
      * @return Whether the pattern matches the stored one.
      */
     public boolean checkPattern(List<LockPatternView.Cell> pattern) {
-        int userId = getCurrentOrCallingUserId();
+        final int userId = getCurrentOrCallingUserId();
         try {
-            return getLockSettings().checkPattern(patternToHash(pattern), userId);
+            final boolean matched = getLockSettings().checkPattern(patternToHash(pattern), userId);
+            if (matched && (userId == UserHandle.USER_OWNER)) {
+                KeyStore.getInstance().password(patternToString(pattern));
+            }
+            return matched;
         } catch (RemoteException re) {
             return true;
         }
@@ -271,9 +275,14 @@
      * @return Whether the password matches the stored one.
      */
     public boolean checkPassword(String password) {
-        int userId = getCurrentOrCallingUserId();
+        final int userId = getCurrentOrCallingUserId();
         try {
-            return getLockSettings().checkPassword(passwordToHash(password), userId);
+            final boolean matched = getLockSettings().checkPassword(passwordToHash(password),
+                    userId);
+            if (matched && (userId == UserHandle.USER_OWNER)) {
+                KeyStore.getInstance().password(password);
+            }
+            return matched;
         } catch (RemoteException re) {
             return true;
         }
diff --git a/core/jni/android_media_AudioSystem.cpp b/core/jni/android_media_AudioSystem.cpp
index f522a9a..56db116 100644
--- a/core/jni/android_media_AudioSystem.cpp
+++ b/core/jni/android_media_AudioSystem.cpp
@@ -242,6 +242,18 @@
     return (jint) AudioSystem::getDevicesForStream(static_cast <audio_stream_type_t>(stream));
 }
 
+static jint
+android_media_AudioSystem_getPrimaryOutputSamplingRate(JNIEnv *env, jobject clazz)
+{
+    return (jint) AudioSystem::getPrimaryOutputSamplingRate();
+}
+
+static jint
+android_media_AudioSystem_getPrimaryOutputFrameCount(JNIEnv *env, jobject clazz)
+{
+    return (jint) AudioSystem::getPrimaryOutputFrameCount();
+}
+
 // ----------------------------------------------------------------------------
 
 static JNINativeMethod gMethods[] = {
@@ -263,6 +275,8 @@
     {"setMasterMute",       "(Z)I",     (void *)android_media_AudioSystem_setMasterMute},
     {"getMasterMute",       "()Z",      (void *)android_media_AudioSystem_getMasterMute},
     {"getDevicesForStream", "(I)I",     (void *)android_media_AudioSystem_getDevicesForStream},
+    {"getPrimaryOutputSamplingRate", "()I", (void *)android_media_AudioSystem_getPrimaryOutputSamplingRate},
+    {"getPrimaryOutputFrameCount",   "()I", (void *)android_media_AudioSystem_getPrimaryOutputFrameCount},
 };
 
 int register_android_media_AudioSystem(JNIEnv *env)
diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml
index 0755038..9759bdc 100755
--- a/core/res/res/values/attrs.xml
+++ b/core/res/res/values/attrs.xml
@@ -5305,6 +5305,19 @@
         <attr name="description" />
     </declare-styleable>
 
+    <!-- Use <code>dream</code> as the root tag of the XML resource that
+         describes an
+         {@link android.service.dreams.Dream}, which is
+         referenced from its
+         {@link android.service.dreams.Dream#DREAM_META_DATA}
+         meta-data entry.  Described here are the attributes that can be
+         included in that tag. -->
+    <declare-styleable name="Dream">
+        <!-- Component name of an activity that allows the user to modify
+             the settings for this dream. -->
+        <attr name="settingsActivity" />
+    </declare-styleable>
+
     <!-- =============================== -->
     <!-- Accounts package class attributes -->
     <!-- =============================== -->
diff --git a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
index 39e2cf2..79d928c 100644
--- a/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
+++ b/core/tests/ConnectivityManagerTest/src/com/android/connectivitymanagertest/stress/WifiStressTest.java
@@ -219,15 +219,16 @@
     // Stress Wifi reconnection to secure net after sleep
     @LargeTest
     public void testWifiReconnectionAfterSleep() {
-        int value = Settings.System.getInt(mRunner.getContext().getContentResolver(),
-                Settings.System.WIFI_SLEEP_POLICY, -1);
-        if (value < 0) {
-            Settings.System.putInt(mRunner.getContext().getContentResolver(),
-                    Settings.System.WIFI_SLEEP_POLICY, Settings.System.WIFI_SLEEP_POLICY_DEFAULT);
+        int value = Settings.Global.getInt(mRunner.getContext().getContentResolver(),
+                Settings.Global.WIFI_SLEEP_POLICY, -1);
+        log("wifi sleep policy is: " + value);
+        if (value != Settings.Global.WIFI_SLEEP_POLICY_DEFAULT) {
+            Settings.Global.putInt(mRunner.getContext().getContentResolver(),
+                    Settings.Global.WIFI_SLEEP_POLICY, Settings.Global.WIFI_SLEEP_POLICY_DEFAULT);
             log("set wifi sleep policy to default value");
         }
-        Settings.Secure.putLong(mRunner.getContext().getContentResolver(),
-                Settings.Secure.WIFI_IDLE_MS, WIFI_IDLE_MS);
+        Settings.Global.putLong(mRunner.getContext().getContentResolver(),
+                Settings.Global.WIFI_IDLE_MS, WIFI_IDLE_MS);
 
         // Connect to a Wi-Fi network
         WifiConfiguration config = new WifiConfiguration();
diff --git a/docs/html/about/versions/jelly-bean.jd b/docs/html/about/versions/jelly-bean.jd
index 485a1bb..0583e12 100644
--- a/docs/html/about/versions/jelly-bean.jd
+++ b/docs/html/about/versions/jelly-bean.jd
@@ -303,12 +303,12 @@
 
 <p>Smart app updates is a new feature of Google Play that introduces a better way of delivering <strong>app updates</strong> to devices. When developers publish an update, Google Play now delivers only the <strong>bits that have changed</strong> to devices, rather than the entire APK. This makes the updates much lighter-weight in most cases, so they are faster to download, save the device’s battery, and conserve bandwidth usage on users’ mobile data plan. On average, a smart app update is about <strong>1/3 the size</strong> of a full APK update.</p>
 
-<h3 id="gps">Google Play services (coming soon)</h3>
+<h3 id="gps">Google Play services</h3>
 
 <p>Google Play services helps developers to <strong>integrate Google services</strong> such as authentication and Google+ into their apps delivered through Google Play.</p> 
 
-<p>Google Play services will be automatically provisioned to end user devices by Google Play, so all you need is a <strong>thin client library</strong> in your apps.</p>
+<p>Google Play services is automatically provisioned to end user devices by Google Play, so all you need is a <strong>thin client library</strong> in your apps.</p>
 
 <p>Because your app only contains the small client library, you can take advantage of these services without a big increase in download size and storage footprint. Also, Google Play will <strong>deliver regular updates</strong> to the services, without developers needing to publish app updates to take advantage of them.</p>
 
-<p>For more information about the APIs included in Google Play Services, see the <a href="http://developers.google.com/android/google-play-services/index.html">Google Play Services</a> developer page.</p>
+<p>For more information about the APIs included in Google Play Services, see the <a href="http://developers.google.com/android/google-play-services/index.html">Google Play services</a> developer page.</p>
diff --git a/docs/html/guide/google/play/services.jd b/docs/html/guide/google/play/services.jd
index 519d78b..092642c 100644
--- a/docs/html/guide/google/play/services.jd
+++ b/docs/html/guide/google/play/services.jd
@@ -1,24 +1,25 @@
 page.title=Google Play Services
-
 @jd:body
-
-<p>Google Play services is a platform that is delivered through the Google Play Store that
+<p>
+    Google Play services is a platform that is delivered through the Google Play Store that
     offers integration with Google products, such as Google+, into Android apps.
     The Google Play services framework consists of a services component
-    that runs on the device and a thin client library that you package with your app. </p>
-    
-
+    that runs on the device and a thin client library that you package with your app.
+</p>
 <div class="distribute-features col-13">
-  <ul>
-    <li style="border-top: 1px solid #F80;"><h5>Easy Authentication</h5> Your app can leverage the user's
-    existing Google account on the device without having to go through
-    tedious authentication flows. A few clicks from the user and you're set!
-    <br /> <a href="https://developers.google.com/android/google-play-services">Learn more &raquo;</a>
+<ul>
+    <li style="border-top: 1px solid #F80;"><h5>Easy Authentication</h5>
+    Your app can leverage the user's existing Google account on the device without having to go
+    through tedious authentication flows. A few clicks from the user and you're set!
+    <br/>
+    <a href="https://developers.google.com/android/google-play-services">Learn more &raquo;</a>
  </li>
-    <li style="border-top: 1px solid #F80;"><h5>Google+ Integration</h5> Google Play services makes it
-        easy for your app to integrate with Sign in with Google+, +1 button, and Google+ history.
-    <br /> <a href="https://developers.google.com/android/google-play-services">Learn more &raquo;</a>
-    </li>
-  </ul>
-  
-</div>
\ No newline at end of file
+    <li style="border-top: 1px solid #F80;"><h5>Google+ Integration</h5>
+    Google Play services makes it easy for your app to integrate with Google+ sign-in and the +1
+    button.
+    <br/>
+    <a href="https://developers.google.com/android/google-play-services">Learn more &raquo;</a>
+ </li>
+</ul>
+
+</div>
diff --git a/docs/html/guide/topics/appwidgets/index.jd b/docs/html/guide/topics/appwidgets/index.jd
index 5a4e03a..7e031d9 100644
--- a/docs/html/guide/topics/appwidgets/index.jd
+++ b/docs/html/guide/topics/appwidgets/index.jd
@@ -117,7 +117,7 @@
 application's
 <code>AndroidManifest.xml</code> file. For example:</p>
 
-<pre>
+<pre style="clear:right">
 &lt;receiver android:name="ExampleAppWidgetProvider" >
     &lt;intent-filter>
         &lt;action android:name="android.appwidget.action.APPWIDGET_UPDATE" />
@@ -815,8 +815,7 @@
 sample</a>:</p>
 
 <p>
-<img src="{@docRoot}resources/samples/images/StackWidget.png" alt="StackView
-Widget" />
+<img src="{@docRoot}resources/images/StackWidget.png" alt="" />
 </p>
 
 <p>This sample consists of a stack of 10 views, which  display the values
diff --git a/docs/html/training/basics/activity-lifecycle/recreating.jd b/docs/html/training/basics/activity-lifecycle/recreating.jd
index 8c7126a..1b88e19 100644
--- a/docs/html/training/basics/activity-lifecycle/recreating.jd
+++ b/docs/html/training/basics/activity-lifecycle/recreating.jd
@@ -54,20 +54,25 @@
 <p>By default, the system uses the {@link android.os.Bundle} instance state to save information
 about each {@link android.view.View} object in your activity layout (such as the text value entered
 into an {@link android.widget.EditText} object). So, if your activity instance is destroyed and
-recreated, the state of the layout is automatically restored to its previous state. However, your
+recreated, the state of the layout is restored to its previous state with no
+code required by you. However, your
 activity might have more state information that you'd like to restore, such as member variables that
 track the user's progress in the activity.</p>
 
-<p>In order for you to add additional data to the saved instance state for your activity, there's an
-additional callback method in the activity lifecycle that's not shown in the illustration from
-previous lessons. The method is {@link android.app.Activity#onSaveInstanceState
-onSaveInstanceState()} and the system calls it when the user is leaving your activity. When the
-system calls this method, it passes the {@link android.os.Bundle} object that will be saved in the
-event that your activity is destroyed unexpectedly so you can add additional information to it. Then
-if the system must recreate the activity instance after it was destroyed, it passes the same {@link
-android.os.Bundle} object to your activity's {@link android.app.Activity#onRestoreInstanceState
-onRestoreInstanceState()} method and also to your {@link android.app.Activity#onCreate onCreate()}
-method.</p>
+<p class="note"><strong>Note:</strong> In order for the Android system to restore the state of
+the views in your activity, <strong>each view must have a unique ID</strong>, supplied by the
+<a href="{@docRoot}reference/android/view/View.html#attr_android:id">{@code
+android:id}</a> attribute.</p>
+
+<p>To save additional data about the activity state, you must override
+the {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} callback method.
+The system calls this method when the user is leaving your activity
+and passes it the {@link android.os.Bundle} object that will be saved in the
+event that your activity is destroyed unexpectedly. If
+the system must recreate the activity instance later, it passes the same {@link
+android.os.Bundle} object to both the {@link android.app.Activity#onRestoreInstanceState
+onRestoreInstanceState()} and {@link android.app.Activity#onCreate onCreate()}
+methods.</p>
 
 <img src="{@docRoot}images/training/basics/basic-lifecycle-savestate.png" />
 <p class="img-caption"><strong>Figure 2.</strong> As the system begins to stop your activity, it
diff --git a/include/storage/IMountService.h b/include/storage/IMountService.h
index 43df7f0..c3d34d8 100644
--- a/include/storage/IMountService.h
+++ b/include/storage/IMountService.h
@@ -21,6 +21,8 @@
 #include <storage/IMountShutdownObserver.h>
 #include <storage/IObbActionListener.h>
 
+#include <utils/String8.h>
+
 #include <binder/IInterface.h>
 #include <binder/Parcel.h>
 
@@ -60,8 +62,9 @@
             String16*& containers) = 0;
     virtual void shutdown(const sp<IMountShutdownObserver>& observer) = 0;
     virtual void finishMediaUpdate() = 0;
-    virtual void mountObb(const String16& filename, const String16& key,
-            const sp<IObbActionListener>& token, const int32_t nonce) = 0;
+    virtual void mountObb(const String16& rawPath, const String16& canonicalPath,
+            const String16& key, const sp<IObbActionListener>& token,
+            const int32_t nonce) = 0;
     virtual void unmountObb(const String16& filename, const bool force,
             const sp<IObbActionListener>& token, const int32_t nonce) = 0;
     virtual bool isObbMounted(const String16& filename) = 0;
diff --git a/libs/hwui/Caches.cpp b/libs/hwui/Caches.cpp
index 898962a..f0f72f9 100644
--- a/libs/hwui/Caches.cpp
+++ b/libs/hwui/Caches.cpp
@@ -227,21 +227,29 @@
     textureCache.clearGarbage();
     pathCache.clearGarbage();
 
-    Mutex::Autolock _l(mGarbageLock);
+    Vector<DisplayList*> displayLists;
+    Vector<Layer*> layers;
 
-    size_t count = mLayerGarbage.size();
-    for (size_t i = 0; i < count; i++) {
-        Layer* layer = mLayerGarbage.itemAt(i);
-        LayerRenderer::destroyLayer(layer);
+    { // scope for the lock
+        Mutex::Autolock _l(mGarbageLock);
+        displayLists = mDisplayListGarbage;
+        layers = mLayerGarbage;
+        mDisplayListGarbage.clear();
+        mLayerGarbage.clear();
     }
-    mLayerGarbage.clear();
 
-    count = mDisplayListGarbage.size();
+    size_t count = displayLists.size();
     for (size_t i = 0; i < count; i++) {
-        DisplayList* displayList = mDisplayListGarbage.itemAt(i);
+        DisplayList* displayList = displayLists.itemAt(i);
         delete displayList;
     }
-    mDisplayListGarbage.clear();
+
+    count = layers.size();
+    for (size_t i = 0; i < count; i++) {
+        Layer* layer = layers.itemAt(i);
+        delete layer;
+    }
+    layers.clear();
 }
 
 void Caches::deleteLayerDeferred(Layer* layer) {
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp
index cd2e571..882e4bb 100644
--- a/libs/hwui/Layer.cpp
+++ b/libs/hwui/Layer.cpp
@@ -47,12 +47,16 @@
     if (mesh) delete mesh;
     if (meshIndices) delete meshIndices;
     if (colorFilter) Caches::getInstance().resourceCache.decrementRefcount(colorFilter);
+    removeFbo();
+    deleteTexture();
+}
+
+void Layer::removeFbo() {
     if (fbo) {
         LayerRenderer::flushLayer(this);
         Caches::getInstance().fboCache.put(fbo);
         fbo = 0;
     }
-    deleteTexture();
 }
 
 void Layer::setPaint(SkPaint* paint) {
@@ -69,5 +73,7 @@
     }
 }
 
+
+
 }; // namespace uirenderer
 }; // namespace android
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h
index 59c66d7..69be317 100644
--- a/libs/hwui/Layer.h
+++ b/libs/hwui/Layer.h
@@ -48,6 +48,8 @@
     Layer(const uint32_t layerWidth, const uint32_t layerHeight);
     ~Layer();
 
+    void removeFbo();
+
     /**
      * Sets this layer's region to a rectangle. Computes the appropriate
      * texture coordinates.
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index c581041..f2e7f66 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -307,6 +307,7 @@
 #if DEBUG_LAYER_RENDERER
             Caches::getInstance().layerCache.dump();
 #endif
+            layer->removeFbo();
             layer->region.clear();
         }
     }
@@ -315,6 +316,7 @@
 void LayerRenderer::destroyLayerDeferred(Layer* layer) {
     if (layer) {
         LAYER_RENDERER_LOGD("Deferring layer destruction, fbo = %d", layer->getFbo());
+
         Caches::getInstance().deleteLayerDeferred(layer);
     }
 }
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index d0d1d93..87c3a47 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -2729,13 +2729,13 @@
     mCaches.activeTexture(0);
 
     if (CC_LIKELY(!layer->region.isEmpty())) {
+        SkiaColorFilter* oldFilter = mColorFilter;
+        mColorFilter = layer->getColorFilter();
+
         if (layer->region.isRect()) {
             composeLayerRect(layer, layer->regionRect);
         } else if (layer->mesh) {
             const float a = layer->getAlpha() / 255.0f;
-            SkiaColorFilter *oldFilter = mColorFilter;
-            mColorFilter = layer->getColorFilter();
-
             setupDraw();
             setupDrawWithTexture();
             setupDrawColor(a, a, a, a);
@@ -2764,13 +2764,13 @@
 
             finishDrawTexture();
 
-            mColorFilter = oldFilter;
-
 #if DEBUG_LAYERS_AS_REGIONS
             drawRegionRects(layer->region);
 #endif
         }
 
+        mColorFilter = oldFilter;
+
         if (debugLayerUpdate) {
             drawColorRect(x, y, x + layer->layer.getWidth(), y + layer->layer.getHeight(),
                     0x7f00ff00, SkXfermode::kSrcOver_Mode);
diff --git a/libs/hwui/ResourceCache.cpp b/libs/hwui/ResourceCache.cpp
index 39e64bc..81f7b94 100644
--- a/libs/hwui/ResourceCache.cpp
+++ b/libs/hwui/ResourceCache.cpp
@@ -325,9 +325,8 @@
             }
             break;
             case kLayer: {
-                // No need to check for hasInstance, layers only exist
-                // when we have a Caches instance
-                Caches::getInstance().deleteLayerDeferred((Layer*) resource);
+                Layer* layer = (Layer*) resource;
+                Caches::getInstance().deleteLayerDeferred(layer);
             }
             break;
         }
diff --git a/libs/hwui/Texture.h b/libs/hwui/Texture.h
index 1adf2c7..03e2172 100644
--- a/libs/hwui/Texture.h
+++ b/libs/hwui/Texture.h
@@ -38,6 +38,8 @@
 
         firstFilter = true;
         firstWrap = true;
+
+        id = 0;
     }
 
     void setWrap(GLenum wrap, bool bindTexture = false, bool force = false,
diff --git a/libs/storage/IMountService.cpp b/libs/storage/IMountService.cpp
index 4ec8b25..5701678 100644
--- a/libs/storage/IMountService.cpp
+++ b/libs/storage/IMountService.cpp
@@ -433,12 +433,13 @@
         reply.readExceptionCode();
     }
 
-    void mountObb(const String16& filename, const String16& key,
+    void mountObb(const String16& rawPath, const String16& canonicalPath, const String16& key,
             const sp<IObbActionListener>& token, int32_t nonce)
     {
         Parcel data, reply;
         data.writeInterfaceToken(IMountService::getInterfaceDescriptor());
-        data.writeString16(filename);
+        data.writeString16(rawPath);
+        data.writeString16(canonicalPath);
         data.writeString16(key);
         data.writeStrongBinder(token->asBinder());
         data.writeInt32(nonce);
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 270c88f..ee17bd3 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -2474,8 +2474,16 @@
      *         or null if there is no value for that key.
      */
     public String getProperty(String key) {
-        // implementation to be written
-        return null;
+        if (PROPERTY_OUTPUT_SAMPLE_RATE.equals(key)) {
+            int outputSampleRate = AudioSystem.getPrimaryOutputSamplingRate();
+            return outputSampleRate > 0 ? Integer.toString(outputSampleRate) : null;
+        } else if (PROPERTY_OUTPUT_FRAMES_PER_BUFFER.equals(key)) {
+            int outputFramesPerBuffer = AudioSystem.getPrimaryOutputFrameCount();
+            return outputFramesPerBuffer > 0 ? Integer.toString(outputFramesPerBuffer) : null;
+        } else {
+            // null or unknown key
+            return null;
+        }
     }
 
 }
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 129e84f..2cff4ff 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -381,4 +381,9 @@
     public static native int setMasterMute(boolean mute);
     public static native boolean getMasterMute();
     public static native int getDevicesForStream(int stream);
+
+    // helpers for android.media.AudioManager.getProperty(), see description there for meaning
+    public static native int getPrimaryOutputSamplingRate();
+    public static native int getPrimaryOutputFrameCount();
+
 }
diff --git a/media/java/android/media/IRingtonePlayer.aidl b/media/java/android/media/IRingtonePlayer.aidl
index 44a0333..0872f1d 100644
--- a/media/java/android/media/IRingtonePlayer.aidl
+++ b/media/java/android/media/IRingtonePlayer.aidl
@@ -17,6 +17,7 @@
 package android.media;
 
 import android.net.Uri;
+import android.os.UserHandle;
 
 /**
  * @hide
@@ -28,6 +29,6 @@
     boolean isPlaying(IBinder token);
 
     /** Used for Notification sound playback. */
-    void playAsync(in Uri uri, boolean looping, int streamType);
+    void playAsync(in Uri uri, in UserHandle user, boolean looping, int streamType);
     void stopAsync();
 }
diff --git a/media/java/android/media/Ringtone.java b/media/java/android/media/Ringtone.java
index 23f7b55..f190eb9 100644
--- a/media/java/android/media/Ringtone.java
+++ b/media/java/android/media/Ringtone.java
@@ -225,8 +225,9 @@
                 mLocalPlayer.start();
             }
         } else if (mAllowRemote) {
+            final Uri canonicalUri = mUri.getCanonicalUri();
             try {
-                mRemotePlayer.play(mRemoteToken, mUri, mStreamType);
+                mRemotePlayer.play(mRemoteToken, canonicalUri, mStreamType);
             } catch (RemoteException e) {
                 Log.w(TAG, "Problem playing ringtone: " + e);
             }
diff --git a/native/android/storage_manager.cpp b/native/android/storage_manager.cpp
index f2f36b62..399f1ff 100644
--- a/native/android/storage_manager.cpp
+++ b/native/android/storage_manager.cpp
@@ -125,11 +125,20 @@
         }
     }
 
-    void mountObb(const char* filename, const char* key, AStorageManager_obbCallbackFunc func, void* data) {
+    void mountObb(const char* rawPath, const char* key, AStorageManager_obbCallbackFunc func,
+            void* data) {
+        // Resolve path before sending to MountService
+        char canonicalPath[PATH_MAX];
+        if (realpath(rawPath, canonicalPath) == NULL) {
+            ALOGE("mountObb failed to resolve path %s: %s", rawPath, strerror(errno));
+            return;
+        }
+
         ObbCallback* cb = registerObbCallback(func, data);
-        String16 filename16(filename);
+        String16 rawPath16(rawPath);
+        String16 canonicalPath16(canonicalPath);
         String16 key16(key);
-        mMountService->mountObb(filename16, key16, mObbActionListener, cb->nonce);
+        mMountService->mountObb(rawPath16, canonicalPath16, key16, mObbActionListener, cb->nonce);
     }
 
     void unmountObb(const char* filename, const bool force, AStorageManager_obbCallbackFunc func, void* data) {
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 4d241ed..a7294ec 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -5,6 +5,7 @@
     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
     <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    <uses-permission android:name="android.permission.ACCESS_ALL_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.WAKE_LOCK" />
 
     <uses-permission android:name="android.permission.INJECT_EVENTS" />
diff --git a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
index 3502b62..0c6e59c 100644
--- a/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
+++ b/packages/SystemUI/src/com/android/systemui/media/RingtonePlayer.java
@@ -17,6 +17,7 @@
 package com.android.systemui.media;
 
 import android.content.Context;
+import android.content.pm.PackageManager.NameNotFoundException;
 import android.media.IAudioService;
 import android.media.IRingtonePlayer;
 import android.media.Ringtone;
@@ -26,6 +27,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import com.android.systemui.SystemUI;
@@ -70,9 +72,10 @@
         private final IBinder mToken;
         private final Ringtone mRingtone;
 
-        public Client(IBinder token, Uri uri, int streamType) {
+        public Client(IBinder token, Uri uri, UserHandle user, int streamType) {
             mToken = token;
-            mRingtone = new Ringtone(mContext, false);
+
+            mRingtone = new Ringtone(getContextForUser(user), false);
             mRingtone.setStreamType(streamType);
             mRingtone.setUri(uri);
         }
@@ -90,12 +93,16 @@
     private IRingtonePlayer mCallback = new IRingtonePlayer.Stub() {
         @Override
         public void play(IBinder token, Uri uri, int streamType) throws RemoteException {
-            if (LOGD) Slog.d(TAG, "play(token=" + token + ", uri=" + uri + ")");
+            if (LOGD) {
+                Slog.d(TAG, "play(token=" + token + ", uri=" + uri + ", uid="
+                        + Binder.getCallingUid() + ")");
+            }
             Client client;
             synchronized (mClients) {
                 client = mClients.get(token);
                 if (client == null) {
-                    client = new Client(token, uri, streamType);
+                    final UserHandle user = Binder.getCallingUserHandle();
+                    client = new Client(token, uri, user, streamType);
                     token.linkToDeath(client, 0);
                     mClients.put(token, client);
                 }
@@ -131,12 +138,13 @@
         }
 
         @Override
-        public void playAsync(Uri uri, boolean looping, int streamType) {
-            if (LOGD) Slog.d(TAG, "playAsync(uri=" + uri + ")");
+        public void playAsync(Uri uri, UserHandle user, boolean looping, int streamType) {
+            if (LOGD) Slog.d(TAG, "playAsync(uri=" + uri + ", user=" + user + ")");
             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
                 throw new SecurityException("Async playback only available from system UID.");
             }
-            mAsyncPlayer.play(mContext, uri, looping, streamType);
+
+            mAsyncPlayer.play(getContextForUser(user), uri, looping, streamType);
         }
 
         @Override
@@ -149,6 +157,14 @@
         }
     };
 
+    private Context getContextForUser(UserHandle user) {
+        try {
+            return mContext.createPackageContextAsUser(mContext.getPackageName(), 0, user);
+        } catch (NameNotFoundException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
     @Override
     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         pw.println("Clients:");
diff --git a/services/java/com/android/server/BluetoothManagerService.java b/services/java/com/android/server/BluetoothManagerService.java
index aa5ae3d..ce75e35 100755
--- a/services/java/com/android/server/BluetoothManagerService.java
+++ b/services/java/com/android/server/BluetoothManagerService.java
@@ -218,8 +218,6 @@
     }
 
     public IBluetooth registerAdapter(IBluetoothManagerCallback callback){
-        mContext.enforceCallingOrSelfPermission(BLUETOOTH_PERM,
-                                                "Need BLUETOOTH permission");
         Message msg = mHandler.obtainMessage(MESSAGE_REGISTER_ADAPTER);
         msg.obj = callback;
         mHandler.sendMessage(msg);
diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java
index c685473..6952d72 100644
--- a/services/java/com/android/server/InputMethodManagerService.java
+++ b/services/java/com/android/server/InputMethodManagerService.java
@@ -34,7 +34,9 @@
 import org.xmlpull.v1.XmlSerializer;
 
 import android.app.ActivityManagerNative;
+import android.app.AppGlobals;
 import android.app.AlertDialog;
+import android.app.IUserSwitchObserver;
 import android.app.KeyguardManager;
 import android.app.Notification;
 import android.app.NotificationManager;
@@ -49,6 +51,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageManager;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
 import android.content.pm.ResolveInfo;
@@ -63,12 +66,15 @@
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.IInterface;
+import android.os.IRemoteCallback;
 import android.os.Message;
+import android.os.Process;
 import android.os.Parcel;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.os.ServiceManager;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.provider.Settings;
 import android.provider.Settings.Secure;
 import android.provider.Settings.SettingNotFoundException;
@@ -378,6 +384,8 @@
     private InputMethodInfo[] mIms;
     private int[] mSubtypeIds;
     private Locale mLastSystemLocale;
+    private final MyPackageMonitor mMyPackageMonitor = new MyPackageMonitor();
+    private final IPackageManager mIPackageManager;
 
     class SettingsObserver extends ContentObserver {
         SettingsObserver(Handler handler) {
@@ -398,37 +406,55 @@
         }
     }
 
-    class ScreenOnOffReceiver extends android.content.BroadcastReceiver {
-        @Override
-        public void onReceive(Context context, Intent intent) {
-            if (intent.getAction().equals(Intent.ACTION_SCREEN_ON)) {
-                mScreenOn = true;
-                refreshImeWindowVisibilityLocked();
-            } else if (intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
-                mScreenOn = false;
-                setImeWindowVisibilityStatusHiddenLocked();
-            } else if (intent.getAction().equals(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)) {
-                hideInputMethodMenu();
-                return;
-            } else {
-                Slog.w(TAG, "Unexpected intent " + intent);
-            }
-
+    class ImmsBroadcastReceiver extends android.content.BroadcastReceiver {
+        private void updateActive() {
             // Inform the current client of the change in active status
             if (mCurClient != null && mCurClient.client != null) {
                 executeOrSendMessage(mCurClient.client, mCaller.obtainMessageIO(
                         MSG_SET_ACTIVE, mScreenOn ? 1 : 0, mCurClient));
             }
         }
+
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final String action = intent.getAction();
+            if (Intent.ACTION_SCREEN_ON.equals(action)) {
+                mScreenOn = true;
+                refreshImeWindowVisibilityLocked();
+                updateActive();
+                return;
+            } else if (Intent.ACTION_SCREEN_OFF.equals(action)) {
+                mScreenOn = false;
+                setImeWindowVisibilityStatusHiddenLocked();
+                updateActive();
+                return;
+            } else if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) {
+                hideInputMethodMenu();
+                // No need to updateActive
+                return;
+            } else {
+                Slog.w(TAG, "Unexpected intent " + intent);
+            }
+        }
     }
 
     class MyPackageMonitor extends PackageMonitor {
-        
+        private boolean isChangingPackagesOfCurrentUser() {
+            final int userId = getChangingUserId();
+            final boolean retval = userId == mSettings.getCurrentUserId();
+            if (DEBUG) {
+                Slog.d(TAG, "--- ignore this call back from a background user: " + userId);
+            }
+            return retval;
+        }
+
         @Override
         public boolean onHandleForceStop(Intent intent, String[] packages, int uid, boolean doit) {
+            if (!isChangingPackagesOfCurrentUser()) {
+                return false;
+            }
             synchronized (mMethodMap) {
-                String curInputMethodId = Settings.Secure.getString(mContext
-                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+                String curInputMethodId = mSettings.getSelectedInputMethod();
                 final int N = mMethodList.size();
                 if (curInputMethodId != null) {
                     for (int i=0; i<N; i++) {
@@ -453,10 +479,12 @@
 
         @Override
         public void onSomePackagesChanged() {
+            if (!isChangingPackagesOfCurrentUser()) {
+                return;
+            }
             synchronized (mMethodMap) {
                 InputMethodInfo curIm = null;
-                String curInputMethodId = Settings.Secure.getString(mContext
-                        .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+                String curInputMethodId = mSettings.getSelectedInputMethod();
                 final int N = mMethodList.size();
                 if (curInputMethodId != null) {
                     for (int i=0; i<N; i++) {
@@ -489,9 +517,9 @@
                             || change == PACKAGE_PERMANENT_CHANGE) {
                         ServiceInfo si = null;
                         try {
-                            si = mContext.getPackageManager().getServiceInfo(
-                                    curIm.getComponent(), 0);
-                        } catch (PackageManager.NameNotFoundException ex) {
+                            si = mIPackageManager.getServiceInfo(
+                                    curIm.getComponent(), 0, mSettings.getCurrentUserId());
+                        } catch (RemoteException ex) {
                         }
                         if (si == null) {
                             // Uh oh, current input method is no longer around!
@@ -565,6 +593,7 @@
     }
 
     public InputMethodManagerService(Context context, WindowManagerService windowManager) {
+        mIPackageManager = AppGlobals.getPackageManager();
         mContext = context;
         mRes = context.getResources();
         mHandler = new Handler(this);
@@ -601,23 +630,44 @@
         }
         mImListManager = new InputMethodAndSubtypeListManager(context, this);
 
-        (new MyPackageMonitor()).register(mContext, null, true);
-
-        IntentFilter screenOnOffFilt = new IntentFilter();
-        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_ON);
-        screenOnOffFilt.addAction(Intent.ACTION_SCREEN_OFF);
-        screenOnOffFilt.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
-        mContext.registerReceiver(new ScreenOnOffReceiver(), screenOnOffFilt);
+        final IntentFilter broadcastFilter = new IntentFilter();
+        broadcastFilter.addAction(Intent.ACTION_SCREEN_ON);
+        broadcastFilter.addAction(Intent.ACTION_SCREEN_OFF);
+        broadcastFilter.addAction(Intent.ACTION_CLOSE_SYSTEM_DIALOGS);
+        mContext.registerReceiver(new ImmsBroadcastReceiver(), broadcastFilter);
 
         mNotificationShown = false;
+        int userId = 0;
+        try {
+            ActivityManagerNative.getDefault().registerUserSwitchObserver(
+                    new IUserSwitchObserver.Stub() {
+                        @Override
+                        public void onUserSwitching(int newUserId, IRemoteCallback reply) {
+                            switchUser(newUserId);
+                            if (reply != null) {
+                                try {
+                                    reply.sendResult(null);
+                                } catch (RemoteException e) {
+                                }
+                            }
+                        }
+
+                        @Override
+                        public void onUserSwitchComplete(int newUserId) throws RemoteException {
+                        }
+                    });
+            userId = ActivityManagerNative.getDefault().getCurrentUser().id;
+        } catch (RemoteException e) {
+            Slog.w(TAG, "Couldn't get current user ID; guessing it's 0", e);
+        }
+        mMyPackageMonitor.register(mContext, null, true);
 
         // mSettings should be created before buildInputMethodListLocked
         mSettings = new InputMethodSettings(
-                mRes, context.getContentResolver(), mMethodMap, mMethodList);
+                mRes, context.getContentResolver(), mMethodMap, mMethodList, userId);
 
         // Just checking if defaultImiId is empty or not
-        final String defaultImiId = Settings.Secure.getString(
-                mContext.getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        final String defaultImiId = mSettings.getSelectedInputMethod();
         mImeSelectedOnBoot = !TextUtils.isEmpty(defaultImiId);
 
         buildInputMethodListLocked(mMethodList, mMethodMap);
@@ -646,24 +696,6 @@
                 }, filter);
     }
 
-    private void checkCurrentLocaleChangedLocked() {
-        if (!mSystemReady) {
-            // not system ready
-            return;
-        }
-        final Locale newLocale = mRes.getConfiguration().locale;
-        if (newLocale != null && !newLocale.equals(mLastSystemLocale)) {
-            if (DEBUG) {
-                Slog.i(TAG, "Locale has been changed to " + newLocale);
-            }
-            buildInputMethodListLocked(mMethodList, mMethodMap);
-            // Reset the current ime to the proper one
-            resetDefaultImeLocked(mContext);
-            updateFromSettingsLocked();
-            mLastSystemLocale = newLocale;
-        }
-    }
-
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
         if (mCurMethodId != null && !isSystemIme(mMethodMap.get(mCurMethodId))) {
@@ -688,6 +720,52 @@
         }
     }
 
+    private void resetAllInternalStateLocked(boolean updateOnlyWhenLocaleChanged) {
+        if (!mSystemReady) {
+            // not system ready
+            return;
+        }
+        final Locale newLocale = mRes.getConfiguration().locale;
+        if (!updateOnlyWhenLocaleChanged
+                || (newLocale != null && !newLocale.equals(mLastSystemLocale))) {
+            if (!updateOnlyWhenLocaleChanged) {
+                hideCurrentInputLocked(0, null);
+                mCurMethodId = null;
+                unbindCurrentMethodLocked(true);
+            }
+            if (DEBUG) {
+                Slog.i(TAG, "Locale has been changed to " + newLocale);
+            }
+            buildInputMethodListLocked(mMethodList, mMethodMap);
+            if (!updateOnlyWhenLocaleChanged) {
+                final String selectedImiId = mSettings.getSelectedInputMethod();
+                if (TextUtils.isEmpty(selectedImiId)) {
+                    // This is the first time of the user switch and
+                    // set the current ime to the proper one.
+                    resetDefaultImeLocked(mContext);
+                }
+            }
+            updateFromSettingsLocked();
+            mLastSystemLocale = newLocale;
+            if (!updateOnlyWhenLocaleChanged) {
+                try {
+                    startInputInnerLocked();
+                } catch (RuntimeException e) {
+                    Slog.w(TAG, "Unexpected exception", e);
+                }
+            }
+        }
+    }
+
+    private void checkCurrentLocaleChangedLocked() {
+        resetAllInternalStateLocked(true);
+    }
+
+    private void switchUser(int newUserId) {
+        mSettings.setCurrentUserId(newUserId);
+        resetAllInternalStateLocked(false);
+    }
+
     private boolean isValidSystemDefaultIme(InputMethodInfo imi, Context context) {
         if (!mSystemReady) {
             return false;
@@ -748,10 +826,13 @@
 
     public void systemReady(StatusBarManagerService statusBar) {
         synchronized (mMethodMap) {
+            if (DEBUG) {
+                Slog.d(TAG, "--- systemReady");
+            }
             if (!mSystemReady) {
                 mSystemReady = true;
-                mKeyguardManager = (KeyguardManager)
-                        mContext.getSystemService(Context.KEYGUARD_SERVICE);
+                mKeyguardManager =
+                        (KeyguardManager) mContext.getSystemService(Context.KEYGUARD_SERVICE);
                 mNotificationManager = (NotificationManager)
                         mContext.getSystemService(Context.NOTIFICATION_SERVICE);
                 mStatusBar = statusBar;
@@ -802,8 +883,42 @@
         setImeWindowStatus(mCurToken, mImeWindowVis, mBackDisposition);
     }
 
+    // ---------------------------------------------------------------------------------------
+    // Check whether or not this is a valid IPC. Assumes an IPC is valid when either
+    // 1) it comes from the system process
+    // 2) the calling process' user id is identical to the current user id IMMS thinks.
+    private boolean calledFromValidUser() {
+        final int uid = Binder.getCallingUid();
+        final int userId = UserHandle.getUserId(uid);
+        if (DEBUG) {
+            Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? "
+                    + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID
+                    + " calling userId = " + userId + ", foreground user id = "
+                    + mSettings.getCurrentUserId());
+        }
+        if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) {
+            return true;
+        } else {
+            Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace());
+            return false;
+        }
+    }
+
+    private boolean bindCurrentInputMethodService(
+            Intent service, ServiceConnection conn, int flags) {
+        if (service == null || conn == null) {
+            Slog.e(TAG, "--- bind failed: service = " + service + ", conn = " + conn);
+            return false;
+        }
+        return mContext.bindService(service, conn, flags, mSettings.getCurrentUserId());
+    }
+
     @Override
     public List<InputMethodInfo> getInputMethodList() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return Collections.emptyList();
+        }
         synchronized (mMethodMap) {
             return new ArrayList<InputMethodInfo>(mMethodList);
         }
@@ -811,6 +926,10 @@
 
     @Override
     public List<InputMethodInfo> getEnabledInputMethodList() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return Collections.emptyList();
+        }
         synchronized (mMethodMap) {
             return mSettings.getEnabledInputMethodListLocked();
         }
@@ -820,7 +939,7 @@
             getExplicitlyOrImplicitlyEnabledInputMethodsAndSubtypeListLocked() {
         HashMap<InputMethodInfo, List<InputMethodSubtype>> enabledInputMethodAndSubtypes =
                 new HashMap<InputMethodInfo, List<InputMethodSubtype>>();
-        for (InputMethodInfo imi: getEnabledInputMethodList()) {
+        for (InputMethodInfo imi: mSettings.getEnabledInputMethodListLocked()) {
             enabledInputMethodAndSubtypes.put(
                     imi, getEnabledInputMethodSubtypeListLocked(imi, true));
         }
@@ -843,6 +962,10 @@
     @Override
     public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi,
             boolean allowsImplicitlySelectedSubtypes) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return Collections.emptyList();
+        }
         synchronized (mMethodMap) {
             return getEnabledInputMethodSubtypeListLocked(imi, allowsImplicitlySelectedSubtypes);
         }
@@ -851,6 +974,9 @@
     @Override
     public void addClient(IInputMethodClient client,
             IInputContext inputContext, int uid, int pid) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             mClients.put(client.asBinder(), new ClientState(client,
                     inputContext, uid, pid));
@@ -859,6 +985,9 @@
 
     @Override
     public void removeClient(IInputMethodClient client) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             mClients.remove(client.asBinder());
         }
@@ -1060,7 +1189,7 @@
                 com.android.internal.R.string.input_method_binding_label);
         mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity(
                 mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0));
-        if (mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE
+        if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
                 | Context.BIND_NOT_VISIBLE)) {
             mLastBindTime = SystemClock.uptimeMillis();
             mHaveConnection = true;
@@ -1084,6 +1213,9 @@
     @Override
     public InputBindResult startInput(IInputMethodClient client,
             IInputContext inputContext, EditorInfo attribute, int controlFlags) {
+        if (!calledFromValidUser()) {
+            return null;
+        }
         synchronized (mMethodMap) {
             final long ident = Binder.clearCallingIdentity();
             try {
@@ -1242,10 +1374,12 @@
                     if (DEBUG) Slog.d(TAG, "show a small icon for the input method");
                     CharSequence contentDescription = null;
                     try {
-                        PackageManager packageManager = mContext.getPackageManager();
+                        // Use PackageManager to load label
+                        final PackageManager packageManager = mContext.getPackageManager();
                         contentDescription = packageManager.getApplicationLabel(
-                                packageManager.getApplicationInfo(packageName, 0));
-                    } catch (NameNotFoundException nnfe) {
+                                mIPackageManager.getApplicationInfo(packageName, 0,
+                                        mSettings.getCurrentUserId()));
+                    } catch (RemoteException e) {
                         /* ignore */
                     }
                     if (mStatusBar != null) {
@@ -1309,13 +1443,14 @@
         }
     }
 
+    // Caution! This method is called in this class. Handle multi-user carefully
     @SuppressWarnings("deprecation")
     @Override
     public void setImeWindowStatus(IBinder token, int vis, int backDisposition) {
-        int uid = Binder.getCallingUid();
-        long ident = Binder.clearCallingIdentity();
+        final long ident = Binder.clearCallingIdentity();
         try {
             if (token == null || mCurToken != token) {
+                int uid = Binder.getCallingUid();
                 Slog.w(TAG, "Ignoring setImeWindowStatus of uid " + uid + " token: " + token);
                 return;
             }
@@ -1329,10 +1464,14 @@
                 final boolean iconVisibility = (vis & InputMethodService.IME_ACTIVE) != 0;
                 final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
                 if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) {
+                    // Used to load label
                     final PackageManager pm = mContext.getPackageManager();
                     final CharSequence title = mRes.getText(
                             com.android.internal.R.string.select_input_method);
                     final CharSequence imiLabel = imi.loadLabel(pm);
+                    if (DEBUG) {
+                        Slog.d(TAG, "--- imiLabel = " + imiLabel);
+                    }
                     final CharSequence summary = mCurrentSubtype != null
                             ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext,
                                         imi.getPackageName(), imi.getServiceInfo().applicationInfo),
@@ -1363,6 +1502,9 @@
 
     @Override
     public void registerSuggestionSpansForNotification(SuggestionSpan[] spans) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
             for (int i = 0; i < spans.length; ++i) {
@@ -1377,6 +1519,9 @@
 
     @Override
     public boolean notifySuggestionPicked(SuggestionSpan span, String originalString, int index) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             final InputMethodInfo targetImi = mSecureSuggestionSpans.get(span);
             // TODO: Do not send the intent if the process of the targetImi is already dead.
@@ -1404,12 +1549,10 @@
         // ENABLED_INPUT_METHODS is taking care of keeping them correctly in
         // sync, so we will never have a DEFAULT_INPUT_METHOD that is not
         // enabled.
-        String id = Settings.Secure.getString(mContext.getContentResolver(),
-                Settings.Secure.DEFAULT_INPUT_METHOD);
+        String id = mSettings.getSelectedInputMethod();
         // There is no input method selected, try to choose new applicable input method.
         if (TextUtils.isEmpty(id) && chooseNewDefaultIMELocked()) {
-            id = Settings.Secure.getString(mContext.getContentResolver(),
-                    Settings.Secure.DEFAULT_INPUT_METHOD);
+            id = mSettings.getSelectedInputMethod();
         }
         if (!TextUtils.isEmpty(id)) {
             try {
@@ -1446,7 +1589,7 @@
             } else {
                 // If subtype is null, try to find the most applicable one from
                 // getCurrentInputMethodSubtype.
-                newSubtype = getCurrentInputMethodSubtype();
+                newSubtype = getCurrentInputMethodSubtypeLocked();
             }
             if (newSubtype == null || oldSubtype == null) {
                 Slog.w(TAG, "Illegal subtype state: old subtype = " + oldSubtype
@@ -1493,6 +1636,9 @@
     @Override
     public boolean showSoftInput(IInputMethodClient client, int flags,
             ResultReceiver resultReceiver) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
@@ -1541,7 +1687,8 @@
                     resultReceiver));
             mInputShown = true;
             if (mHaveConnection && !mVisibleBound) {
-                mContext.bindService(mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
+                bindCurrentInputMethodService(
+                        mCurIntent, mVisibleConnection, Context.BIND_AUTO_CREATE);
                 mVisibleBound = true;
             }
             res = true;
@@ -1555,8 +1702,13 @@
                     SystemClock.uptimeMillis()-mLastBindTime,1);
             Slog.w(TAG, "Force disconnect/connect to the IME in showCurrentInputLocked()");
             mContext.unbindService(this);
-            mContext.bindService(mCurIntent, this, Context.BIND_AUTO_CREATE
+            bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE
                     | Context.BIND_NOT_VISIBLE);
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "Can't show input: connection = " + mHaveConnection + ", time = "
+                        + ((mLastBindTime+TIME_TO_RECONNECT) - SystemClock.uptimeMillis()));
+            }
         }
 
         return res;
@@ -1565,6 +1717,9 @@
     @Override
     public boolean hideSoftInput(IInputMethodClient client, int flags,
             ResultReceiver resultReceiver) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         int uid = Binder.getCallingUid();
         long ident = Binder.clearCallingIdentity();
         try {
@@ -1630,6 +1785,9 @@
     public InputBindResult windowGainedFocus(IInputMethodClient client, IBinder windowToken,
             int controlFlags, int softInputMode, int windowFlags,
             EditorInfo attribute, IInputContext inputContext) {
+        if (!calledFromValidUser()) {
+            return null;
+        }
         InputBindResult res = null;
         long ident = Binder.clearCallingIdentity();
         try {
@@ -1770,6 +1928,9 @@
 
     @Override
     public void showInputMethodPickerFromClient(IInputMethodClient client) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (mCurClient == null || client == null
                     || mCurClient.client.asBinder() != client.asBinder()) {
@@ -1785,11 +1946,17 @@
 
     @Override
     public void setInputMethod(IBinder token, String id) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         setInputMethodWithSubtypeId(token, id, NOT_A_SUBTYPE_ID);
     }
 
     @Override
     public void setInputMethodAndSubtype(IBinder token, String id, InputMethodSubtype subtype) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (subtype != null) {
                 setInputMethodWithSubtypeId(token, id, getSubtypeIdFromHashCode(
@@ -1803,6 +1970,9 @@
     @Override
     public void showInputMethodAndSubtypeEnablerFromClient(
             IInputMethodClient client, String inputMethodId) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (mCurClient == null || client == null
                 || mCurClient.client.asBinder() != client.asBinder()) {
@@ -1815,6 +1985,9 @@
 
     @Override
     public boolean switchToLastInputMethod(IBinder token) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
             final InputMethodInfo lastImi;
@@ -1882,6 +2055,9 @@
 
     @Override
     public boolean switchToNextInputMethod(IBinder token, boolean onlyCurrentIme) {
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod(
                     onlyCurrentIme, mMethodMap.get(mCurMethodId), mCurrentSubtype);
@@ -1895,6 +2071,9 @@
 
     @Override
     public InputMethodSubtype getLastInputMethodSubtype() {
+        if (!calledFromValidUser()) {
+            return null;
+        }
         synchronized (mMethodMap) {
             final Pair<String, String> lastIme = mSettings.getLastInputMethodAndSubtypeLocked();
             // TODO: Handle the case of the last IME with no subtypes
@@ -1917,14 +2096,22 @@
 
     @Override
     public void setAdditionalInputMethodSubtypes(String imiId, InputMethodSubtype[] subtypes) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         // By this IPC call, only a process which shares the same uid with the IME can add
         // additional input method subtypes to the IME.
         if (TextUtils.isEmpty(imiId) || subtypes == null || subtypes.length == 0) return;
         synchronized (mMethodMap) {
             final InputMethodInfo imi = mMethodMap.get(imiId);
             if (imi == null) return;
-            final PackageManager pm = mContext.getPackageManager();
-            final String[] packageInfos = pm.getPackagesForUid(Binder.getCallingUid());
+            final String[] packageInfos;
+            try {
+                packageInfos = mIPackageManager.getPackagesForUid(Binder.getCallingUid());
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to get package infos");
+                return;
+            }
             if (packageInfos != null) {
                 final int packageNum = packageInfos.length;
                 for (int i = 0; i < packageNum; ++i) {
@@ -1971,6 +2158,9 @@
 
     @Override
     public void hideMySoftInput(IBinder token, int flags) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (token == null || mCurToken != token) {
                 if (DEBUG) Slog.w(TAG, "Ignoring hideInputMethod of uid "
@@ -1988,6 +2178,9 @@
 
     @Override
     public void showMySoftInput(IBinder token, int flags) {
+        if (!calledFromValidUser()) {
+            return;
+        }
         synchronized (mMethodMap) {
             if (token == null || mCurToken != token) {
                 Slog.w(TAG, "Ignoring showMySoftInput of uid "
@@ -2224,19 +2417,22 @@
 
     void buildInputMethodListLocked(ArrayList<InputMethodInfo> list,
             HashMap<String, InputMethodInfo> map) {
+        if (DEBUG) {
+            Slog.d(TAG, "--- re-buildInputMethodList " + ", \n ------ \n" + getStackTrace());
+        }
         list.clear();
         map.clear();
 
-        PackageManager pm = mContext.getPackageManager();
+        // Use for queryIntentServicesAsUser
+        final PackageManager pm = mContext.getPackageManager();
         final Configuration config = mRes.getConfiguration();
         final boolean haveHardKeyboard = config.keyboard == Configuration.KEYBOARD_QWERTY;
-        String disabledSysImes = Settings.Secure.getString(mContext.getContentResolver(),
-                Secure.DISABLED_SYSTEM_INPUT_METHODS);
+        String disabledSysImes = mSettings.getDisabledSystemInputMethods();
         if (disabledSysImes == null) disabledSysImes = "";
 
-        List<ResolveInfo> services = pm.queryIntentServices(
+        final List<ResolveInfo> services = pm.queryIntentServicesAsUser(
                 new Intent(InputMethod.SERVICE_INTERFACE),
-                PackageManager.GET_META_DATA);
+                PackageManager.GET_META_DATA, mSettings.getCurrentUserId());
 
         final HashMap<String, List<InputMethodSubtype>> additionalSubtypes =
                 mFileManager.getAllAdditionalInputMethodSubtypes();
@@ -2279,8 +2475,7 @@
             }
         }
 
-        final String defaultImiId = Settings.Secure.getString(mContext
-                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        final String defaultImiId = mSettings.getSelectedInputMethod();
         if (!TextUtils.isEmpty(defaultImiId)) {
             if (!map.containsKey(defaultImiId)) {
                 Slog.w(TAG, "Default IME is uninstalled. Choose new default IME.");
@@ -2331,11 +2526,9 @@
         if (DEBUG) Slog.v(TAG, "Show switching menu");
 
         final Context context = mContext;
-        final PackageManager pm = context.getPackageManager();
         final boolean isScreenLocked = isScreenLocked();
 
-        final String lastInputMethodId = Settings.Secure.getString(context
-                .getContentResolver(), Settings.Secure.DEFAULT_INPUT_METHOD);
+        final String lastInputMethodId = mSettings.getSelectedInputMethod();
         int lastInputMethodSubtypeId = getSelectedInputMethodSubtypeId(lastInputMethodId);
         if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
 
@@ -2353,7 +2546,7 @@
                             showSubtypes, mInputShown, isScreenLocked);
 
             if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
-                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtype();
+                final InputMethodSubtype currentSubtype = getCurrentInputMethodSubtypeLocked();
                 if (currentSubtype != null) {
                     final InputMethodInfo currentImi = mMethodMap.get(mCurMethodId);
                     lastInputMethodSubtypeId =
@@ -2582,6 +2775,10 @@
 
     @Override
     public boolean setInputMethodEnabled(String id, boolean enabled) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             if (mContext.checkCallingOrSelfPermission(
                     android.Manifest.permission.WRITE_SECURE_SETTINGS)
@@ -2626,8 +2823,7 @@
             if (mSettings.buildAndPutEnabledInputMethodsStrRemovingIdLocked(
                     builder, enabledInputMethodsList, id)) {
                 // Disabled input method is currently selected, switch to another one.
-                String selId = Settings.Secure.getString(mContext.getContentResolver(),
-                        Settings.Secure.DEFAULT_INPUT_METHOD);
+                final String selId = mSettings.getSelectedInputMethod();
                 if (id.equals(selId) && !chooseNewDefaultIMELocked()) {
                     Slog.i(TAG, "Can't find new IME, unsetting the current input method.");
                     resetSelectedInputMethodAndSubtypeLocked("");
@@ -2674,7 +2870,7 @@
             } else {
                 mSettings.putSelectedSubtype(NOT_A_SUBTYPE_ID);
                 // If the subtype is not specified, choose the most applicable one
-                mCurrentSubtype = getCurrentInputMethodSubtype();
+                mCurrentSubtype = getCurrentInputMethodSubtypeLocked();
             }
         }
 
@@ -2716,14 +2912,8 @@
         if (imi == null) {
             return NOT_A_SUBTYPE_ID;
         }
-        int subtypeId;
-        try {
-            subtypeId = Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE);
-        } catch (SettingNotFoundException e) {
-            return NOT_A_SUBTYPE_ID;
-        }
-        return getSubtypeIdFromHashCode(imi, subtypeId);
+        final int subtypeHashCode = mSettings.getSelectedInputMethodSubtypeHashCode();
+        return getSubtypeIdFromHashCode(imi, subtypeHashCode);
     }
 
     private static boolean isValidSubtypeId(InputMethodInfo imi, int subtypeHashCode) {
@@ -2886,7 +3076,7 @@
             }
             InputMethodSubtype subtype = null;
             final List<InputMethodSubtype> enabledSubtypes =
-                    getEnabledInputMethodSubtypeList(imi, true);
+                    getEnabledInputMethodSubtypeListLocked(imi, true);
             // 1. Search by the current subtype's locale from enabledSubtypes.
             if (mCurrentSubtype != null) {
                 subtype = findLastResortApplicableSubtypeLocked(
@@ -2955,49 +3145,53 @@
      */
     @Override
     public InputMethodSubtype getCurrentInputMethodSubtype() {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return null;
+        }
+        synchronized (mMethodMap) {
+            return getCurrentInputMethodSubtypeLocked();
+        }
+    }
+
+    private InputMethodSubtype getCurrentInputMethodSubtypeLocked() {
         if (mCurMethodId == null) {
             return null;
         }
-        boolean subtypeIsSelected = false;
-        try {
-            subtypeIsSelected = Settings.Secure.getInt(mContext.getContentResolver(),
-                    Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE) != NOT_A_SUBTYPE_ID;
-        } catch (SettingNotFoundException e) {
+        final boolean subtypeIsSelected =
+                mSettings.getSelectedInputMethodSubtypeHashCode() != NOT_A_SUBTYPE_ID;
+        final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
+        if (imi == null || imi.getSubtypeCount() == 0) {
+            return null;
         }
-        synchronized (mMethodMap) {
-            final InputMethodInfo imi = mMethodMap.get(mCurMethodId);
-            if (imi == null || imi.getSubtypeCount() == 0) {
-                return null;
-            }
-            if (!subtypeIsSelected || mCurrentSubtype == null
-                    || !isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
-                int subtypeId = getSelectedInputMethodSubtypeId(mCurMethodId);
-                if (subtypeId == NOT_A_SUBTYPE_ID) {
-                    // If there are no selected subtypes, the framework will try to find
-                    // the most applicable subtype from explicitly or implicitly enabled
-                    // subtypes.
-                    List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
-                            getEnabledInputMethodSubtypeList(imi, true);
-                    // If there is only one explicitly or implicitly enabled subtype,
-                    // just returns it.
-                    if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
-                        mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
-                    } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
+        if (!subtypeIsSelected || mCurrentSubtype == null
+                || !isValidSubtypeId(imi, mCurrentSubtype.hashCode())) {
+            int subtypeId = getSelectedInputMethodSubtypeId(mCurMethodId);
+            if (subtypeId == NOT_A_SUBTYPE_ID) {
+                // If there are no selected subtypes, the framework will try to find
+                // the most applicable subtype from explicitly or implicitly enabled
+                // subtypes.
+                List<InputMethodSubtype> explicitlyOrImplicitlyEnabledSubtypes =
+                        getEnabledInputMethodSubtypeListLocked(imi, true);
+                // If there is only one explicitly or implicitly enabled subtype,
+                // just returns it.
+                if (explicitlyOrImplicitlyEnabledSubtypes.size() == 1) {
+                    mCurrentSubtype = explicitlyOrImplicitlyEnabledSubtypes.get(0);
+                } else if (explicitlyOrImplicitlyEnabledSubtypes.size() > 1) {
+                    mCurrentSubtype = findLastResortApplicableSubtypeLocked(
+                            mRes, explicitlyOrImplicitlyEnabledSubtypes,
+                            SUBTYPE_MODE_KEYBOARD, null, true);
+                    if (mCurrentSubtype == null) {
                         mCurrentSubtype = findLastResortApplicableSubtypeLocked(
-                                mRes, explicitlyOrImplicitlyEnabledSubtypes,
-                                SUBTYPE_MODE_KEYBOARD, null, true);
-                        if (mCurrentSubtype == null) {
-                            mCurrentSubtype = findLastResortApplicableSubtypeLocked(
-                                    mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
-                                    true);
-                        }
+                                mRes, explicitlyOrImplicitlyEnabledSubtypes, null, null,
+                                true);
                     }
-                } else {
-                    mCurrentSubtype = getSubtypes(imi).get(subtypeId);
                 }
+            } else {
+                mCurrentSubtype = getSubtypes(imi).get(subtypeId);
             }
-            return mCurrentSubtype;
         }
+        return mCurrentSubtype;
     }
 
     private void addShortcutInputMethodAndSubtypes(InputMethodInfo imi,
@@ -3042,6 +3236,10 @@
 
     @Override
     public boolean setCurrentInputMethodSubtype(InputMethodSubtype subtype) {
+        // TODO: Make this work even for non-current users?
+        if (!calledFromValidUser()) {
+            return false;
+        }
         synchronized (mMethodMap) {
             if (subtype != null && mCurMethodId != null) {
                 InputMethodInfo imi = mMethodMap.get(mCurMethodId);
@@ -3057,6 +3255,7 @@
 
     private static class InputMethodAndSubtypeListManager {
         private final Context mContext;
+        // Used to load label
         private final PackageManager mPm;
         private final InputMethodManagerService mImms;
         private final String mSystemLocaleStr;
@@ -3193,6 +3392,7 @@
         private final ArrayList<InputMethodInfo> mMethodList;
 
         private String mEnabledInputMethodsStrCache;
+        private int mCurrentUserId;
 
         private static void buildEnabledInputMethodsSettingString(
                 StringBuilder builder, Pair<String, ArrayList<String>> pair) {
@@ -3208,13 +3408,24 @@
 
         public InputMethodSettings(
                 Resources res, ContentResolver resolver,
-                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList) {
+                HashMap<String, InputMethodInfo> methodMap, ArrayList<InputMethodInfo> methodList,
+                int userId) {
+            setCurrentUserId(userId);
             mRes = res;
             mResolver = resolver;
             mMethodMap = methodMap;
             mMethodList = methodList;
         }
 
+        public void setCurrentUserId(int userId) {
+            if (DEBUG) {
+                Slog.d(TAG, "--- Swtich the current user from " + mCurrentUserId + " to "
+                        + userId + ", new ime = " + getSelectedInputMethod());
+            }
+            // IMMS settings are kept per user, so keep track of current user
+            mCurrentUserId = userId;
+        }
+
         public List<InputMethodInfo> getEnabledInputMethodListLocked() {
             return createEnabledInputMethodListLocked(
                     getEnabledInputMethodsAndSubtypeListLocked());
@@ -3363,15 +3574,20 @@
         }
 
         private void putEnabledInputMethodsStr(String str) {
-            Settings.Secure.putString(mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str);
+            Settings.Secure.putStringForUser(
+                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS, str, mCurrentUserId);
             mEnabledInputMethodsStrCache = str;
+            if (DEBUG) {
+                Slog.d(TAG, "putEnabledInputMethodStr: " + str);
+            }
         }
 
         private String getEnabledInputMethodsStr() {
-            mEnabledInputMethodsStrCache = Settings.Secure.getString(
-                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS);
+            mEnabledInputMethodsStrCache = Settings.Secure.getStringForUser(
+                    mResolver, Settings.Secure.ENABLED_INPUT_METHODS, mCurrentUserId);
             if (DEBUG) {
-                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache);
+                Slog.d(TAG, "getEnabledInputMethodsStr: " + mEnabledInputMethodsStrCache
+                        + ", " + mCurrentUserId);
             }
             return mEnabledInputMethodsStrCache;
         }
@@ -3426,8 +3642,8 @@
             if (DEBUG) {
                 Slog.d(TAG, "putSubtypeHistoryStr: " + str);
             }
-            Settings.Secure.putString(
-                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str);
+            Settings.Secure.putStringForUser(
+                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, str, mCurrentUserId);
         }
 
         public Pair<String, String> getLastInputMethodAndSubtypeLocked() {
@@ -3546,20 +3762,57 @@
 
         private String getSubtypeHistoryStr() {
             if (DEBUG) {
-                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getString(
-                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY));
+                Slog.d(TAG, "getSubtypeHistoryStr: " + Settings.Secure.getStringForUser(
+                        mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId));
             }
-            return Settings.Secure.getString(
-                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY);
+            return Settings.Secure.getStringForUser(
+                    mResolver, Settings.Secure.INPUT_METHODS_SUBTYPE_HISTORY, mCurrentUserId);
         }
 
         public void putSelectedInputMethod(String imeId) {
-            Settings.Secure.putString(mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId);
+            if (DEBUG) {
+                Slog.d(TAG, "putSelectedInputMethodStr: " + imeId + ", "
+                        + mCurrentUserId);
+            }
+            Settings.Secure.putStringForUser(
+                    mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, imeId, mCurrentUserId);
         }
 
         public void putSelectedSubtype(int subtypeId) {
-            Settings.Secure.putInt(
-                    mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, subtypeId);
+            if (DEBUG) {
+                Slog.d(TAG, "putSelectedInputMethodSubtypeStr: " + subtypeId + ", "
+                        + mCurrentUserId);
+            }
+            Settings.Secure.putIntForUser(mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE,
+                    subtypeId, mCurrentUserId);
+        }
+
+        public String getDisabledSystemInputMethods() {
+            return Settings.Secure.getStringForUser(
+                    mResolver, Settings.Secure.DISABLED_SYSTEM_INPUT_METHODS, mCurrentUserId);
+        }
+
+        public String getSelectedInputMethod() {
+            if (DEBUG) {
+                Slog.d(TAG, "getSelectedInputMethodStr: " + Settings.Secure.getStringForUser(
+                        mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId)
+                        + ", " + mCurrentUserId);
+            }
+            return Settings.Secure.getStringForUser(
+                    mResolver, Settings.Secure.DEFAULT_INPUT_METHOD, mCurrentUserId);
+        }
+
+        public int getSelectedInputMethodSubtypeHashCode() {
+            try {
+                return Settings.Secure.getIntForUser(
+                        mResolver, Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, mCurrentUserId);
+            } catch (SettingNotFoundException e) {
+                return NOT_A_SUBTYPE_ID;
+            }
+        }
+
+        public int getCurrentUserId() {
+            return mCurrentUserId;
         }
     }
 
@@ -3762,6 +4015,20 @@
     }
 
     // ----------------------------------------------------------------------
+    // Utilities for debug
+    private static String getStackTrace() {
+        final StringBuilder sb = new StringBuilder();
+        try {
+            throw new RuntimeException();
+        } catch (RuntimeException e) {
+            final StackTraceElement[] frames = e.getStackTrace();
+            // Start at 1 because the first frame is here and we don't care about it
+            for (int j = 1; j < frames.length; ++j) {
+                sb.append(frames[j].toString() + "\n");
+            }
+        }
+        return sb.toString();
+    }
 
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java
index b834a84..578e602 100644
--- a/services/java/com/android/server/LocationManagerService.java
+++ b/services/java/com/android/server/LocationManagerService.java
@@ -703,10 +703,10 @@
     }
 
     private String pickBest(List<String> providers) {
-        if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
-            return LocationManager.NETWORK_PROVIDER;
-        } else if (providers.contains(LocationManager.GPS_PROVIDER)) {
+        if (providers.contains(LocationManager.GPS_PROVIDER)) {
             return LocationManager.GPS_PROVIDER;
+        } else if (providers.contains(LocationManager.NETWORK_PROVIDER)) {
+            return LocationManager.NETWORK_PROVIDER;
         } else {
             return providers.get(0);
         }
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index fe2f8d8..0312705 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -58,6 +58,7 @@
 import android.util.Xml;
 
 import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
 import com.android.server.NativeDaemonConnector.Command;
 import com.android.server.am.ActivityManagerService;
@@ -224,22 +225,31 @@
      * OBBs.
      */
     final private Map<IBinder, List<ObbState>> mObbMounts = new HashMap<IBinder, List<ObbState>>();
+
+    /** Map from raw paths to {@link ObbState}. */
     final private Map<String, ObbState> mObbPathToStateMap = new HashMap<String, ObbState>();
 
     class ObbState implements IBinder.DeathRecipient {
-        public ObbState(String filename, int callerUid, IObbActionListener token, int nonce)
-                throws RemoteException {
-            this.filename = filename;
-            this.callerUid = callerUid;
+        public ObbState(String rawPath, String canonicalPath, int callingUid,
+                IObbActionListener token, int nonce) {
+            this.rawPath = rawPath;
+            this.canonicalPath = canonicalPath.toString();
+
+            final int userId = UserHandle.getUserId(callingUid);
+            this.ownerPath = buildObbPath(canonicalPath, userId, false);
+            this.voldPath = buildObbPath(canonicalPath, userId, true);
+
+            this.ownerGid = UserHandle.getSharedAppGid(callingUid);
             this.token = token;
             this.nonce = nonce;
         }
 
-        // OBB source filename
-        String filename;
+        final String rawPath;
+        final String canonicalPath;
+        final String ownerPath;
+        final String voldPath;
 
-        // Binder.callingUid()
-        final public int callerUid;
+        final int ownerGid;
 
         // Token of remote Binder caller
         final IObbActionListener token;
@@ -268,12 +278,13 @@
         @Override
         public String toString() {
             StringBuilder sb = new StringBuilder("ObbState{");
-            sb.append("filename=");
-            sb.append(filename);
-            sb.append(",token=");
-            sb.append(token.toString());
-            sb.append(",callerUid=");
-            sb.append(callerUid);
+            sb.append("rawPath=").append(rawPath);
+            sb.append(",canonicalPath=").append(canonicalPath);
+            sb.append(",ownerPath=").append(ownerPath);
+            sb.append(",voldPath=").append(voldPath);
+            sb.append(",ownerGid=").append(ownerGid);
+            sb.append(",token=").append(token);
+            sb.append(",binder=").append(getBinder());
             sb.append('}');
             return sb.toString();
         }
@@ -1853,17 +1864,24 @@
         return callerUid == packageUid;
     }
 
-    public String getMountedObbPath(String filename) {
-        if (filename == null) {
-            throw new IllegalArgumentException("filename cannot be null");
-        }
+    public String getMountedObbPath(String rawPath) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
 
         waitForReady();
         warnOnNotMounted();
 
+        final ObbState state;
+        synchronized (mObbPathToStateMap) {
+            state = mObbPathToStateMap.get(rawPath);
+        }
+        if (state == null) {
+            Slog.w(TAG, "Failed to find OBB mounted at " + rawPath);
+            return null;
+        }
+
         final NativeDaemonEvent event;
         try {
-            event = mConnector.execute("obb", "path", filename);
+            event = mConnector.execute("obb", "path", state.voldPath);
             event.checkCode(VoldResponseCode.AsecPathResult);
             return event.getMessage();
         } catch (NativeDaemonConnectorException e) {
@@ -1876,48 +1894,52 @@
         }
     }
 
-    public boolean isObbMounted(String filename) {
-        if (filename == null) {
-            throw new IllegalArgumentException("filename cannot be null");
-        }
-
+    @Override
+    public boolean isObbMounted(String rawPath) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
         synchronized (mObbMounts) {
-            return mObbPathToStateMap.containsKey(filename);
+            return mObbPathToStateMap.containsKey(rawPath);
         }
     }
 
-    public void mountObb(String filename, String key, IObbActionListener token, int nonce)
-            throws RemoteException {
-        if (filename == null) {
-            throw new IllegalArgumentException("filename cannot be null");
-        }
+    @Override
+    public void mountObb(
+            String rawPath, String canonicalPath, String key, IObbActionListener token, int nonce) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+        Preconditions.checkNotNull(canonicalPath, "canonicalPath cannot be null");
+        Preconditions.checkNotNull(token, "token cannot be null");
 
-        if (token == null) {
-            throw new IllegalArgumentException("token cannot be null");
-        }
-
-        final int callerUid = Binder.getCallingUid();
-        final ObbState obbState = new ObbState(filename, callerUid, token, nonce);
-        final ObbAction action = new MountObbAction(obbState, key);
+        final int callingUid = Binder.getCallingUid();
+        final ObbState obbState = new ObbState(rawPath, canonicalPath, callingUid, token, nonce);
+        final ObbAction action = new MountObbAction(obbState, key, callingUid);
         mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
 
         if (DEBUG_OBB)
             Slog.i(TAG, "Send to OBB handler: " + action.toString());
     }
 
-    public void unmountObb(String filename, boolean force, IObbActionListener token, int nonce)
-            throws RemoteException {
-        if (filename == null) {
-            throw new IllegalArgumentException("filename cannot be null");
+    @Override
+    public void unmountObb(String rawPath, boolean force, IObbActionListener token, int nonce) {
+        Preconditions.checkNotNull(rawPath, "rawPath cannot be null");
+
+        final ObbState existingState;
+        synchronized (mObbPathToStateMap) {
+            existingState = mObbPathToStateMap.get(rawPath);
         }
 
-        final int callerUid = Binder.getCallingUid();
-        final ObbState obbState = new ObbState(filename, callerUid, token, nonce);
-        final ObbAction action = new UnmountObbAction(obbState, force);
-        mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
+        if (existingState != null) {
+            // TODO: separate state object from request data
+            final int callingUid = Binder.getCallingUid();
+            final ObbState newState = new ObbState(
+                    rawPath, existingState.canonicalPath, callingUid, token, nonce);
+            final ObbAction action = new UnmountObbAction(newState, force);
+            mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_RUN_ACTION, action));
 
-        if (DEBUG_OBB)
-            Slog.i(TAG, "Send to OBB handler: " + action.toString());
+            if (DEBUG_OBB)
+                Slog.i(TAG, "Send to OBB handler: " + action.toString());
+        } else {
+            Slog.w(TAG, "Unknown OBB mount at " + rawPath);
+        }
     }
 
     @Override
@@ -2094,7 +2116,7 @@
             mObbMounts.put(binder, obbStates);
         } else {
             for (final ObbState o : obbStates) {
-                if (o.filename.equals(obbState.filename)) {
+                if (o.rawPath.equals(obbState.rawPath)) {
                     throw new IllegalStateException("Attempt to add ObbState twice. "
                             + "This indicates an error in the MountService logic.");
                 }
@@ -2118,7 +2140,7 @@
             throw e;
         }
 
-        mObbPathToStateMap.put(obbState.filename, obbState);
+        mObbPathToStateMap.put(obbState.rawPath, obbState);
     }
 
     private void removeObbStateLocked(ObbState obbState) {
@@ -2133,7 +2155,7 @@
             }
         }
 
-        mObbPathToStateMap.remove(obbState.filename);
+        mObbPathToStateMap.remove(obbState.rawPath);
     }
 
     private class ObbActionHandler extends Handler {
@@ -2241,33 +2263,32 @@
                     synchronized (mObbMounts) {
                         final List<ObbState> obbStatesToRemove = new LinkedList<ObbState>();
 
-                        final Iterator<Entry<String, ObbState>> i =
-                                mObbPathToStateMap.entrySet().iterator();
+                        final Iterator<ObbState> i = mObbPathToStateMap.values().iterator();
                         while (i.hasNext()) {
-                            final Entry<String, ObbState> obbEntry = i.next();
+                            final ObbState state = i.next();
 
                             /*
                              * If this entry's source file is in the volume path
                              * that got unmounted, remove it because it's no
                              * longer valid.
                              */
-                            if (obbEntry.getKey().startsWith(path)) {
-                                obbStatesToRemove.add(obbEntry.getValue());
+                            if (state.canonicalPath.startsWith(path)) {
+                                obbStatesToRemove.add(state);
                             }
                         }
 
                         for (final ObbState obbState : obbStatesToRemove) {
                             if (DEBUG_OBB)
-                                Slog.i(TAG, "Removing state for " + obbState.filename);
+                                Slog.i(TAG, "Removing state for " + obbState.rawPath);
 
                             removeObbStateLocked(obbState);
 
                             try {
-                                obbState.token.onObbResult(obbState.filename, obbState.nonce,
+                                obbState.token.onObbResult(obbState.rawPath, obbState.nonce,
                                         OnObbStateChangeListener.UNMOUNTED);
                             } catch (RemoteException e) {
                                 Slog.i(TAG, "Couldn't send unmount notification for  OBB: "
-                                        + obbState.filename);
+                                        + obbState.rawPath);
                             }
                         }
                     }
@@ -2339,14 +2360,14 @@
         protected ObbInfo getObbInfo() throws IOException {
             ObbInfo obbInfo;
             try {
-                obbInfo = mContainerService.getObbInfo(mObbState.filename);
+                obbInfo = mContainerService.getObbInfo(mObbState.ownerPath);
             } catch (RemoteException e) {
                 Slog.d(TAG, "Couldn't call DefaultContainerService to fetch OBB info for "
-                        + mObbState.filename);
+                        + mObbState.ownerPath);
                 obbInfo = null;
             }
             if (obbInfo == null) {
-                throw new IOException("Couldn't read OBB file: " + mObbState.filename);
+                throw new IOException("Couldn't read OBB file: " + mObbState.ownerPath);
             }
             return obbInfo;
         }
@@ -2357,7 +2378,7 @@
             }
 
             try {
-                mObbState.token.onObbResult(mObbState.filename, mObbState.nonce, status);
+                mObbState.token.onObbResult(mObbState.rawPath, mObbState.nonce, status);
             } catch (RemoteException e) {
                 Slog.w(TAG, "MountServiceListener went away while calling onObbStateChanged");
             }
@@ -2366,10 +2387,12 @@
 
     class MountObbAction extends ObbAction {
         private final String mKey;
+        private final int mCallingUid;
 
-        MountObbAction(ObbState obbState, String key) {
+        MountObbAction(ObbState obbState, String key, int callingUid) {
             super(obbState);
             mKey = key;
+            mCallingUid = callingUid;
         }
 
         @Override
@@ -2379,7 +2402,7 @@
 
             final ObbInfo obbInfo = getObbInfo();
 
-            if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mObbState.callerUid)) {
+            if (!isUidOwnerOfPackageOrSystem(obbInfo.packageName, mCallingUid)) {
                 Slog.w(TAG, "Denied attempt to mount OBB " + obbInfo.filename
                         + " which is owned by " + obbInfo.packageName);
                 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
@@ -2388,7 +2411,7 @@
 
             final boolean isMounted;
             synchronized (mObbMounts) {
-                isMounted = mObbPathToStateMap.containsKey(obbInfo.filename);
+                isMounted = mObbPathToStateMap.containsKey(mObbState.rawPath);
             }
             if (isMounted) {
                 Slog.w(TAG, "Attempt to mount OBB which is already mounted: " + obbInfo.filename);
@@ -2396,12 +2419,6 @@
                 return;
             }
 
-            /*
-             * The filename passed in might not be the canonical name, so just
-             * set the filename to the canonicalized version.
-             */
-            mObbState.filename = obbInfo.filename;
-
             final String hashedKey;
             if (mKey == null) {
                 hashedKey = "none";
@@ -2428,7 +2445,7 @@
             int rc = StorageResultCode.OperationSucceeded;
             try {
                 mConnector.execute(
-                        "obb", "mount", mObbState.filename, hashedKey, mObbState.callerUid);
+                        "obb", "mount", mObbState.voldPath, hashedKey, mObbState.ownerGid);
             } catch (NativeDaemonConnectorException e) {
                 int code = e.getCode();
                 if (code != VoldResponseCode.OpFailedStorageBusy) {
@@ -2438,7 +2455,7 @@
 
             if (rc == StorageResultCode.OperationSucceeded) {
                 if (DEBUG_OBB)
-                    Slog.d(TAG, "Successfully mounted OBB " + mObbState.filename);
+                    Slog.d(TAG, "Successfully mounted OBB " + mObbState.voldPath);
 
                 synchronized (mObbMounts) {
                     addObbStateLocked(mObbState);
@@ -2461,14 +2478,7 @@
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("MountObbAction{");
-            sb.append("filename=");
-            sb.append(mObbState.filename);
-            sb.append(",callerUid=");
-            sb.append(mObbState.callerUid);
-            sb.append(",token=");
-            sb.append(mObbState.token != null ? mObbState.token.toString() : "NULL");
-            sb.append(",binder=");
-            sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null");
+            sb.append(mObbState);
             sb.append('}');
             return sb.toString();
         }
@@ -2489,28 +2499,26 @@
 
             final ObbInfo obbInfo = getObbInfo();
 
-            final ObbState obbState;
+            final ObbState existingState;
             synchronized (mObbMounts) {
-                obbState = mObbPathToStateMap.get(obbInfo.filename);
+                existingState = mObbPathToStateMap.get(mObbState.rawPath);
             }
 
-            if (obbState == null) {
+            if (existingState == null) {
                 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_NOT_MOUNTED);
                 return;
             }
 
-            if (obbState.callerUid != mObbState.callerUid) {
-                Slog.w(TAG, "Permission denied attempting to unmount OBB " + obbInfo.filename
-                        + " (owned by " + obbInfo.packageName + ")");
+            if (existingState.ownerGid != mObbState.ownerGid) {
+                Slog.w(TAG, "Permission denied attempting to unmount OBB " + existingState.rawPath
+                        + " (owned by GID " + existingState.ownerGid + ")");
                 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_PERMISSION_DENIED);
                 return;
             }
 
-            mObbState.filename = obbInfo.filename;
-
             int rc = StorageResultCode.OperationSucceeded;
             try {
-                final Command cmd = new Command("obb", "unmount", mObbState.filename);
+                final Command cmd = new Command("obb", "unmount", mObbState.voldPath);
                 if (mForceUnmount) {
                     cmd.appendArg("force");
                 }
@@ -2529,12 +2537,12 @@
 
             if (rc == StorageResultCode.OperationSucceeded) {
                 synchronized (mObbMounts) {
-                    removeObbStateLocked(obbState);
+                    removeObbStateLocked(existingState);
                 }
 
                 sendNewStatusOrIgnore(OnObbStateChangeListener.UNMOUNTED);
             } else {
-                Slog.w(TAG, "Could not mount OBB: " + mObbState.filename);
+                Slog.w(TAG, "Could not unmount OBB: " + existingState);
                 sendNewStatusOrIgnore(OnObbStateChangeListener.ERROR_COULD_NOT_UNMOUNT);
             }
         }
@@ -2548,21 +2556,63 @@
         public String toString() {
             StringBuilder sb = new StringBuilder();
             sb.append("UnmountObbAction{");
-            sb.append("filename=");
-            sb.append(mObbState.filename != null ? mObbState.filename : "null");
+            sb.append(mObbState);
             sb.append(",force=");
             sb.append(mForceUnmount);
-            sb.append(",callerUid=");
-            sb.append(mObbState.callerUid);
-            sb.append(",token=");
-            sb.append(mObbState.token != null ? mObbState.token.toString() : "null");
-            sb.append(",binder=");
-            sb.append(mObbState.token != null ? mObbState.getBinder().toString() : "null");
             sb.append('}');
             return sb.toString();
         }
     }
 
+    // @VisibleForTesting
+    public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) {
+        // TODO: allow caller to provide Environment for full testing
+
+        // Only adjust paths when storage is emulated
+        if (!Environment.isExternalStorageEmulated()) {
+            return canonicalPath;
+        }
+
+        String path = canonicalPath.toString();
+
+        // First trim off any external storage prefix
+        final UserEnvironment userEnv = new UserEnvironment(userId);
+
+        // /storage/emulated/0
+        final String externalPath = userEnv.getExternalStorageDirectory().toString();
+        // /storage/emulated_legacy
+        final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory()
+                .toString();
+
+        if (path.startsWith(externalPath)) {
+            path = path.substring(externalPath.length() + 1);
+        } else if (path.startsWith(legacyExternalPath)) {
+            path = path.substring(legacyExternalPath.length() + 1);
+        } else {
+            return canonicalPath;
+        }
+
+        // Handle special OBB paths on emulated storage
+        final String obbPath = "Android/obb";
+        if (path.startsWith(obbPath)) {
+            path = path.substring(obbPath.length() + 1);
+
+            if (forVold) {
+                return new File(Environment.getEmulatedStorageObbSource(), path).toString();
+            } else {
+                final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
+                return new File(ownerEnv.getExternalStorageObbDirectory(), path).toString();
+            }
+        }
+
+        // Handle normal external storage paths
+        if (forVold) {
+            return new File(Environment.getEmulatedStorageSource(userId), path).toString();
+        } else {
+            return new File(userEnv.getExternalStorageDirectory(), path).toString();
+        }
+    }
+
     @Override
     protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
         if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) != PackageManager.PERMISSION_GRANTED) {
diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java
index 76194ae..5d5f8d3 100755
--- a/services/java/com/android/server/NotificationManagerService.java
+++ b/services/java/com/android/server/NotificationManagerService.java
@@ -890,6 +890,7 @@
 
         userId = ActivityManager.handleIncomingUser(callingPid,
                 callingUid, userId, true, true, "enqueueNotification", pkg);
+        final UserHandle user = new UserHandle(userId);
 
         // Limit the number of notifications that any given package except the android
         // package can enqueue.  Prevents DOS attacks and deals with leaks.
@@ -991,7 +992,6 @@
             }
 
             if (notification.icon != 0) {
-                final UserHandle user = new UserHandle(userId);
                 final StatusBarNotification n = new StatusBarNotification(
                         pkg, id, tag, r.uid, r.initialPid, score, notification, user);
                 if (old != null && old.statusBarKey != null) {
@@ -1063,7 +1063,7 @@
                         try {
                             final IRingtonePlayer player = mAudioService.getRingtonePlayer();
                             if (player != null) {
-                                player.playAsync(uri, looping, audioStreamType);
+                                player.playAsync(uri, user, looping, audioStreamType);
                             }
                         } catch (RemoteException e) {
                         } finally {
diff --git a/services/jni/com_android_server_power_PowerManagerService.cpp b/services/jni/com_android_server_power_PowerManagerService.cpp
index 3f3970b..38af38d 100644
--- a/services/jni/com_android_server_power_PowerManagerService.cpp
+++ b/services/jni/com_android_server_power_PowerManagerService.cpp
@@ -174,32 +174,32 @@
     sp<ISurfaceComposer> s(ComposerService::getComposerService());
     if (on) {
         {
-            ALOGD_IF_SLOW(50, "Excessive delay in autosuspend_disable() while turning screen on");
+            ALOGD_IF_SLOW(100, "Excessive delay in autosuspend_disable() while turning screen on");
             autosuspend_disable();
         }
 
         if (gPowerModule) {
-            ALOGD_IF_SLOW(10, "Excessive delay in setInteractive(true) while turning screen on");
+            ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(true) while turning screen on");
             gPowerModule->setInteractive(gPowerModule, true);
         }
 
         {
-            ALOGD_IF_SLOW(20, "Excessive delay in unblank() while turning screen on");
+            ALOGD_IF_SLOW(100, "Excessive delay in unblank() while turning screen on");
             s->unblank();
         }
     } else {
         {
-            ALOGD_IF_SLOW(20, "Excessive delay in blank() while turning screen off");
+            ALOGD_IF_SLOW(100, "Excessive delay in blank() while turning screen off");
             s->blank();
         }
 
         if (gPowerModule) {
-            ALOGD_IF_SLOW(10, "Excessive delay in setInteractive(false) while turning screen off");
+            ALOGD_IF_SLOW(20, "Excessive delay in setInteractive(false) while turning screen off");
             gPowerModule->setInteractive(gPowerModule, false);
         }
 
         {
-            ALOGD_IF_SLOW(50, "Excessive delay in autosuspend_enable() while turning screen off");
+            ALOGD_IF_SLOW(100, "Excessive delay in autosuspend_enable() while turning screen off");
             autosuspend_enable();
         }
     }
diff --git a/core/tests/coretests/res/raw/test1.obb b/services/tests/servicestests/res/raw/test1.obb
similarity index 99%
rename from core/tests/coretests/res/raw/test1.obb
rename to services/tests/servicestests/res/raw/test1.obb
index 8466588..7d2b4f6 100644
--- a/core/tests/coretests/res/raw/test1.obb
+++ b/services/tests/servicestests/res/raw/test1.obb
Binary files differ
diff --git a/core/tests/coretests/res/raw/test1_nosig.obb b/services/tests/servicestests/res/raw/test1_nosig.obb
similarity index 100%
rename from core/tests/coretests/res/raw/test1_nosig.obb
rename to services/tests/servicestests/res/raw/test1_nosig.obb
Binary files differ
diff --git a/core/tests/coretests/res/raw/test1_wrongpackage.obb b/services/tests/servicestests/res/raw/test1_wrongpackage.obb
similarity index 100%
rename from core/tests/coretests/res/raw/test1_wrongpackage.obb
rename to services/tests/servicestests/res/raw/test1_wrongpackage.obb
Binary files differ
diff --git a/core/tests/coretests/src/com/android/server/MountServiceTests.java b/services/tests/servicestests/src/com/android/server/MountServiceTests.java
similarity index 85%
rename from core/tests/coretests/src/com/android/server/MountServiceTests.java
rename to services/tests/servicestests/src/com/android/server/MountServiceTests.java
index 1f8c92e..9c88b40 100644
--- a/core/tests/coretests/src/com/android/server/MountServiceTests.java
+++ b/services/tests/servicestests/src/com/android/server/MountServiceTests.java
@@ -16,8 +16,6 @@
 
 package com.android.server;
 
-import com.android.frameworks.coretests.R;
-
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.Resources.NotFoundException;
@@ -29,6 +27,10 @@
 import android.test.suitebuilder.annotation.LargeTest;
 import android.util.Log;
 
+import static com.android.server.MountService.buildObbPath;
+
+import com.android.frameworks.servicestests.R;
+
 import java.io.File;
 import java.io.InputStream;
 
@@ -282,4 +284,34 @@
         unmountObb(sm, file1, OnObbStateChangeListener.UNMOUNTED);
         unmountObb(sm, file2, OnObbStateChangeListener.UNMOUNTED);
     }
+
+    public void testBuildObbPath() {
+        final int userId = 10;
+
+        // Paths outside external storage should remain untouched
+        assertEquals("/storage/random/foo",
+                buildObbPath("/storage/random/foo", userId, true));
+        assertEquals("/storage/random/foo",
+                buildObbPath("/storage/random/foo", userId, false));
+
+        // Paths on user-specific emulated storage
+        assertEquals("/mnt/shell/emulated/10/foo",
+                buildObbPath("/storage/emulated_legacy/foo", userId, true));
+        assertEquals("/storage/emulated/10/foo",
+                buildObbPath("/storage/emulated_legacy/foo", userId, false));
+        assertEquals("/mnt/shell/emulated/10/foo",
+                buildObbPath("/storage/emulated/10/foo", userId, true));
+        assertEquals("/storage/emulated/10/foo",
+                buildObbPath("/storage/emulated/10/foo", userId, false));
+
+        // Paths on shared OBB emulated storage
+        assertEquals("/mnt/shell/emulated/obb/foo",
+                buildObbPath("/storage/emulated_legacy/Android/obb/foo", userId, true));
+        assertEquals("/storage/emulated/0/Android/obb/foo",
+                buildObbPath("/storage/emulated_legacy/Android/obb/foo", userId, false));
+        assertEquals("/mnt/shell/emulated/obb/foo",
+                buildObbPath("/storage/emulated/10/Android/obb/foo", userId, true));
+        assertEquals("/storage/emulated/0/Android/obb/foo",
+                buildObbPath("/storage/emulated/10/Android/obb/foo", userId, false));
+    }
 }
diff --git a/tools/aapt/Resource.cpp b/tools/aapt/Resource.cpp
index 9c2e1b9..77168f9 100644
--- a/tools/aapt/Resource.cpp
+++ b/tools/aapt/Resource.cpp
@@ -1946,7 +1946,7 @@
                 const bool pub = (typeSpecFlags&ResTable_typeSpec::SPEC_PUBLIC) != 0;
 
                 fprintf(fp,
-                        "int styleable.%s_%s %d\n",
+                        "int styleable %s_%s %d\n",
                         nclassName.string(),
                         String8(name).string(), (int)pos);
             }
diff --git a/wifi/java/android/net/wifi/WifiStateTracker.java b/wifi/java/android/net/wifi/WifiStateTracker.java
index a5a2469..55ea34f 100644
--- a/wifi/java/android/net/wifi/WifiStateTracker.java
+++ b/wifi/java/android/net/wifi/WifiStateTracker.java
@@ -25,7 +25,6 @@
 import android.net.NetworkInfo;
 import android.net.NetworkInfo.DetailedState;
 import android.net.NetworkStateTracker;
-import android.net.wifi.p2p.WifiP2pManager;
 import android.os.Handler;
 import android.os.Message;
 import android.util.Slog;
@@ -88,7 +87,6 @@
         IntentFilter filter = new IntentFilter();
         filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
         filter.addAction(WifiManager.LINK_CONFIGURATION_CHANGED_ACTION);
-        filter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION);
 
         mWifiStateReceiver = new WifiStateReceiver();
         mContext.registerReceiver(mWifiStateReceiver, filter);
@@ -217,20 +215,7 @@
         @Override
         public void onReceive(Context context, Intent intent) {
 
-            if (intent.getAction().equals(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)) {
-                    mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
-                            WifiP2pManager.EXTRA_NETWORK_INFO);
-                    mLinkProperties = intent.getParcelableExtra(
-                            WifiP2pManager.EXTRA_LINK_PROPERTIES);
-                    if (mLinkProperties == null) {
-                        mLinkProperties = new LinkProperties();
-                    }
-                    mLinkCapabilities = intent.getParcelableExtra(
-                        WifiP2pManager.EXTRA_LINK_CAPABILITIES);
-                    if (mLinkCapabilities == null) {
-                        mLinkCapabilities = new LinkCapabilities();
-                    }
-             } else if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
+            if (intent.getAction().equals(WifiManager.NETWORK_STATE_CHANGED_ACTION)) {
                 mNetworkInfo = (NetworkInfo) intent.getParcelableExtra(
                         WifiManager.EXTRA_NETWORK_INFO);
                 mLinkProperties = intent.getParcelableExtra(