Merge "Only consider hiddenRequested when deciding layout" into jb-mr1-dev
diff --git a/api/current.txt b/api/current.txt
index e1bff14..db378c8 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -25551,12 +25551,12 @@
     method protected boolean isLayoutRtl();
     method public boolean isMarginRelative();
     method public void setLayoutDirection(int);
+    method public void setMarginEnd(int);
+    method public void setMarginStart(int);
     method public void setMargins(int, int, int, int);
     field public int bottomMargin;
-    field public int endMargin;
     field public int leftMargin;
     field public int rightMargin;
-    field public int startMargin;
     field public int topMargin;
   }
 
@@ -26943,6 +26943,9 @@
     method public boolean useHttpAuthUsernamePassword();
   }
 
+  public abstract class JavascriptInterface implements java.lang.annotation.Annotation {
+  }
+
   public class JsPromptResult extends android.webkit.JsResult {
     method public void confirm(java.lang.String);
   }
@@ -27067,7 +27070,7 @@
   }
 
   public abstract class WebSettings {
-    method public boolean enableSmoothTransition();
+    method public deprecated boolean enableSmoothTransition();
     method public boolean getAllowContentAccess();
     method public boolean getAllowFileAccess();
     method public abstract boolean getAllowFileAccessFromFileURLs();
@@ -27133,7 +27136,7 @@
     method public void setDefaultZoom(android.webkit.WebSettings.ZoomDensity);
     method public void setDisplayZoomControls(boolean);
     method public synchronized void setDomStorageEnabled(boolean);
-    method public void setEnableSmoothTransition(boolean);
+    method public deprecated void setEnableSmoothTransition(boolean);
     method public synchronized void setFantasyFontFamily(java.lang.String);
     method public synchronized void setFixedFontFamily(java.lang.String);
     method public synchronized void setGeolocationDatabasePath(java.lang.String);
@@ -27172,7 +27175,7 @@
     field public static final int LOAD_CACHE_ELSE_NETWORK = 1; // 0x1
     field public static final int LOAD_CACHE_ONLY = 3; // 0x3
     field public static final int LOAD_DEFAULT = -1; // 0xffffffff
-    field public static final int LOAD_NORMAL = 0; // 0x0
+    field public static final deprecated int LOAD_NORMAL = 0; // 0x0
     field public static final int LOAD_NO_CACHE = 2; // 0x2
   }
 
@@ -27255,13 +27258,13 @@
     ctor public WebView(android.content.Context);
     ctor public WebView(android.content.Context, android.util.AttributeSet);
     ctor public WebView(android.content.Context, android.util.AttributeSet, int);
-    ctor public WebView(android.content.Context, android.util.AttributeSet, int, boolean);
+    ctor public deprecated WebView(android.content.Context, android.util.AttributeSet, int, boolean);
     method public void addJavascriptInterface(java.lang.Object, java.lang.String);
     method public boolean canGoBack();
     method public boolean canGoBackOrForward(int);
     method public boolean canGoForward();
-    method public boolean canZoomIn();
-    method public boolean canZoomOut();
+    method public deprecated boolean canZoomIn();
+    method public deprecated boolean canZoomOut();
     method public android.graphics.Picture capturePicture();
     method public void clearCache(boolean);
     method public void clearFormData();
@@ -27289,7 +27292,7 @@
     method public java.lang.String[] getHttpAuthUsernamePassword(java.lang.String, java.lang.String);
     method public java.lang.String getOriginalUrl();
     method public int getProgress();
-    method public float getScale();
+    method public deprecated float getScale();
     method public android.webkit.WebSettings getSettings();
     method public java.lang.String getTitle();
     method public java.lang.String getUrl();
@@ -27326,13 +27329,13 @@
     method public android.webkit.WebBackForwardList saveState(android.os.Bundle);
     method public void saveWebArchive(java.lang.String);
     method public void saveWebArchive(java.lang.String, boolean, android.webkit.ValueCallback<java.lang.String>);
-    method public void setCertificate(android.net.http.SslCertificate);
+    method public deprecated void setCertificate(android.net.http.SslCertificate);
     method public void setDownloadListener(android.webkit.DownloadListener);
     method public void setFindListener(android.webkit.WebView.FindListener);
     method public void setHorizontalScrollbarOverlay(boolean);
     method public void setHttpAuthUsernamePassword(java.lang.String, java.lang.String, java.lang.String, java.lang.String);
     method public void setInitialScale(int);
-    method public void setMapTrackballToArrowKeys(boolean);
+    method public deprecated void setMapTrackballToArrowKeys(boolean);
     method public void setNetworkAvailable(boolean);
     method public deprecated void setPictureListener(android.webkit.WebView.PictureListener);
     method public void setVerticalScrollbarOverlay(boolean);
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 09fa99c..812ac9e 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -52,6 +52,7 @@
 import android.os.Binder;
 import android.os.Bundle;
 import android.os.Debug;
+import android.os.Environment;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -4877,6 +4878,8 @@
         // StrictMode) on debug builds, but using DropBox, not logs.
         CloseGuard.setEnabled(false);
 
+        Environment.initForCurrentUser();
+
         Security.addProvider(new AndroidKeyStoreProvider());
 
         Process.setArgV0("<pre-initialized>");
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 3498919..92104fa 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1503,7 +1503,7 @@
                     (message != null ? (message + ": ") : "") +
                     (selfToo
                      ? "Neither user " + uid + " nor current process has "
-                     : "User " + uid + " does not have ") +
+                     : "uid " + uid + " does not have ") +
                     permission +
                     ".");
         }
diff --git a/core/java/android/content/pm/UserInfo.java b/core/java/android/content/pm/UserInfo.java
index 060a235..6bc9a1f 100644
--- a/core/java/android/content/pm/UserInfo.java
+++ b/core/java/android/content/pm/UserInfo.java
@@ -18,7 +18,7 @@
 
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.Parcelable.Creator;
+import android.os.UserHandle;
 
 /**
  * Per-user information.
@@ -92,6 +92,10 @@
         serialNumber = orig.serialNumber;
     }
 
+    public UserHandle getUserHandle() {
+        return new UserHandle(id);
+    }
+
     @Override
     public String toString() {
         return "UserInfo{" + id + ":" + name + ":" + Integer.toHexString(flags) + "}";
diff --git a/core/java/android/os/Debug.java b/core/java/android/os/Debug.java
index 591cd0e..c08bfeb 100644
--- a/core/java/android/os/Debug.java
+++ b/core/java/android/os/Debug.java
@@ -95,7 +95,7 @@
      * Default trace file path and file
      */
     private static final String DEFAULT_TRACE_PATH_PREFIX =
-        Environment.getExternalStorageDirectory().getPath() + "/";
+        Environment.getLegacyExternalStorageDirectory().getPath() + "/";
     private static final String DEFAULT_TRACE_BODY = "dmtrace";
     private static final String DEFAULT_TRACE_EXTENSION = ".trace";
     private static final String DEFAULT_TRACE_FILE_PATH =
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 2fbcf3f..6667a41f 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -16,9 +16,10 @@
 
 package android.os;
 
-import android.content.res.Resources;
 import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.File;
@@ -29,31 +30,125 @@
 public class Environment {
     private static final String TAG = "Environment";
 
+    private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
+    private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
+
     private static final File ROOT_DIRECTORY
             = getDirectory("ANDROID_ROOT", "/system");
 
     private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
 
-    private static final Object mLock = new Object();
+    private static UserEnvironment sCurrentUser;
 
-    private volatile static StorageVolume mPrimaryVolume = null;
+    private static final Object sLock = new Object();
+
+    // @GuardedBy("sLock")
+    private static volatile StorageVolume sPrimaryVolume;
 
     private static StorageVolume getPrimaryVolume() {
-        if (mPrimaryVolume == null) {
-            synchronized (mLock) {
-                if (mPrimaryVolume == null) {
+        if (sPrimaryVolume == null) {
+            synchronized (sLock) {
+                if (sPrimaryVolume == null) {
                     try {
                         IMountService mountService = IMountService.Stub.asInterface(ServiceManager
                                 .getService("mount"));
-                        Parcelable[] volumes = mountService.getVolumeList();
-                        mPrimaryVolume = (StorageVolume)volumes[0];
+                        final StorageVolume[] volumes = mountService.getVolumeList();
+                        sPrimaryVolume = StorageManager.getPrimaryVolume(volumes);
                     } catch (Exception e) {
                         Log.e(TAG, "couldn't talk to MountService", e);
                     }
                 }
             }
         }
-        return mPrimaryVolume;
+        return sPrimaryVolume;
+    }
+
+    static {
+        initForCurrentUser();
+    }
+
+    /** {@hide} */
+    public static void initForCurrentUser() {
+        final int userId = UserHandle.myUserId();
+        sCurrentUser = new UserEnvironment(userId);
+
+        synchronized (sLock) {
+            sPrimaryVolume = null;
+        }
+    }
+
+    /** {@hide} */
+    public static class UserEnvironment {
+        // TODO: generalize further to create package-specific environment
+
+        private final File mExternalStorage;
+        private final File mExternalStorageAndroidData;
+        private final File mExternalStorageAndroidMedia;
+        private final File mExternalStorageAndroidObb;
+
+        public UserEnvironment(int userId) {
+            // See storage config details at http://source.android.com/tech/storage/
+            String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
+            String rawEmulatedStorageTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
+
+            if (!TextUtils.isEmpty(rawEmulatedStorageTarget)) {
+                // Device has emulated storage; external storage paths should have
+                // userId burned into them.
+                final File emulatedBase = new File(rawEmulatedStorageTarget);
+
+                // /storage/emulated/0
+                mExternalStorage = buildPath(emulatedBase, Integer.toString(userId));
+                // /storage/emulated/obb
+                mExternalStorageAndroidObb = buildPath(emulatedBase, "obb");
+
+            } else {
+                // Device has physical external storage; use plain paths.
+                if (TextUtils.isEmpty(rawExternalStorage)) {
+                    Log.w(TAG, "EXTERNAL_STORAGE undefined; falling back to default");
+                    rawExternalStorage = "/storage/sdcard0";
+                }
+
+                // /storage/sdcard0
+                mExternalStorage = new File(rawExternalStorage);
+                // /storage/sdcard0/Android/obb
+                mExternalStorageAndroidObb = buildPath(mExternalStorage, "Android", "obb");
+            }
+
+            mExternalStorageAndroidData = buildPath(mExternalStorage, "Android", "data");
+            mExternalStorageAndroidMedia = buildPath(mExternalStorage, "Android", "media");
+        }
+
+        public File getExternalStorageDirectory() {
+            return mExternalStorage;
+        }
+
+        public File getExternalStoragePublicDirectory(String type) {
+            return new File(mExternalStorage, type);
+        }
+
+        public File getExternalStorageAndroidDataDir() {
+            return mExternalStorageAndroidData;
+        }
+
+        public File getExternalStorageAppDataDirectory(String packageName) {
+            return new File(mExternalStorageAndroidData, packageName);
+        }
+
+        public File getExternalStorageAppMediaDirectory(String packageName) {
+            return new File(mExternalStorageAndroidMedia, packageName);
+        }
+
+        public File getExternalStorageAppObbDirectory(String packageName) {
+            return new File(mExternalStorageAndroidObb, packageName);
+        }
+
+        public File getExternalStorageAppFilesDirectory(String packageName) {
+            return new File(new File(mExternalStorageAndroidData, packageName), "files");
+        }
+
+        public File getExternalStorageAppCacheDirectory(String packageName) {
+            return new File(new File(mExternalStorageAndroidData, packageName), "cache");
+        }
     }
 
     /**
@@ -137,20 +232,7 @@
     private static final File MEDIA_STORAGE_DIRECTORY
             = getDirectory("MEDIA_STORAGE", "/data/media");
 
-    private static final File EXTERNAL_STORAGE_DIRECTORY
-            = getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0");
-
-    private static final File EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY = new File(new File(
-            getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "data");
-
-    private static final File EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY = new File(new File(
-            getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "media");
-
-    private static final File EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY = new File(new File(
-            getDirectory("EXTERNAL_STORAGE", "/storage/sdcard0"), "Android"), "obb");
-
-    private static final File DOWNLOAD_CACHE_DIRECTORY
-            = getDirectory("DOWNLOAD_CACHE", "/cache");
+    private static final File DOWNLOAD_CACHE_DIRECTORY = getDirectory("DOWNLOAD_CACHE", "/cache");
 
     /**
      * Gets the Android data directory.
@@ -198,7 +280,13 @@
      * @see #isExternalStorageRemovable()
      */
     public static File getExternalStorageDirectory() {
-        return EXTERNAL_STORAGE_DIRECTORY;
+        throwIfSystem();
+        return sCurrentUser.getExternalStorageDirectory();
+    }
+
+    /** {@hide} */
+    public static File getLegacyExternalStorageDirectory() {
+        return new File(System.getenv(ENV_EXTERNAL_STORAGE));
     }
 
     /**
@@ -318,7 +406,8 @@
      * using it such as with {@link File#mkdirs File.mkdirs()}.
      */
     public static File getExternalStoragePublicDirectory(String type) {
-        return new File(getExternalStorageDirectory(), type);
+        throwIfSystem();
+        return sCurrentUser.getExternalStoragePublicDirectory(type);
     }
 
     /**
@@ -326,7 +415,8 @@
      * @hide
      */
     public static File getExternalStorageAndroidDataDir() {
-        return EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY;
+        throwIfSystem();
+        return sCurrentUser.getExternalStorageAndroidDataDir();
     }
     
     /**
@@ -334,7 +424,8 @@
      * @hide
      */
     public static File getExternalStorageAppDataDirectory(String packageName) {
-        return new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY, packageName);
+        throwIfSystem();
+        return sCurrentUser.getExternalStorageAppDataDirectory(packageName);
     }
     
     /**
@@ -342,7 +433,8 @@
      * @hide
      */
     public static File getExternalStorageAppMediaDirectory(String packageName) {
-        return new File(EXTERNAL_STORAGE_ANDROID_MEDIA_DIRECTORY, packageName);
+        throwIfSystem();
+        return sCurrentUser.getExternalStorageAppMediaDirectory(packageName);
     }
     
     /**
@@ -350,7 +442,8 @@
      * @hide
      */
     public static File getExternalStorageAppObbDirectory(String packageName) {
-        return new File(EXTERNAL_STORAGE_ANDROID_OBB_DIRECTORY, packageName);
+        throwIfSystem();
+        return sCurrentUser.getExternalStorageAppObbDirectory(packageName);
     }
     
     /**
@@ -358,17 +451,17 @@
      * @hide
      */
     public static File getExternalStorageAppFilesDirectory(String packageName) {
-        return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY,
-                packageName), "files");
+        throwIfSystem();
+        return sCurrentUser.getExternalStorageAppFilesDirectory(packageName);
     }
-    
+
     /**
      * Generates the path to an application's cache.
      * @hide
      */
     public static File getExternalStorageAppCacheDirectory(String packageName) {
-        return new File(new File(EXTERNAL_STORAGE_ANDROID_DATA_DIRECTORY,
-                packageName), "cache");
+        throwIfSystem();
+        return sCurrentUser.getExternalStorageAppCacheDirectory(packageName);
     }
     
     /**
@@ -441,9 +534,10 @@
         try {
             IMountService mountService = IMountService.Stub.asInterface(ServiceManager
                     .getService("mount"));
-            return mountService.getVolumeState(getExternalStorageDirectory()
-                    .toString());
-        } catch (Exception rex) {
+            final StorageVolume primary = getPrimaryVolume();
+            return mountService.getVolumeState(primary.getPath());
+        } catch (RemoteException rex) {
+            Log.w(TAG, "Failed to read external storage state; assuming REMOVED: " + rex);
             return Environment.MEDIA_REMOVED;
         }
     }
@@ -457,8 +551,8 @@
      * <p>See {@link #getExternalStorageDirectory()} for more information.
      */
     public static boolean isExternalStorageRemovable() {
-        StorageVolume volume = getPrimaryVolume();
-        return (volume != null && volume.isRemovable());
+        final StorageVolume primary = getPrimaryVolume();
+        return (primary != null && primary.isRemovable());
     }
 
     /**
@@ -475,12 +569,30 @@
      * android.content.ComponentName, boolean)} for additional details.
      */
     public static boolean isExternalStorageEmulated() {
-        StorageVolume volume = getPrimaryVolume();
-        return (volume != null && volume.isEmulated());
+        final StorageVolume primary = getPrimaryVolume();
+        return (primary != null && primary.isEmulated());
     }
 
     static File getDirectory(String variableName, String defaultPath) {
         String path = System.getenv(variableName);
         return path == null ? new File(defaultPath) : new File(path);
     }
+
+    private static void throwIfSystem() {
+        if (Process.myUid() == Process.SYSTEM_UID) {
+            Log.wtf(TAG, "Static storage paths aren't available from AID_SYSTEM", new Throwable());
+        }
+    }
+
+    private static File buildPath(File base, String... segments) {
+        File cur = base;
+        for (String segment : segments) {
+            if (cur == null) {
+                cur = new File(segment);
+            } else {
+                cur = new File(cur, segment);
+            }
+        }
+        return cur;
+    }
 }
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index ab64866..0b16316 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -677,15 +677,15 @@
                 return _result;
             }
 
-            public Parcelable[] getVolumeList() throws RemoteException {
+            public StorageVolume[] getVolumeList() throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
-                Parcelable[] _result;
+                StorageVolume[] _result;
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
                     mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
                     _reply.readException();
-                    _result = _reply.readParcelableArray(StorageVolume.class.getClassLoader());
+                    _result = _reply.createTypedArray(StorageVolume.CREATOR);
                 } finally {
                     _reply.recycle();
                     _data.recycle();
@@ -1119,9 +1119,9 @@
                 }
                 case TRANSACTION_getVolumeList: {
                     data.enforceInterface(DESCRIPTOR);
-                    Parcelable[] result = getVolumeList();
+                    StorageVolume[] result = getVolumeList();
                     reply.writeNoException();
-                    reply.writeParcelableArray(result, 0);
+                    reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                     return true;
                 }
                 case TRANSACTION_getSecureContainerFilesystemPath: {
@@ -1358,7 +1358,7 @@
     /**
      * Returns list of all mountable volumes.
      */
-    public Parcelable[] getVolumeList() throws RemoteException;
+    public StorageVolume[] getVolumeList() throws RemoteException;
 
     /**
      * Gets the path on the filesystem for the ASEC container itself.
diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java
index 8a20a6e..54c8709d 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -16,6 +16,8 @@
 
 package android.os.storage;
 
+import android.app.NotificationManager;
+import android.content.Context;
 import android.os.Environment;
 import android.os.Handler;
 import android.os.Looper;
@@ -285,6 +287,11 @@
         }
     }
 
+    /** {@hide} */
+    public static StorageManager from(Context context) {
+        return (StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
+    }
+
     /**
      * Constructs a StorageManager object through which an application can
      * can communicate with the systems mount service.
@@ -594,4 +601,20 @@
         }
         return paths;
     }
+
+    /** {@hide} */
+    public StorageVolume getPrimaryVolume() {
+        return getPrimaryVolume(getVolumeList());
+    }
+
+    /** {@hide} */
+    public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
+        for (StorageVolume volume : volumes) {
+            if (volume.isPrimary()) {
+                return volume;
+            }
+        }
+        Log.w(TAG, "No primary storage defined");
+        return null;
+    }
 }
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index b5983d1..177a955 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -19,16 +19,22 @@
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
+
+import java.io.File;
 
 /**
- * A class representing a storage volume
+ * Description of a storage volume and its capabilities, including the
+ * filesystem path where it may be mounted.
+ *
  * @hide
  */
 public class StorageVolume implements Parcelable {
 
+    // TODO: switch to more durable token
     private int mStorageId;
 
-    private final String mPath;
+    private final File mPath;
     private final int mDescriptionId;
     private final boolean mPrimary;
     private final boolean mRemovable;
@@ -37,14 +43,17 @@
     private final boolean mAllowMassStorage;
     /** Maximum file size for the storage, or zero for no limit */
     private final long mMaxFileSize;
+    /** When set, indicates exclusive ownership of this volume */
+    private final UserHandle mOwner;
 
     // StorageVolume extra for ACTION_MEDIA_REMOVED, ACTION_MEDIA_UNMOUNTED, ACTION_MEDIA_CHECKING,
     // ACTION_MEDIA_NOFS, ACTION_MEDIA_MOUNTED, ACTION_MEDIA_SHARED, ACTION_MEDIA_UNSHARED,
     // ACTION_MEDIA_BAD_REMOVAL, ACTION_MEDIA_UNMOUNTABLE and ACTION_MEDIA_EJECT broadcasts.
     public static final String EXTRA_STORAGE_VOLUME = "storage_volume";
 
-    public StorageVolume(String path, int descriptionId, boolean primary, boolean removable,
-            boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize) {
+    public StorageVolume(File path, int descriptionId, boolean primary, boolean removable,
+            boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize,
+            UserHandle owner) {
         mPath = path;
         mDescriptionId = descriptionId;
         mPrimary = primary;
@@ -53,18 +62,26 @@
         mMtpReserveSpace = mtpReserveSpace;
         mAllowMassStorage = allowMassStorage;
         mMaxFileSize = maxFileSize;
+        mOwner = owner;
     }
 
     private StorageVolume(Parcel in) {
         mStorageId = in.readInt();
-        mPath = in.readString();
+        mPath = new File(in.readString());
         mDescriptionId = in.readInt();
-        mPrimary = in.readByte() != 0;
-        mRemovable = in.readByte() != 0;
-        mEmulated = in.readByte() != 0;
+        mPrimary = in.readInt() != 0;
+        mRemovable = in.readInt() != 0;
+        mEmulated = in.readInt() != 0;
         mMtpReserveSpace = in.readInt();
-        mAllowMassStorage = in.readByte() != 0;
+        mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
+        mOwner = in.readParcelable(null);
+    }
+
+    public static StorageVolume fromTemplate(StorageVolume template, File path, UserHandle owner) {
+        return new StorageVolume(path, template.mDescriptionId, template.mPrimary,
+                template.mRemovable, template.mEmulated, template.mMtpReserveSpace,
+                template.mAllowMassStorage, template.mMaxFileSize, owner);
     }
 
     /**
@@ -73,6 +90,10 @@
      * @return the mount path
      */
     public String getPath() {
+        return mPath.toString();
+    }
+
+    public File getPathFile() {
         return mPath;
     }
 
@@ -164,6 +185,10 @@
         return mMaxFileSize;
     }
 
+    public UserHandle getOwner() {
+        return mOwner;
+    }
+
     @Override
     public boolean equals(Object obj) {
         if (obj instanceof StorageVolume && mPath != null) {
@@ -180,10 +205,19 @@
 
     @Override
     public String toString() {
-        return "StorageVolume [mAllowMassStorage=" + mAllowMassStorage + ", mDescriptionId="
-                + mDescriptionId + ", mEmulated=" + mEmulated + ", mMaxFileSize=" + mMaxFileSize
-                + ", mMtpReserveSpace=" + mMtpReserveSpace + ", mPath=" + mPath + ", mRemovable="
-                + mRemovable + ", mStorageId=" + mStorageId + "]";
+        final StringBuilder builder = new StringBuilder("StorageVolume [");
+        builder.append("mStorageId=").append(mStorageId);
+        builder.append(" mPath=").append(mPath);
+        builder.append(" mDescriptionId=").append(mDescriptionId);
+        builder.append(" mPrimary=").append(mPrimary);
+        builder.append(" mRemovable=").append(mRemovable);
+        builder.append(" mEmulated=").append(mEmulated);
+        builder.append(" mMtpReserveSpace=").append(mMtpReserveSpace);
+        builder.append(" mAllowMassStorage=").append(mAllowMassStorage);
+        builder.append(" mMaxFileSize=").append(mMaxFileSize);
+        builder.append(" mOwner=").append(mOwner);
+        builder.append("]");
+        return builder.toString();
     }
 
     public static final Creator<StorageVolume> CREATOR = new Creator<StorageVolume>() {
@@ -206,7 +240,7 @@
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeInt(mStorageId);
-        parcel.writeString(mPath);
+        parcel.writeString(mPath.toString());
         parcel.writeInt(mDescriptionId);
         parcel.writeInt(mPrimary ? 1 : 0);
         parcel.writeInt(mRemovable ? 1 : 0);
@@ -214,5 +248,6 @@
         parcel.writeInt(mMtpReserveSpace);
         parcel.writeInt(mAllowMassStorage ? 1 : 0);
         parcel.writeLong(mMaxFileSize);
+        parcel.writeParcelable(mOwner, flags);
     }
 }
diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java
index 08621ea..54a2273 100755
--- a/core/java/android/provider/ContactsContract.java
+++ b/core/java/android/provider/ContactsContract.java
@@ -7900,6 +7900,16 @@
                 "com.android.contacts.action.GET_MULTIPLE_PHONES";
 
         /**
+         * A broadcast action which is sent when any change has been made to the profile, such
+         * as the profile name or the picture.  A receiver must have
+         * the android.permission.READ_PROFILE permission.
+         *
+         * @hide
+         */
+        public static final String ACTION_PROFILE_CHANGED =
+                "android.provider.Contacts.PROFILE_CHANGED";
+
+        /**
          * Used with {@link #SHOW_OR_CREATE_CONTACT} to force creating a new
          * contact if no matching contact found. Otherwise, default behavior is
          * to prompt user with dialog before creating.
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 62e1383..395a2cb 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -5568,7 +5568,7 @@
          * to this field.
          */
         @ViewDebug.ExportedProperty(category = "layout")
-        public int startMargin = DEFAULT_RELATIVE;
+        private int startMargin = DEFAULT_RELATIVE;
 
         /**
          * The end margin in pixels of the child.
@@ -5576,7 +5576,7 @@
          * to this field.
          */
         @ViewDebug.ExportedProperty(category = "layout")
-        public int endMargin = DEFAULT_RELATIVE;
+        private int endMargin = DEFAULT_RELATIVE;
 
         /**
          * The default start and end margin.
@@ -5724,6 +5724,17 @@
         }
 
         /**
+         * Sets the relative start margin.
+         *
+         * @param start the start marging size
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
+         */
+        public void setMarginStart(int start) {
+            startMargin = start;
+        }
+
+        /**
          * Returns the start margin in pixels.
          *
          * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginStart
@@ -5742,6 +5753,17 @@
         }
 
         /**
+         * Sets the relative end margin.
+         *
+         * @param end the end marging size
+         *
+         * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
+         */
+        public void setMarginEnd(int end) {
+            endMargin = end;
+        }
+
+        /**
          * Returns the end margin in pixels.
          *
          * @attr ref android.R.styleable#ViewGroup_MarginLayout_layout_marginEnd
diff --git a/core/java/android/view/WindowInfo.java b/core/java/android/view/WindowInfo.java
index 00875ae..7d16e14 100644
--- a/core/java/android/view/WindowInfo.java
+++ b/core/java/android/view/WindowInfo.java
@@ -45,13 +45,15 @@
 
     public final Rect touchableRegion = new Rect();
 
-    public int type;
+    public int type = UNDEFINED;
 
-    public float compatibilityScale;
+    public float compatibilityScale = UNDEFINED;
 
     public boolean visible;
 
-    public int displayId;
+    public int displayId = UNDEFINED;
+
+    public int layer = UNDEFINED;
 
     private WindowInfo() {
         /* do nothing - reduce visibility */
@@ -71,6 +73,7 @@
         parcel.writeFloat(compatibilityScale);
         parcel.writeInt(visible ? 1 : 0);
         parcel.writeInt(displayId);
+        parcel.writeInt(layer);
         recycle();
     }
 
@@ -82,6 +85,7 @@
         compatibilityScale = parcel.readFloat();
         visible = (parcel.readInt() == 1);
         displayId = parcel.readInt();
+        layer = parcel.readInt();
     }
 
     public static WindowInfo obtain(WindowInfo other) {
@@ -90,9 +94,10 @@
         info.frame.set(other.frame);
         info.touchableRegion.set(other.touchableRegion);
         info.type = other.type;
-        info.displayId = other.displayId;
         info.compatibilityScale = other.compatibilityScale;
         info.visible = other.visible;
+        info.displayId = other.displayId;
+        info.layer = other.layer;
         return info;
     }
 
@@ -131,8 +136,25 @@
         frame.setEmpty();
         touchableRegion.setEmpty();
         type = UNDEFINED;
-        displayId = UNDEFINED;
+        compatibilityScale = UNDEFINED;
         visible = false;
+        displayId = UNDEFINED;
+        layer = UNDEFINED;
+    }
+
+    @Override
+    public String toString() {
+        StringBuilder builder = new StringBuilder();
+        builder.append("Window [token:").append((token != null) ? token.hashCode() : null);
+        builder.append(", displayId:").append(displayId);
+        builder.append(", type:").append(type);
+        builder.append(", visible:").append(visible);
+        builder.append(", layer:").append(layer);
+        builder.append(", compatibilityScale:").append(compatibilityScale);
+        builder.append(", frame:").append(frame);
+        builder.append(", touchableRegion:").append(touchableRegion);
+        builder.append("]");
+        return builder.toString();
     }
 
     /**
diff --git a/core/java/android/webkit/CertTool.java b/core/java/android/webkit/CertTool.java
index a2325c3..e4d09a9 100644
--- a/core/java/android/webkit/CertTool.java
+++ b/core/java/android/webkit/CertTool.java
@@ -16,6 +16,7 @@
 
 package android.webkit;
 
+import com.android.org.bouncycastle.asn1.ASN1Encoding;
 import com.android.org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
 import com.android.org.bouncycastle.asn1.x509.AlgorithmIdentifier;
 import com.android.org.bouncycastle.jce.netscape.NetscapeCertRequest;
@@ -57,7 +58,7 @@
             NetscapeCertRequest request = new NetscapeCertRequest(challenge,
                     MD5_WITH_RSA, pair.getPublic());
             request.sign(pair.getPrivate());
-            byte[] signed = request.toASN1Object().getDEREncoded();
+            byte[] signed = request.toASN1Primitive().getEncoded(ASN1Encoding.DER);
 
             Credentials.getInstance().install(context, pair);
             return new String(Base64.encode(signed));
diff --git a/core/java/android/webkit/JavascriptInterface.java b/core/java/android/webkit/JavascriptInterface.java
index 3f1ed12..6cd2a7b 100644
--- a/core/java/android/webkit/JavascriptInterface.java
+++ b/core/java/android/webkit/JavascriptInterface.java
@@ -25,9 +25,8 @@
  * Annotation that allows exposing methods to JavaScript. Starting from API level
  * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} and above, only methods explicitly
  * marked with this annotation are available to the Javascript code. See
- * {@link android.webkit.Webview#addJavaScriptInterface} for more information about it.
+ * {@link android.webkit.WebView#addJavascriptInterface} for more information about it.
  *
- * @hide
  */
 @SuppressWarnings("javadoc")
 @Retention(RetentionPolicy.RUNTIME)
diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java
index e8ff01f..a29ff37 100644
--- a/core/java/android/webkit/WebSettings.java
+++ b/core/java/android/webkit/WebSettings.java
@@ -102,7 +102,12 @@
 
     /**
      * Normal cache usage mode. Use with {@link #setCacheMode}.
+     *
+     * @deprecated This value is obsolete, as from API level
+     * {@link android.os.Build.VERSION_CODES#HONEYCOMB} and onwards it has the
+     * same effect as {@link #LOAD_DEFAULT}.
      */
+    @Deprecated
     public static final int LOAD_NORMAL = 0;
 
     /**
@@ -333,7 +338,10 @@
      * If it is true, WebView will choose a solution to maximize the performance.
      * e.g. the WebView's content may not be updated during the transition.
      * If it is false, WebView will keep its fidelity. The default value is false.
+     *
+     * @deprecated This method is now obsolete, and will become a no-op in future.
      */
+    @Deprecated
     public void setEnableSmoothTransition(boolean enable) {
         throw new MustOverrideException();
     }
@@ -343,7 +351,10 @@
      * zooming.
      *
      * @see #setEnableSmoothTransition
+     *
+     * @deprecated This method is now obsolete, and will become a no-op in future.
      */
+    @Deprecated
     public boolean enableSmoothTransition() {
         throw new MustOverrideException();
     }
diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java
index 9d6d929..e1c30f7 100644
--- a/core/java/android/webkit/WebView.java
+++ b/core/java/android/webkit/WebView.java
@@ -26,7 +26,6 @@
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.net.http.SslCertificate;
-import android.os.Build;
 import android.os.Bundle;
 import android.os.Looper;
 import android.os.Message;
@@ -470,7 +469,13 @@
      * @param defStyle the default style resource ID
      * @param privateBrowsing whether this WebView will be initialized in
      *                        private mode
+     *
+     * @deprecated Private browsing is no longer supported directly via 
+     * WebView and will be removed in a future release. Prefer using
+     * {@link WebSettings}, {@link WebViewDatabase}, {@link CookieManager}
+     * and {@link WebStorage} for fine-grained control of privacy data.
      */
+    @Deprecated
     public WebView(Context context, AttributeSet attrs, int defStyle,
             boolean privateBrowsing) {
         this(context, attrs, defStyle, null, privateBrowsing);
@@ -569,7 +574,11 @@
 
     /**
      * Sets the SSL certificate for the main top-level page.
+     *
+     * @deprecated Calling this function has no useful effect, and will be
+     * ignored in future releases.
      */
+    @Deprecated
     public void setCertificate(SslCertificate certificate) {
         checkThread();
         mProvider.setCertificate(certificate);
@@ -1018,7 +1027,12 @@
      * Gets the current scale of this WebView.
      *
      * @return the current scale
+     *
+     * @deprecated This method is prone to inaccuracy due to race conditions
+     * between the web rendering and UI threads; prefer
+     * {@link WebViewClient#onScaleChanged}.
      */
+    @Deprecated
     @ViewDebug.ExportedProperty(category = "webview")
     public float getScale() {
         checkThread();
@@ -1479,10 +1493,22 @@
     /**
      * Injects the supplied Java object into this WebView. The object is
      * injected into the JavaScript context of the main frame, using the
-     * supplied name. This allows the Java object's public methods to be
-     * accessed from JavaScript. Note that that injected objects will not
+     * supplied name. This allows the Java object's methods to be
+     * accessed from JavaScript. For applications targeted to API
+     * level {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1}
+     * and above, only public methods that are annotated with
+     * {@link android.webkit.JavascriptInterface} can be accessed from JavaScript.
+     * For applications targeted to API level {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below,
+     * all public methods (including the inherited ones) can be accessed, see the
+     * important security note below for implications.
+     * <p> Note that injected objects will not
      * appear in JavaScript until the page is next (re)loaded. For example:
-     * <pre> webView.addJavascriptInterface(new Object(), "injectedObject");
+     * <pre>
+     * class JsObject {
+     *    {@literal @}JavascriptInterface
+     *    public String toString() { return "injectedObject"; }
+     * }
+     * webView.addJavascriptInterface(new JsObject(), "injectedObject");
      * webView.loadData("<!DOCTYPE html><title></title>", "text/html", null);
      * webView.loadUrl("javascript:alert(injectedObject.toString())");</pre>
      * <p>
@@ -1490,7 +1516,9 @@
      * <ul>
      * <li> This method can be used to allow JavaScript to control the host
      * application. This is a powerful feature, but also presents a security
-     * risk, particularly as JavaScript could use reflection to access an
+     * risk for applications targeted to API level
+     * {@link android.os.Build.VERSION_CODES#JELLY_BEAN} or below, because
+     * JavaScript could use reflection to access an
      * injected object's public fields. Use of this method in a WebView
      * containing untrusted content could allow an attacker to manipulate the
      * host application in unintended ways, executing Java code with the
@@ -1499,6 +1527,7 @@
      * <li> JavaScript interacts with Java object on a private, background
      * thread of this WebView. Care is therefore required to maintain thread
      * safety.</li>
+     * <li> The Java object's fields are not accessible.</li>
      * </ul>
      *
      * @param object the Java object to inject into this WebView's JavaScript
@@ -1508,9 +1537,6 @@
     public void addJavascriptInterface(Object object, String name) {
         checkThread();
         mProvider.addJavascriptInterface(object, name);
-        // TODO in a separate CL provide logic to enable annotations for API level JB_MR1 and above. Don't forget to
-        // update the doc, set a link to annotation and unhide the annotation.
-        // also describe that fields of java objects are not accessible from JS.
     }
 
     /**
@@ -1598,6 +1624,10 @@
     public void onGlobalFocusChanged(View oldFocus, View newFocus) {
     }
 
+    /**
+     * @deprecated Only the default case, true, will be supported in a future version.
+     */
+    @Deprecated
     public void setMapTrackballToArrowKeys(boolean setMap) {
         checkThread();
         mProvider.setMapTrackballToArrowKeys(setMap);
@@ -1631,7 +1661,12 @@
      * Gets whether this WebView can be zoomed in.
      *
      * @return true if this WebView can be zoomed in
+     *
+     * @deprecated This method is prone to inaccuracy due to race conditions
+     * between the web rendering and UI threads; prefer
+     * {@link WebViewClient#onScaleChanged}.
      */
+    @Deprecated
     public boolean canZoomIn() {
         checkThread();
         return mProvider.canZoomIn();
@@ -1641,7 +1676,12 @@
      * Gets whether this WebView can be zoomed out.
      *
      * @return true if this WebView can be zoomed out
+     *
+     * @deprecated This method is prone to inaccuracy due to race conditions
+     * between the web rendering and UI threads; prefer
+     * {@link WebViewClient#onScaleChanged}.
      */
+    @Deprecated
     public boolean canZoomOut() {
         checkThread();
         return mProvider.canZoomOut();
diff --git a/core/java/android/webkit/WebViewClassic.java b/core/java/android/webkit/WebViewClassic.java
index a2c1575..d23f52c 100644
--- a/core/java/android/webkit/WebViewClassic.java
+++ b/core/java/android/webkit/WebViewClassic.java
@@ -55,6 +55,7 @@
 import android.net.Uri;
 import android.net.http.SslCertificate;
 import android.os.AsyncTask;
+import android.os.Build;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Looper;
@@ -4119,10 +4120,17 @@
             return;
         }
         WebViewCore.JSInterfaceData arg = new WebViewCore.JSInterfaceData();
-        // TODO in a separate CL provide logic to enable annotations for API level JB_MR1 and above.
+
         arg.mObject = object;
         arg.mInterfaceName = name;
-        arg.mRequireAnnotation = false;
+
+        // starting with JELLY_BEAN_MR1, annotations are mandatory for enabling access to
+        // methods that are accessible from JS.
+        if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
+            arg.mRequireAnnotation = true;
+        } else {
+            arg.mRequireAnnotation = false;
+        }
         mWebViewCore.sendMessage(EventHub.ADD_JS_INTERFACE, arg);
     }
 
diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java
index 34f78c6..62253d3 100644
--- a/core/java/android/widget/AbsSeekBar.java
+++ b/core/java/android/widget/AbsSeekBar.java
@@ -304,7 +304,8 @@
         }
         
         // Canvas will be translated, so 0,0 is where we start drawing
-        thumb.setBounds(thumbPos, topBound, thumbPos + thumbWidth, bottomBound);
+        final int left = isLayoutRtl() ? available - thumbPos : thumbPos;
+        thumb.setBounds(left, topBound, left + thumbWidth, bottomBound);
     }
 
     @Override
diff --git a/core/java/android/widget/Switch.java b/core/java/android/widget/Switch.java
index 1f713d4..689bd02 100644
--- a/core/java/android/widget/Switch.java
+++ b/core/java/android/widget/Switch.java
@@ -750,10 +750,11 @@
         mTextPaint.drawableState = getDrawableState();
 
         Layout switchText = getTargetCheckedState() ? mOnLayout : mOffLayout;
-
-        canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
-                (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
-        switchText.draw(canvas);
+        if (switchText != null) {
+            canvas.translate((thumbLeft + thumbRight) / 2 - switchText.getWidth() / 2,
+                    (switchInnerTop + switchInnerBottom) / 2 - switchText.getHeight() / 2);
+            switchText.draw(canvas);
+        }
 
         canvas.restore();
     }
diff --git a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
index 3905c88..fb7f215 100644
--- a/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
+++ b/core/java/com/android/internal/os/storage/ExternalStorageFormatter.java
@@ -122,7 +122,7 @@
     public void onCancel(DialogInterface dialog) {
         IMountService mountService = getMountService();
         String extStoragePath = mStorageVolume == null ?
-                Environment.getExternalStorageDirectory().toString() :
+                Environment.getLegacyExternalStorageDirectory().toString() :
                 mStorageVolume.getPath();
         try {
             mountService.mountVolume(extStoragePath);
@@ -149,7 +149,7 @@
             updateProgressDialog(R.string.progress_unmounting);
             IMountService mountService = getMountService();
             final String extStoragePath = mStorageVolume == null ?
-                    Environment.getExternalStorageDirectory().toString() :
+                    Environment.getLegacyExternalStorageDirectory().toString() :
                     mStorageVolume.getPath();
             try {
                 // Remove encryption mapping if this is an unmount for a factory reset.
@@ -163,7 +163,7 @@
             updateProgressDialog(R.string.progress_erasing);
             final IMountService mountService = getMountService();
             final String extStoragePath = mStorageVolume == null ?
-                    Environment.getExternalStorageDirectory().toString() :
+                    Environment.getLegacyExternalStorageDirectory().toString() :
                     mStorageVolume.getPath();
             if (mountService != null) {
                 new Thread() {
diff --git a/core/jni/android/graphics/TextLayoutCache.cpp b/core/jni/android/graphics/TextLayoutCache.cpp
index 7abfcf1..8032ed8 100644
--- a/core/jni/android/graphics/TextLayoutCache.cpp
+++ b/core/jni/android/graphics/TextLayoutCache.cpp
@@ -78,8 +78,10 @@
 /*
  * Cache clearing
  */
-void TextLayoutCache::clear() {
+void TextLayoutCache::purgeCaches() {
+    AutoMutex _l(mLock);
     mCache.clear();
+    mShaper->purgeCaches();
 }
 
 /*
@@ -965,8 +967,7 @@
 
 void TextLayoutEngine::purgeCaches() {
 #if USE_TEXT_LAYOUT_CACHE
-    mTextLayoutCache->clear();
-    mShaper->purgeCaches();
+    mTextLayoutCache->purgeCaches();
 #if DEBUG_GLYPHS
     ALOGD("Purged TextLayoutEngine caches");
 #endif
diff --git a/core/jni/android/graphics/TextLayoutCache.h b/core/jni/android/graphics/TextLayoutCache.h
index 64b33a0..1f4e22c 100644
--- a/core/jni/android/graphics/TextLayoutCache.h
+++ b/core/jni/android/graphics/TextLayoutCache.h
@@ -276,7 +276,7 @@
     /**
      * Clear the cache
      */
-    void clear();
+    void purgeCaches();
 
 private:
     TextLayoutShaper* mShaper;
diff --git a/core/res/res/layout-land/keyguard_host_view.xml b/core/res/res/layout-land/keyguard_host_view.xml
index 8e7c232..01e1866 100644
--- a/core/res/res/layout-land/keyguard_host_view.xml
+++ b/core/res/res/layout-land/keyguard_host_view.xml
@@ -35,6 +35,8 @@
 
         <!-- TODO: Remove this once supported as a widget -->
         <include layout="@layout/keyguard_status_view"/>
+        <include layout="@layout/keyguard_transport_control_view"/>
+
     </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
 
 
diff --git a/core/res/res/layout-sw600dp-land/keyguard_host_view.xml b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
index 652bdde..ea0b3ba 100644
--- a/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
+++ b/core/res/res/layout-sw600dp-land/keyguard_host_view.xml
@@ -35,6 +35,7 @@
 
         <!-- TODO: Remove this once supported as a widget -->
         <include layout="@layout/keyguard_status_view"/>
+        <include layout="@layout/keyguard_transport_control_view"/>
 
     </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
 
diff --git a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
index 0c1dd0c..84b1b03 100644
--- a/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
+++ b/core/res/res/layout-sw600dp-port/keyguard_host_view.xml
@@ -25,8 +25,7 @@
     android:orientation="vertical"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
-    android:gravity="center_horizontal"
-    android:clipChildren="false">
+    android:gravity="center_horizontal">
 
     <com.android.internal.policy.impl.keyguard.KeyguardWidgetPager
         android:id="@+id/app_widget_container"
@@ -37,6 +36,7 @@
 
         <!-- TODO: Remove this once supported as a widget -->
         <include layout="@layout/keyguard_status_view"/>
+        <include layout="@layout/keyguard_transport_control_view"/>
 
     </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
 
diff --git a/core/res/res/layout/keyguard_selector_view.xml b/core/res/res/layout/keyguard_selector_view.xml
index d7f98f9..b090f13 100644
--- a/core/res/res/layout/keyguard_selector_view.xml
+++ b/core/res/res/layout/keyguard_selector_view.xml
@@ -34,6 +34,7 @@
         android:visibility="gone">
             <!-- TODO: Remove this when supported as a widget -->
             <include layout="@layout/keyguard_status_view"/>
+            <include layout="@layout/keyguard_transport_control_view"/>
     </com.android.internal.policy.impl.keyguard.KeyguardWidgetPager>
 
     <RelativeLayout
diff --git a/core/res/res/layout/keyguard_status_view.xml b/core/res/res/layout/keyguard_status_view.xml
index 37e6779..5d8020e 100644
--- a/core/res/res/layout/keyguard_status_view.xml
+++ b/core/res/res/layout/keyguard_status_view.xml
@@ -22,6 +22,7 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="wrap_content"
     android:layout_height="wrap_content"
+    android:id="@+id/keyguard_status_view"
     android:gravity="center_horizontal">
 
     <com.android.internal.policy.impl.keyguard.KeyguardStatusView
diff --git a/core/res/res/layout/keyguard_transport_control.xml b/core/res/res/layout/keyguard_transport_control_view.xml
similarity index 89%
rename from core/res/res/layout/keyguard_transport_control.xml
rename to core/res/res/layout/keyguard_transport_control_view.xml
index 3e702df..c40aa66 100644
--- a/core/res/res/layout/keyguard_transport_control.xml
+++ b/core/res/res/layout/keyguard_transport_control_view.xml
@@ -14,19 +14,20 @@
      limitations under the License.
 -->
 
-<!-- Note: This file is meant to be included in various password unlock screens. As such,
-     LayoutParams (layout_*) for TransportControlView should *NOT* be specified here,
-     but rather as include tags for this file or the layout will break. -->
-<com.android.internal.widget.TransportControlView
+<!-- This is a view to control music playback in keyguard. -->
+<com.android.internal.policy.impl.keyguard.KeyguardTransportControlView
     xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/transport_controls">
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:gravity="center_horizontal"
+    android:id="@+id/keyguard_transport_control">
 
     <!-- FrameLayout used as scrim to show between album art and buttons -->
     <FrameLayout
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foreground="@drawable/ic_lockscreen_player_background">
-        <!-- We use ImageView for its cropping features, otherwise could be android:background -->
+        <!-- Use ImageView for its cropping features; otherwise could be android:background -->
         <ImageView
             android:id="@+id/albumart"
             android:layout_width="match_parent"
@@ -107,4 +108,4 @@
         </LinearLayout>
     </LinearLayout>
 
-</com.android.internal.widget.TransportControlView>
+</com.android.internal.policy.impl.keyguard.KeyguardTransportControlView>
\ No newline at end of file
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 669ffe7..54dbf2e 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -1343,6 +1343,8 @@
   <java-symbol type="id" name="keyguard_user_name" />
   <java-symbol type="id" name="keyguard_active_user" />
   <java-symbol type="id" name="keyguard_inactive_users" />
+  <java-symbol type="id" name="keyguard_transport_control" />
+  <java-symbol type="id" name="keyguard_status_view" />
   <java-symbol type="integer" name="config_carDockRotation" />
   <java-symbol type="integer" name="config_defaultUiModeType" />
   <java-symbol type="integer" name="config_deskDockRotation" />
diff --git a/docs/html/guide/topics/ui/themes.jd b/docs/html/guide/topics/ui/themes.jd
index d787492..bc1c4f0 100644
--- a/docs/html/guide/topics/ui/themes.jd
+++ b/docs/html/guide/topics/ui/themes.jd
@@ -413,8 +413,8 @@
 themes will give you a better understanding of what style properties each one provides.
 For a better reference to the Android styles and themes, see the following source code:</p>
 <ul>
-	<li><a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/res/res/values/styles.xml;h=d7b654e49809cb97a35682754b1394af5c8bc88b;hb=HEAD">Android Styles (styles.xml)</a></li>
-	<li><a href="http://android.git.kernel.org/?p=platform/frameworks/base.git;a=blob;f=core/res/res/values/themes.xml;h=6b3d7407d1c895a3c297e60d5beac98e2d34c271;hb=HEAD">Android Themes (themes.xml)</a></li>
+	<li><a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/values/styles.xml">Android Styles (styles.xml)</a></li>
+	<li><a href="https://android.googlesource.com/platform/frameworks/base/+/refs/heads/master/core/res/res/values/themes.xml">Android Themes (themes.xml)</a></li>
 </ul>
 
 <p>These files will help you learn through example. For instance, in the Android themes source code,
@@ -422,9 +422,8 @@
 you'll see all of the properties that are used to style dialogs that are used by the Android
 framework.</p>
 
-<p>For more information about the syntax used to create styles in XML, see
-<a href="{@docRoot}guide/topics/resources/available-resources.html#stylesandthemes">Available Resource Types:
-Style and Themes</a>.</p>
+<p>For more information about the syntax for styles and themes in XML, see the
+<a href="{@docRoot}guide/topics/resources/style-resource.html">Style Resource</a> document.</p>
 
 <p>For a reference of available style attributes that you can use to define a style or theme
 (e.g., "windowBackground" or "textAppearance"), see {@link android.R.attr} or the respective
diff --git a/docs/html/sdk/installing/installing-adt.jd b/docs/html/sdk/installing/installing-adt.jd
index 4fc2ca6..feec56df 100644
--- a/docs/html/sdk/installing/installing-adt.jd
+++ b/docs/html/sdk/installing/installing-adt.jd
@@ -63,23 +63,20 @@
 
 <h2 id="Configure">Configure the ADT Plugin</h2>
 
-<p>After you've installed ADT and restarted Eclipse, you
+<p>Once Eclipse restarts, you
   must specify the location of your Android SDK directory:</p>
 
 <ol>
-    <li>Select <strong>Window</strong> &gt; <strong>Preferences...</strong> to open the Preferences
-        panel (on Mac OS X, select <strong>Eclipse</strong> &gt; <strong>Preferences</strong>).</li>
-    <li>Select <strong>Android</strong> from the left panel.</li>
-      <p>You may see a dialog asking whether you want to send usage statistics to Google. If so,
-make your choice and click <strong>Proceed</strong>.</p>
-    <li>For the <em>SDK Location</em> in the main panel, click <strong>Browse...</strong> and
-        locate your downloaded Android SDK directory (such as <code>android-sdk-windows</code>).</li>
-    <li>Click <strong>Apply</strong>, then <strong>OK</strong>.</li>
+    <li>In the "Welcome to Android Development" window that appears, select <strong>Use
+existing SDKs</strong>.</li>
+    <li>Browse and select the location of the Android SDK directory you recently
+downloaded.</li>
+    <li>Click <strong>Next</strong>.</li>
 </ol>
 
 
 <p>If you haven't encountered any errors, you're done setting up ADT
-  and can continue to the next step of the SDK installation.</p>
+  and can continue to <a href="{@docRoot}sdk/installing/next.html">Next Steps</a>.</p>
 
 
 
diff --git a/graphics/java/android/renderscript/ScriptIntrinsicBlur.java b/graphics/java/android/renderscript/ScriptIntrinsicBlur.java
new file mode 100644
index 0000000..56c5426
--- /dev/null
+++ b/graphics/java/android/renderscript/ScriptIntrinsicBlur.java
@@ -0,0 +1,66 @@
+/*
+ * 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.renderscript;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+
+/**
+ * @hide
+ **/
+public class ScriptIntrinsicBlur extends ScriptIntrinsic {
+    private float[] mValues = new float[9];
+    private Allocation mInput;
+
+    ScriptIntrinsicBlur(int id, RenderScript rs) {
+        super(id, rs);
+    }
+
+    /**
+     * Supported elements types are float, float4, uchar, uchar4
+     *
+     *
+     * @param rs
+     * @param e
+     *
+     * @return ScriptIntrinsicConvolve3x3
+     */
+    public static ScriptIntrinsicBlur create(RenderScript rs, Element e) {
+        int id = rs.nScriptIntrinsicCreate(5, e.getID(rs));
+        return new ScriptIntrinsicBlur(id, rs);
+
+    }
+
+    public void setInput(Allocation ain) {
+        mInput = ain;
+        bindAllocation(ain, 1);
+    }
+
+    public void setRadius(float v) {
+        if (v < 0 || v > 25) {
+            throw new RSIllegalArgumentException("Radius out of range (0-25).");
+        }
+        setVar(0, v);
+    }
+
+    public void forEach(Allocation aout) {
+        forEach(0, null, aout, null);
+    }
+
+}
+
diff --git a/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java b/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
new file mode 100644
index 0000000..242623b
--- /dev/null
+++ b/graphics/java/android/renderscript/ScriptIntrinsicConvolve5x5.java
@@ -0,0 +1,66 @@
+/*
+ * 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.renderscript;
+
+import android.util.Log;
+
+/**
+ * @hide
+ **/
+public class ScriptIntrinsicConvolve5x5 extends ScriptIntrinsic {
+    private float[] mValues = new float[25];
+    private Allocation mInput;
+
+    ScriptIntrinsicConvolve5x5(int id, RenderScript rs) {
+        super(id, rs);
+    }
+
+    /**
+     * Supported elements types are float, float4, uchar, uchar4
+     *
+     *
+     * @param rs
+     * @param e
+     *
+     * @return ScriptIntrinsicConvolve5x5
+     */
+    public static ScriptIntrinsicConvolve5x5 create(RenderScript rs, Element e) {
+        int id = rs.nScriptIntrinsicCreate(4, e.getID(rs));
+        return new ScriptIntrinsicConvolve5x5(id, rs);
+
+    }
+
+    public void setInput(Allocation ain) {
+        mInput = ain;
+        bindAllocation(ain, 1);
+    }
+
+    public void setCoefficients(float v[]) {
+        FieldPacker fp = new FieldPacker(25*4);
+        for (int ct=0; ct < mValues.length; ct++) {
+            mValues[ct] = v[ct];
+            fp.addF32(mValues[ct]);
+        }
+        setVar(0, fp);
+    }
+
+    public void forEach(Allocation aout) {
+        forEach(0, null, aout, null);
+    }
+
+}
+
diff --git a/graphics/java/android/renderscript/ScriptIntrinsicLUT.java b/graphics/java/android/renderscript/ScriptIntrinsicLUT.java
new file mode 100644
index 0000000..e7d8d34
--- /dev/null
+++ b/graphics/java/android/renderscript/ScriptIntrinsicLUT.java
@@ -0,0 +1,110 @@
+/*
+ * 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.renderscript;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.util.Log;
+
+
+/**
+ * @hide
+ **/
+public class ScriptIntrinsicLUT extends ScriptIntrinsic {
+    private Matrix4f mMatrix = new Matrix4f();
+    private Allocation mTables;
+    private byte mCache[] = new byte[1024];
+    private boolean mDirty = true;
+
+    ScriptIntrinsicLUT(int id, RenderScript rs) {
+        super(id, rs);
+        mTables = Allocation.createSized(rs, Element.U8(rs), 1024);
+        for (int ct=0; ct < 256; ct++) {
+            mCache[ct] = (byte)ct;
+            mCache[ct + 256] = (byte)ct;
+            mCache[ct + 512] = (byte)ct;
+            mCache[ct + 768] = (byte)ct;
+        }
+        bindAllocation(mTables, 0);
+    }
+
+    /**
+     * Supported elements types are uchar4
+     *
+     * @param rs
+     * @param e
+     *
+     * @return ScriptIntrinsicColorMatrix
+     */
+    public static ScriptIntrinsicLUT create(RenderScript rs, Element e) {
+        int id = rs.nScriptIntrinsicCreate(3, e.getID(rs));
+        return new ScriptIntrinsicLUT(id, rs);
+
+    }
+
+
+    private void validate(int index, int value) {
+        if (index < 0 || index > 255) {
+            throw new RSIllegalArgumentException("Index out of range (0-255).");
+        }
+        if (value < 0 || value > 255) {
+            throw new RSIllegalArgumentException("Value out of range (0-255).");
+        }
+    }
+
+    public void setRed(int index, int value) {
+        validate(index, value);
+        mCache[index] = (byte)value;
+        mDirty = true;
+    }
+
+    public void setGreen(int index, int value) {
+        validate(index, value);
+        mCache[index+256] = (byte)value;
+        mDirty = true;
+    }
+
+    public void setBlue(int index, int value) {
+        validate(index, value);
+        mCache[index+512] = (byte)value;
+        mDirty = true;
+    }
+
+    public void setAlpha(int index, int value) {
+        validate(index, value);
+        mCache[index+768] = (byte)value;
+        mDirty = true;
+    }
+
+
+    /**
+     * Invoke the kernel and apply the matrix to each cell of ain and copy to
+     * aout.
+     *
+     * @param ain Input allocation
+     * @param aout Output allocation
+     */
+    public void forEach(Allocation ain, Allocation aout) {
+        if (mDirty) {
+            mDirty = false;
+            mTables.copyFromUnchecked(mCache);
+        }
+        forEach(0, ain, aout, null);
+    }
+
+}
+
diff --git a/keystore/java/android/security/Credentials.java b/keystore/java/android/security/Credentials.java
index b233ff6..d8109ce 100644
--- a/keystore/java/android/security/Credentials.java
+++ b/keystore/java/android/security/Credentials.java
@@ -20,8 +20,9 @@
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
-import com.android.org.bouncycastle.openssl.PEMReader;
-import com.android.org.bouncycastle.openssl.PEMWriter;
+import com.android.org.bouncycastle.util.io.pem.PemObject;
+import com.android.org.bouncycastle.util.io.pem.PemReader;
+import com.android.org.bouncycastle.util.io.pem.PemWriter;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
@@ -32,6 +33,10 @@
 import java.io.Writer;
 import java.nio.charset.Charsets;
 import java.security.KeyPair;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateEncodingException;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.util.ArrayList;
 import java.util.List;
@@ -108,34 +113,41 @@
     public static final String EXTRA_CA_CERTIFICATES_DATA = "ca_certificates_data";
 
     /**
-     * Convert objects to a PEM format, which is used for
-     * CA_CERTIFICATE, USER_CERTIFICATE, and USER_PRIVATE_KEY
-     * entries.
+     * Convert objects to a PEM format which is used for
+     * CA_CERTIFICATE and USER_CERTIFICATE entries.
      */
-    public static byte[] convertToPem(Object... objects) throws IOException {
+    public static byte[] convertToPem(Certificate... objects)
+            throws IOException, CertificateEncodingException {
         ByteArrayOutputStream bao = new ByteArrayOutputStream();
         Writer writer = new OutputStreamWriter(bao, Charsets.US_ASCII);
-        PEMWriter pw = new PEMWriter(writer);
-        for (Object o : objects) {
-            pw.writeObject(o);
+        PemWriter pw = new PemWriter(writer);
+        for (Certificate o : objects) {
+            pw.writeObject(new PemObject("CERTIFICATE", o.getEncoded()));
         }
         pw.close();
         return bao.toByteArray();
     }
     /**
      * Convert objects from PEM format, which is used for
-     * CA_CERTIFICATE, USER_CERTIFICATE, and USER_PRIVATE_KEY
-     * entries.
+     * CA_CERTIFICATE and USER_CERTIFICATE entries.
      */
-    public static List<Object> convertFromPem(byte[] bytes) throws IOException {
+    public static List<X509Certificate> convertFromPem(byte[] bytes)
+            throws IOException, CertificateException {
         ByteArrayInputStream bai = new ByteArrayInputStream(bytes);
         Reader reader = new InputStreamReader(bai, Charsets.US_ASCII);
-        PEMReader pr = new PEMReader(reader);
+        PemReader pr = new PemReader(reader);
 
-        List<Object> result = new ArrayList<Object>();
-        Object o;
-        while ((o = pr.readObject()) != null) {
-            result.add(o);
+        CertificateFactory cf = CertificateFactory.getInstance("X509");
+
+        List<X509Certificate> result = new ArrayList<X509Certificate>();
+        PemObject o;
+        while ((o = pr.readPemObject()) != null) {
+            if (o.getType().equals("CERTIFICATE")) {
+                Certificate c = cf.generateCertificate(new ByteArrayInputStream(o.getContent()));
+                result.add((X509Certificate) c);
+            } else {
+                throw new IllegalArgumentException("Unknown type " + o.getType());
+            }
         }
         pr.close();
         return result;
diff --git a/libs/hwui/DisplayListRenderer.cpp b/libs/hwui/DisplayListRenderer.cpp
index 755170f..c3444e6 100644
--- a/libs/hwui/DisplayListRenderer.cpp
+++ b/libs/hwui/DisplayListRenderer.cpp
@@ -1412,8 +1412,12 @@
     mSnapshot = new Snapshot(mFirstSnapshot,
             SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag);
     mSaveCount = 1;
+
     mSnapshot->setClip(0.0f, 0.0f, mWidth, mHeight);
+    mDirtyClip = opaque;
+
     mRestoreSaveCount = -1;
+
     return DrawGlInfo::kStatusDone; // No invalidate needed at record-time
 }
 
diff --git a/libs/hwui/LayerRenderer.cpp b/libs/hwui/LayerRenderer.cpp
index 5a8f2b7..02af5e2 100644
--- a/libs/hwui/LayerRenderer.cpp
+++ b/libs/hwui/LayerRenderer.cpp
@@ -332,10 +332,10 @@
         if (Caches::getInstance().extensions.hasDiscardFramebuffer()) {
             GLuint previousFbo;
             glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*) &previousFbo);
-
-            GLenum attachments = GL_COLOR_ATTACHMENT0;
             if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, fbo);
-            glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, &attachments);
+
+            const GLenum attachments[] = { GL_COLOR_ATTACHMENT0 };
+            glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, attachments);
 
             if (fbo != previousFbo) glBindFramebuffer(GL_FRAMEBUFFER, previousFbo);
         }
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index f4c2675..02448e8 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -173,6 +173,15 @@
     mSnapshot->setClip(left, top, right, bottom);
     mDirtyClip = opaque;
 
+    // If we know that we are going to redraw the entire framebuffer,
+    // perform a discard to let the driver know we don't need to preserve
+    // the back buffer for this frame.
+    if (mCaches.extensions.hasDiscardFramebuffer() &&
+            left <= 0.0f && top <= 0.0f && right >= mWidth && bottom >= mHeight) {
+        const GLenum attachments[] = { getTargetFbo() == 0 ? GL_COLOR_EXT : GL_COLOR_ATTACHMENT0 };
+        glDiscardFramebufferEXT(GL_FRAMEBUFFER, 1, attachments);
+    }
+
     syncState();
 
     if (!opaque) {
diff --git a/media/tests/EffectsTest/res/layout/bassboosttest.xml b/media/tests/EffectsTest/res/layout/bassboosttest.xml
index 0888e98..ac912c8 100755
--- a/media/tests/EffectsTest/res/layout/bassboosttest.xml
+++ b/media/tests/EffectsTest/res/layout/bassboosttest.xml
@@ -105,6 +105,8 @@
             style="@android:style/TextAppearance.Medium" />
 
         <EditText android:id="@+id/sessionEdit"
+            android:singleLine="true"
+            android:numeric="integer"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/res/layout/equalizertest.xml b/media/tests/EffectsTest/res/layout/equalizertest.xml
index 2223c48..5ef035d 100755
--- a/media/tests/EffectsTest/res/layout/equalizertest.xml
+++ b/media/tests/EffectsTest/res/layout/equalizertest.xml
@@ -105,6 +105,8 @@
             style="@android:style/TextAppearance.Medium" />
 
         <EditText android:id="@+id/sessionEdit"
+            android:singleLine="true"
+            android:numeric="integer"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/res/layout/presetreverbtest.xml b/media/tests/EffectsTest/res/layout/presetreverbtest.xml
index b648899..cd7fbd3 100755
--- a/media/tests/EffectsTest/res/layout/presetreverbtest.xml
+++ b/media/tests/EffectsTest/res/layout/presetreverbtest.xml
@@ -105,6 +105,8 @@
             style="@android:style/TextAppearance.Medium" />
 
         <EditText android:id="@+id/sessionEdit"
+            android:singleLine="true"
+            android:numeric="integer"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/res/layout/virtualizertest.xml b/media/tests/EffectsTest/res/layout/virtualizertest.xml
index c9203de..1fafeab 100755
--- a/media/tests/EffectsTest/res/layout/virtualizertest.xml
+++ b/media/tests/EffectsTest/res/layout/virtualizertest.xml
@@ -105,6 +105,8 @@
             style="@android:style/TextAppearance.Medium" />
 
         <EditText android:id="@+id/sessionEdit"
+            android:singleLine="true"
+            android:numeric="integer"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/res/layout/visualizertest.xml b/media/tests/EffectsTest/res/layout/visualizertest.xml
index 8611e8c..50ac7bb 100755
--- a/media/tests/EffectsTest/res/layout/visualizertest.xml
+++ b/media/tests/EffectsTest/res/layout/visualizertest.xml
@@ -105,6 +105,8 @@
             style="@android:style/TextAppearance.Medium" />
 
         <EditText android:id="@+id/sessionEdit"
+            android:singleLine="true"
+            android:numeric="integer"
             android:layout_width="fill_parent"
             android:layout_height="wrap_content"
             android:layout_weight="0.5"
diff --git a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
index 6612766..7020246 100755
--- a/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
+++ b/media/tests/EffectsTest/src/com/android/effectstest/EffectsTest.java
@@ -32,6 +32,7 @@
 import android.widget.BaseAdapter;
 import android.widget.LinearLayout;
 import android.media.audiofx.AudioEffect;
+
 import java.util.UUID;
 
 public class EffectsTest extends Activity {
@@ -154,13 +155,35 @@
             this.setOrientation(VERTICAL);
         }
 
+        public String effectUuidToString(UUID effectType) {
+            if (effectType.equals(AudioEffect.EFFECT_TYPE_VIRTUALIZER)) {
+                return "Virtualizer";
+            } else if (effectType.equals(AudioEffect.EFFECT_TYPE_ENV_REVERB)){
+                return "Reverb";
+            } else if (effectType.equals(AudioEffect.EFFECT_TYPE_PRESET_REVERB)){
+                return "Preset Reverb";
+            } else if (effectType.equals(AudioEffect.EFFECT_TYPE_EQUALIZER)){
+                return "Equalizer";
+            } else if (effectType.equals(AudioEffect.EFFECT_TYPE_BASS_BOOST)){
+                return "Bass Boost";
+            } else if (effectType.equals(AudioEffect.EFFECT_TYPE_AGC)){
+                return "Automatic Gain Control";
+            } else if (effectType.equals(AudioEffect.EFFECT_TYPE_AEC)){
+                return "Acoustic Echo Canceler";
+            } else if (effectType.equals(AudioEffect.EFFECT_TYPE_NS)){
+                return "Noise Suppressor";
+            }
+
+            return effectType.toString();
+        }
+
         public void set(int position) {
             TextView tv = new TextView(mContext);
             tv.setText("Effect "+ position);
             addView(tv, new LinearLayout.LayoutParams(
                     LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
             tv = new TextView(mContext);
-            tv.setText(" type: "+ mDescriptors[position].type.toString());
+            tv.setText(" type: "+ effectUuidToString(mDescriptors[position].type));
             addView(tv, new LinearLayout.LayoutParams(
                     LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT));
             tv = new TextView(mContext);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 6773482..523b95e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -61,7 +61,7 @@
 
 public class SettingsProvider extends ContentProvider {
     private static final String TAG = "SettingsProvider";
-    private static final boolean LOCAL_LOGV = true;
+    private static final boolean LOCAL_LOGV = false;
 
     private static final String TABLE_SYSTEM = "system";
     private static final String TABLE_SECURE = "secure";
@@ -422,10 +422,6 @@
                     }
                 }
             }, userFilter);
-
-            if (!ensureAndroidIdIsSet()) {
-                return false;
-            }
         }
         return true;
     }
@@ -473,6 +469,8 @@
         sObserverInstances.append(userHandle, observer);
         observer.startWatching();
 
+        ensureAndroidIdIsSet(userHandle);
+
         startAsyncCachePopulation(userHandle);
     }
 
@@ -537,24 +535,27 @@
         }
     }
 
-    private boolean ensureAndroidIdIsSet() {
-        final Cursor c = query(Settings.Secure.CONTENT_URI,
+    private boolean ensureAndroidIdIsSet(int userHandle) {
+        final Cursor c = queryForUser(Settings.Secure.CONTENT_URI,
                 new String[] { Settings.NameValueTable.VALUE },
                 Settings.NameValueTable.NAME + "=?",
-                new String[] { Settings.Secure.ANDROID_ID }, null);
+                new String[] { Settings.Secure.ANDROID_ID }, null,
+                userHandle);
         try {
             final String value = c.moveToNext() ? c.getString(0) : null;
             if (value == null) {
                 final SecureRandom random = new SecureRandom();
                 final String newAndroidIdValue = Long.toHexString(random.nextLong());
-                Log.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue + "]");
                 final ContentValues values = new ContentValues();
                 values.put(Settings.NameValueTable.NAME, Settings.Secure.ANDROID_ID);
                 values.put(Settings.NameValueTable.VALUE, newAndroidIdValue);
-                final Uri uri = insert(Settings.Secure.CONTENT_URI, values);
+                final Uri uri = insertForUser(Settings.Secure.CONTENT_URI, values, userHandle);
                 if (uri == null) {
+                    Slog.e(TAG, "Unable to generate new ANDROID_ID for user " + userHandle);
                     return false;
                 }
+                Slog.d(TAG, "Generated and saved new ANDROID_ID [" + newAndroidIdValue
+                        + "] for user " + userHandle);
             }
             return true;
         } finally {
@@ -732,12 +733,17 @@
 
     @Override
     public Cursor query(Uri url, String[] select, String where, String[] whereArgs, String sort) {
-        final int callingUser = UserHandle.getCallingUserId();
-        if (LOCAL_LOGV) Slog.v(TAG, "query() for user " + callingUser);
+        return queryForUser(url, select, where, whereArgs, sort, UserHandle.getCallingUserId());
+    }
+
+    public Cursor queryForUser(Uri url, String[] select, String where, String[] whereArgs,
+            String sort, int forUser) {
+        if (LOCAL_LOGV) Slog.v(TAG, "query(" + url + ") for user " + forUser);
         SqlArguments args = new SqlArguments(url, where, whereArgs);
         DatabaseHelper dbH;
         synchronized (this) {
-            dbH = getOrEstablishDatabaseLocked(callingUser);
+            dbH = getOrEstablishDatabaseLocked(
+                    TABLE_GLOBAL.equals(args.table) ? UserHandle.USER_OWNER : forUser);
         }
         SQLiteDatabase db = dbH.getReadableDatabase();
 
@@ -795,7 +801,8 @@
         mutationCount.incrementAndGet();
         DatabaseHelper dbH;
         synchronized (this) {
-            dbH = getOrEstablishDatabaseLocked(callingUser);
+            dbH = getOrEstablishDatabaseLocked(
+                    TABLE_GLOBAL.equals(args.table) ? UserHandle.USER_OWNER : callingUser);
         }
         SQLiteDatabase db = dbH.getWritableDatabase();
         db.beginTransaction();
@@ -898,7 +905,7 @@
     private Uri insertForUser(Uri url, ContentValues initialValues, int desiredUserHandle) {
         final int callingUser = UserHandle.getCallingUserId();
         if (callingUser != desiredUserHandle) {
-            getContext().enforceCallingPermission(
+            getContext().enforceCallingOrSelfPermission(
                     android.Manifest.permission.INTERACT_ACROSS_USERS_FULL,
                     "Not permitted to access settings for other users");
         }
@@ -934,7 +941,7 @@
         mutationCount.incrementAndGet();
         DatabaseHelper dbH;
         synchronized (this) {
-            dbH = getOrEstablishDatabaseLocked(callingUser);
+            dbH = getOrEstablishDatabaseLocked(desiredUserHandle);
         }
         SQLiteDatabase db = dbH.getWritableDatabase();
         final long rowId = db.insert(args.table, null, initialValues);
@@ -952,13 +959,15 @@
 
     @Override
     public int delete(Uri url, String where, String[] whereArgs) {
-        final int callingUser = UserHandle.getCallingUserId();
+        int callingUser = UserHandle.getCallingUserId();
         if (LOCAL_LOGV) Slog.v(TAG, "delete() for user " + callingUser);
         SqlArguments args = new SqlArguments(url, where, whereArgs);
         if (TABLE_FAVORITES.equals(args.table)) {
             return 0;
         } else if (TABLE_OLD_FAVORITES.equals(args.table)) {
             args.table = TABLE_FAVORITES;
+        } else if (TABLE_GLOBAL.equals(args.table)) {
+            callingUser = UserHandle.USER_OWNER;
         }
         checkWritePermissions(args);
 
@@ -987,11 +996,13 @@
         // intended effect (the update will be invisible to the rest of the system).
         // This should have no practical effect, since writes to the Secure db can only
         // be done by system code, and that code should be using the correct API up front.
-        final int callingUser = UserHandle.getCallingUserId();
+        int callingUser = UserHandle.getCallingUserId();
         if (LOCAL_LOGV) Slog.v(TAG, "update() for user " + callingUser);
         SqlArguments args = new SqlArguments(url, where, whereArgs);
         if (TABLE_FAVORITES.equals(args.table)) {
             return 0;
+        } else if (TABLE_GLOBAL.equals(args.table)) {
+            callingUser = UserHandle.USER_OWNER;
         }
         checkWritePermissions(args);
 
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 8fb703f..6aed1aa 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -155,13 +155,13 @@
     <string name="always_use_accessory">Use by default for this USB accessory</string>
 
     <!-- Title of confirmation dialog for USB debugging -->
-    <string name="usb_debugging_title">Allow USB Debugging?</string>
+    <string name="usb_debugging_title">Allow USB debugging?</string>
 
     <!-- Message of confirmation dialog for USB debugging -->
-    <string name="usb_debugging_message">Allow USB Debugging from this computer?\nYour RSA key fingerprint is\n<xliff:g id="fingerprint">%1$s</xliff:g></string>
+    <string name="usb_debugging_message">The computer\'s RSA key fingerprint is:\n<xliff:g id="fingerprint">%1$s</xliff:g></string>
 
     <!-- Option to always allow USB debugging from the attached computer -->
-    <string name="usb_debugging_always">Always allow this computer</string>
+    <string name="usb_debugging_always">Always allow from this computer</string>
 
     <!-- Checkbox label for application compatibility mode ON (zooming app to look like it's running
          on a phone).  [CHAR LIMIT=25] -->
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
index 7056e03..1c3cb2e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettings.java
@@ -167,10 +167,9 @@
         startSettingsActivity(intent);
     }
     private void startSettingsActivity(Intent intent) {
-        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
+        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TOP);
         mBar.collapseAllPanels(true);
         mContext.startActivity(intent);
-
     }
 
     private void addUserTiles(ViewGroup parent, LayoutInflater inflater) {
@@ -310,23 +309,25 @@
         parent.addView(airplaneTile);
 
         // Bluetooth
-        QuickSettingsTileView bluetoothTile = (QuickSettingsTileView)
-                inflater.inflate(R.layout.quick_settings_tile, parent, false);
-        bluetoothTile.setContent(R.layout.quick_settings_tile_bluetooth, inflater);
-        bluetoothTile.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                startSettingsActivity(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
-            }
-        });
-        mModel.addBluetoothTile(bluetoothTile, new QuickSettingsModel.RefreshCallback() {
-            @Override
-            public void refreshView(QuickSettingsTileView view, State state) {
-                TextView tv = (TextView) view.findViewById(R.id.bluetooth_textview);
-                tv.setCompoundDrawablesRelativeWithIntrinsicBounds(0, state.iconId, 0, 0);
-            }
-        });
-        parent.addView(bluetoothTile);
+        if (mModel.deviceSupportsBluetooth()) {
+            QuickSettingsTileView bluetoothTile = (QuickSettingsTileView)
+                    inflater.inflate(R.layout.quick_settings_tile, parent, false);
+            bluetoothTile.setContent(R.layout.quick_settings_tile_bluetooth, inflater);
+            bluetoothTile.setOnClickListener(new View.OnClickListener() {
+                @Override
+                public void onClick(View v) {
+                    startSettingsActivity(android.provider.Settings.ACTION_BLUETOOTH_SETTINGS);
+                }
+            });
+            mModel.addBluetoothTile(bluetoothTile, new QuickSettingsModel.RefreshCallback() {
+                @Override
+                public void refreshView(QuickSettingsTileView view, State state) {
+                    TextView tv = (TextView) view.findViewById(R.id.bluetooth_textview);
+                    tv.setCompoundDrawablesRelativeWithIntrinsicBounds(0, state.iconId, 0, 0);
+                }
+            });
+            parent.addView(bluetoothTile);
+        }
 
         // Brightness
         QuickSettingsTileView brightnessTile = (QuickSettingsTileView)
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
index 2318921..aa40f3c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/QuickSettingsModel.java
@@ -258,6 +258,9 @@
         final BluetoothAdapter adapter = BluetoothAdapter.getDefaultAdapter();
         onBluetoothStateChange(adapter.isEnabled());
     }
+    boolean deviceSupportsBluetooth() {
+        return (BluetoothAdapter.getDefaultAdapter() != null);
+    }
     // BluetoothController callback
     @Override
     public void onBluetoothStateChange(boolean on) {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
index 7fa662eeb..fca7a35 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardHostView.java
@@ -41,6 +41,8 @@
 import android.view.LayoutInflater;
 import android.view.MotionEvent;
 import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewGroup.LayoutParams;
 import android.view.WindowManager;
 import android.view.animation.AnimationUtils;
 import android.widget.RemoteViews.OnClickHandler;
@@ -54,13 +56,14 @@
 import java.util.List;
 
 public class KeyguardHostView extends KeyguardViewBase {
+    private static final String TAG = "KeyguardViewHost";
+
     // Use this to debug all of keyguard
     public static boolean DEBUG;
 
     static final int APPWIDGET_HOST_ID = 0x4B455947;
     private static final String KEYGUARD_WIDGET_PREFS = "keyguard_widget_prefs";
 
-    private static final String TAG = "KeyguardViewHost";
     private AppWidgetHost mAppWidgetHost;
     private KeyguardWidgetPager mAppWidgetContainer;
     private ViewFlipper mSecurityViewContainer;
@@ -77,6 +80,12 @@
     private KeyguardSecurityModel mSecurityModel;
 
     private Rect mTempRect = new Rect();
+    private KeyguardTransportControlView mTransportControl;
+
+    /*package*/ interface TransportCallback {
+        void hide();
+        void show();
+    }
 
     public KeyguardHostView(Context context) {
         this(context, null);
@@ -111,11 +120,56 @@
         mViewMediatorCallback.keyguardDoneDrawing();
     }
 
+    private int getWidgetPosition(int id) {
+        final int children = mAppWidgetContainer.getChildCount();
+        for (int i = 0; i < children; i++) {
+            if (mAppWidgetContainer.getChildAt(i).getId() == id) {
+                return i;
+            }
+        }
+        return -1;
+    }
+
     @Override
     protected void onFinishInflate() {
         mAppWidgetContainer = (KeyguardWidgetPager) findViewById(R.id.app_widget_container);
         mAppWidgetContainer.setVisibility(VISIBLE);
         mSecurityViewContainer = (ViewFlipper) findViewById(R.id.view_flipper);
+
+        // This code manages showing/hiding the transport control. We keep it around and only
+        // add it to the hierarchy if it needs to be present.
+        mTransportControl =
+                (KeyguardTransportControlView) findViewById(R.id.keyguard_transport_control);
+        if (mTransportControl != null) {
+            mTransportControl.setKeyguardCallback(new TransportCallback() {
+                @Override
+                public void hide() {
+                    int page = getWidgetPosition(R.id.keyguard_transport_control);
+                    if (page != -1) {
+                        if (page == mAppWidgetContainer.getCurrentPage()) {
+                            // Switch back to clock view if music was showing.
+                            mAppWidgetContainer
+                                .setCurrentPage(getWidgetPosition(R.id.keyguard_status_view));
+                        }
+                        mAppWidgetContainer.removeView(mTransportControl);
+                        // XXX keep view attached to hierarchy so we still get show/hide events
+                        // from AudioManager
+                        KeyguardHostView.this.addView(mTransportControl);
+                        mTransportControl.setVisibility(View.GONE);
+                    }
+                }
+
+                @Override
+                public void show() {
+                    if (getWidgetPosition(R.id.keyguard_transport_control) == -1) {
+                        KeyguardHostView.this.removeView(mTransportControl);
+                        mAppWidgetContainer.addView(mTransportControl,
+                                getWidgetPosition(R.id.keyguard_status_view) + 1);
+                        mTransportControl.setVisibility(View.VISIBLE);
+                    }
+                }
+            });
+        }
         updateSecurityViews();
     }
 
@@ -423,6 +477,7 @@
     @Override
     public void reset() {
         mIsVerifyUnlockOnly = false;
+        mAppWidgetContainer.setCurrentPage(getWidgetPosition(R.id.keyguard_status_view));
         requestFocus();
     }
 
@@ -639,8 +694,8 @@
             KeyguardWidgetFrame userswitcher = (KeyguardWidgetFrame)
                 LayoutInflater.from(mContext).inflate(R.layout.keyguard_multi_user_selector_widget,
                         mAppWidgetContainer, false);
-            // add the switcher in the first position
-            mAppWidgetContainer.addView(userswitcher, 0);
+            // add the switcher to the left of status view
+            mAppWidgetContainer.addView(userswitcher, getWidgetPosition(R.id.keyguard_status_view));
         }
     }
 
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java
new file mode 100644
index 0000000..3b4ed13
--- /dev/null
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardTransportControlView.java
@@ -0,0 +1,534 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.policy.impl.keyguard;
+
+import java.lang.ref.WeakReference;
+
+import android.app.PendingIntent;
+import android.app.PendingIntent.CanceledException;
+import android.content.Context;
+import android.content.Intent;
+import android.graphics.Bitmap;
+import android.media.AudioManager;
+import android.media.MediaMetadataRetriever;
+import android.media.RemoteControlClient;
+import android.media.IRemoteControlDisplay;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.os.SystemClock;
+import android.text.Spannable;
+import android.text.TextUtils;
+import android.text.style.ForegroundColorSpan;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.FrameLayout;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+
+import com.android.internal.R;
+import com.android.internal.policy.impl.keyguard.KeyguardHostView.TransportCallback;
+
+/**
+ * This is the widget responsible for showing music controls in keyguard.
+ */
+public class KeyguardTransportControlView extends KeyguardWidgetFrame implements OnClickListener {
+
+    private static final int MSG_UPDATE_STATE = 100;
+    private static final int MSG_SET_METADATA = 101;
+    private static final int MSG_SET_TRANSPORT_CONTROLS = 102;
+    private static final int MSG_SET_ARTWORK = 103;
+    private static final int MSG_SET_GENERATION_ID = 104;
+    private static final int MAXDIM = 512;
+    private static final int DISPLAY_TIMEOUT_MS = 5000; // 5s
+    protected static final boolean DEBUG = false;
+    protected static final String TAG = "TransportControlView";
+
+    private ImageView mAlbumArt;
+    private TextView mTrackTitle;
+    private ImageView mBtnPrev;
+    private ImageView mBtnPlay;
+    private ImageView mBtnNext;
+    private int mClientGeneration;
+    private Metadata mMetadata = new Metadata();
+    private boolean mAttached;
+    private PendingIntent mClientIntent;
+    private int mTransportControlFlags;
+    private int mCurrentPlayState;
+    private AudioManager mAudioManager;
+    private IRemoteControlDisplayWeak mIRCD;
+
+    /**
+     * The metadata which should be populated into the view once we've been attached
+     */
+    private Bundle mPopulateMetadataWhenAttached = null;
+
+    // This handler is required to ensure messages from IRCD are handled in sequence and on
+    // the UI thread.
+    private Handler mHandler = new Handler() {
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+            case MSG_UPDATE_STATE:
+                if (mClientGeneration == msg.arg1) updatePlayPauseState(msg.arg2);
+                break;
+
+            case MSG_SET_METADATA:
+                if (mClientGeneration == msg.arg1) updateMetadata((Bundle) msg.obj);
+                break;
+
+            case MSG_SET_TRANSPORT_CONTROLS:
+                if (mClientGeneration == msg.arg1) updateTransportControls(msg.arg2);
+                break;
+
+            case MSG_SET_ARTWORK:
+                if (mClientGeneration == msg.arg1) {
+                    if (mMetadata.bitmap != null) {
+                        mMetadata.bitmap.recycle();
+                    }
+                    mMetadata.bitmap = (Bitmap) msg.obj;
+                    mAlbumArt.setImageBitmap(mMetadata.bitmap);
+                }
+                break;
+
+            case MSG_SET_GENERATION_ID:
+                if (msg.arg2 != 0) {
+                    // This means nobody is currently registered. Hide the view.
+                    hide();
+                }
+                if (DEBUG) Log.v(TAG, "New genId = " + msg.arg1 + ", clearing = " + msg.arg2);
+                mClientGeneration = msg.arg1;
+                mClientIntent = (PendingIntent) msg.obj;
+                break;
+
+            }
+        }
+    };
+    private TransportCallback mTransportCallback;
+
+    /**
+     * This class is required to have weak linkage to the current TransportControlView
+     * because the remote process can hold a strong reference to this binder object and
+     * we can't predict when it will be GC'd in the remote process. Without this code, it
+     * would allow a heavyweight object to be held on this side of the binder when there's
+     * no requirement to run a GC on the other side.
+     */
+    private static class IRemoteControlDisplayWeak extends IRemoteControlDisplay.Stub {
+        private WeakReference<Handler> mLocalHandler;
+
+        IRemoteControlDisplayWeak(Handler handler) {
+            mLocalHandler = new WeakReference<Handler>(handler);
+        }
+
+        public void setPlaybackState(int generationId, int state, long stateChangeTimeMs) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_UPDATE_STATE, generationId, state).sendToTarget();
+            }
+        }
+
+        public void setMetadata(int generationId, Bundle metadata) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
+            }
+        }
+
+        public void setTransportControlFlags(int generationId, int flags) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_TRANSPORT_CONTROLS, generationId, flags)
+                        .sendToTarget();
+            }
+        }
+
+        public void setArtwork(int generationId, Bitmap bitmap) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
+            }
+        }
+
+        public void setAllMetadata(int generationId, Bundle metadata, Bitmap bitmap) {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_METADATA, generationId, 0, metadata).sendToTarget();
+                handler.obtainMessage(MSG_SET_ARTWORK, generationId, 0, bitmap).sendToTarget();
+            }
+        }
+
+        public void setCurrentClientId(int clientGeneration, PendingIntent mediaIntent,
+                boolean clearing) throws RemoteException {
+            Handler handler = mLocalHandler.get();
+            if (handler != null) {
+                handler.obtainMessage(MSG_SET_GENERATION_ID,
+                    clientGeneration, (clearing ? 1 : 0), mediaIntent).sendToTarget();
+            }
+        }
+    };
+
+    public KeyguardTransportControlView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+        Log.v(TAG, "Create TCV " + this);
+        mAudioManager = new AudioManager(mContext);
+        mCurrentPlayState = RemoteControlClient.PLAYSTATE_NONE; // until we get a callback
+        mIRCD = new IRemoteControlDisplayWeak(mHandler);
+    }
+
+    protected void hide() {
+        if (DEBUG) Log.v(TAG, "Transport was told to hide");
+        if (mTransportCallback != null) {
+            mTransportCallback.hide();
+        } else {
+            Log.w(TAG, "Hide music, but callback wasn't set");
+        }
+    }
+
+    private void show() {
+        if (DEBUG) Log.v(TAG, "Transport was told to show");
+        if (mTransportCallback != null) {
+            mTransportCallback.show();
+        } else {
+            Log.w(TAG, "Show music, but callback wasn't set");
+        }
+    }
+
+    private void userActivity() {
+        // TODO Auto-generated method stub
+    }
+
+    private void updateTransportControls(int transportControlFlags) {
+        mTransportControlFlags = transportControlFlags;
+    }
+
+    @Override
+    public void onFinishInflate() {
+        super.onFinishInflate();
+        mTrackTitle = (TextView) findViewById(R.id.title);
+        mTrackTitle.setSelected(true); // enable marquee
+        mAlbumArt = (ImageView) findViewById(R.id.albumart);
+        mBtnPrev = (ImageView) findViewById(R.id.btn_prev);
+        mBtnPlay = (ImageView) findViewById(R.id.btn_play);
+        mBtnNext = (ImageView) findViewById(R.id.btn_next);
+        final View buttons[] = { mBtnPrev, mBtnPlay, mBtnNext };
+        for (View view : buttons) {
+            view.setOnClickListener(this);
+        }
+    }
+
+    @Override
+    public void onAttachedToWindow() {
+        super.onAttachedToWindow();
+        if (DEBUG) Log.v(TAG, "onAttachToWindow()");
+        if (mPopulateMetadataWhenAttached != null) {
+            updateMetadata(mPopulateMetadataWhenAttached);
+            mPopulateMetadataWhenAttached = null;
+        }
+        if (!mAttached) {
+            if (DEBUG) Log.v(TAG, "Registering TCV " + this);
+            mAudioManager.registerRemoteControlDisplay(mIRCD);
+        }
+        mAttached = true;
+    }
+
+    @Override
+    public void onDetachedFromWindow() {
+        if (DEBUG) Log.v(TAG, "onDetachFromWindow()");
+        super.onDetachedFromWindow();
+        if (mAttached) {
+            if (DEBUG) Log.v(TAG, "Unregistering TCV " + this);
+            mAudioManager.unregisterRemoteControlDisplay(mIRCD);
+        }
+        mAttached = false;
+    }
+
+    @Override
+    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+        int dim = Math.min(MAXDIM, Math.max(getWidth(), getHeight()));
+//        Log.v(TAG, "setting max bitmap size: " + dim + "x" + dim);
+//        mAudioManager.remoteControlDisplayUsesBitmapSize(mIRCD, dim, dim);
+    }
+
+    class Metadata {
+        private String artist;
+        private String trackTitle;
+        private String albumTitle;
+        private Bitmap bitmap;
+
+        public String toString() {
+            return "Metadata[artist=" + artist + " trackTitle=" + trackTitle + " albumTitle=" + albumTitle + "]";
+        }
+    }
+
+    private String getMdString(Bundle data, int id) {
+        return data.getString(Integer.toString(id));
+    }
+
+    private void updateMetadata(Bundle data) {
+        if (mAttached) {
+            mMetadata.artist = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUMARTIST);
+            mMetadata.trackTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_TITLE);
+            mMetadata.albumTitle = getMdString(data, MediaMetadataRetriever.METADATA_KEY_ALBUM);
+            populateMetadata();
+        } else {
+            mPopulateMetadataWhenAttached = data;
+        }
+    }
+
+    /**
+     * Populates the given metadata into the view
+     */
+    private void populateMetadata() {
+        StringBuilder sb = new StringBuilder();
+        int trackTitleLength = 0;
+        if (!TextUtils.isEmpty(mMetadata.trackTitle)) {
+            sb.append(mMetadata.trackTitle);
+            trackTitleLength = mMetadata.trackTitle.length();
+        }
+        if (!TextUtils.isEmpty(mMetadata.artist)) {
+            if (sb.length() != 0) {
+                sb.append(" - ");
+            }
+            sb.append(mMetadata.artist);
+        }
+        if (!TextUtils.isEmpty(mMetadata.albumTitle)) {
+            if (sb.length() != 0) {
+                sb.append(" - ");
+            }
+            sb.append(mMetadata.albumTitle);
+        }
+        mTrackTitle.setText(sb.toString(), TextView.BufferType.SPANNABLE);
+        Spannable str = (Spannable) mTrackTitle.getText();
+        if (trackTitleLength != 0) {
+            str.setSpan(new ForegroundColorSpan(0xffffffff), 0, trackTitleLength,
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            trackTitleLength++;
+        }
+        if (sb.length() > trackTitleLength) {
+            str.setSpan(new ForegroundColorSpan(0x7fffffff), trackTitleLength, sb.length(),
+                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+        }
+
+        mAlbumArt.setImageBitmap(mMetadata.bitmap);
+        final int flags = mTransportControlFlags;
+        setVisibilityBasedOnFlag(mBtnPrev, flags, RemoteControlClient.FLAG_KEY_MEDIA_PREVIOUS);
+        setVisibilityBasedOnFlag(mBtnNext, flags, RemoteControlClient.FLAG_KEY_MEDIA_NEXT);
+        setVisibilityBasedOnFlag(mBtnPlay, flags,
+                RemoteControlClient.FLAG_KEY_MEDIA_PLAY
+                | RemoteControlClient.FLAG_KEY_MEDIA_PAUSE
+                | RemoteControlClient.FLAG_KEY_MEDIA_PLAY_PAUSE
+                | RemoteControlClient.FLAG_KEY_MEDIA_STOP);
+
+        updatePlayPauseState(mCurrentPlayState);
+    }
+
+    private static void setVisibilityBasedOnFlag(View view, int flags, int flag) {
+        if ((flags & flag) != 0) {
+            view.setVisibility(View.VISIBLE);
+        } else {
+            view.setVisibility(View.GONE);
+        }
+    }
+
+    private void updatePlayPauseState(int state) {
+        if (DEBUG) Log.v(TAG,
+                "updatePlayPauseState(), old=" + mCurrentPlayState + ", state=" + state);
+        if (state == mCurrentPlayState) {
+            return;
+        }
+        final int imageResId;
+        final int imageDescId;
+        boolean showIfHidden = false;
+        switch (state) {
+            case RemoteControlClient.PLAYSTATE_ERROR:
+                imageResId = com.android.internal.R.drawable.stat_sys_warning;
+                // TODO use more specific image description string for warning, but here the "play"
+                //      message is still valid because this button triggers a play command.
+                imageDescId = com.android.internal.R.string.lockscreen_transport_play_description;
+                break;
+
+            case RemoteControlClient.PLAYSTATE_PLAYING:
+                imageResId = com.android.internal.R.drawable.ic_media_pause;
+                imageDescId = com.android.internal.R.string.lockscreen_transport_pause_description;
+                showIfHidden = true;
+                break;
+
+            case RemoteControlClient.PLAYSTATE_BUFFERING:
+                imageResId = com.android.internal.R.drawable.ic_media_stop;
+                imageDescId = com.android.internal.R.string.lockscreen_transport_stop_description;
+                showIfHidden = true;
+                break;
+
+            case RemoteControlClient.PLAYSTATE_PAUSED:
+            default:
+                imageResId = com.android.internal.R.drawable.ic_media_play;
+                imageDescId = com.android.internal.R.string.lockscreen_transport_play_description;
+                showIfHidden = false;
+                break;
+        }
+        mBtnPlay.setImageResource(imageResId);
+        mBtnPlay.setContentDescription(getResources().getString(imageDescId));
+        if (showIfHidden) {
+            show();
+        }
+        mCurrentPlayState = state;
+    }
+
+    static class SavedState extends BaseSavedState {
+        boolean wasShowing;
+
+        SavedState(Parcelable superState) {
+            super(superState);
+        }
+
+        private SavedState(Parcel in) {
+            super(in);
+            this.wasShowing = in.readInt() != 0;
+        }
+
+        @Override
+        public void writeToParcel(Parcel out, int flags) {
+            super.writeToParcel(out, flags);
+            out.writeInt(this.wasShowing ? 1 : 0);
+        }
+
+        public static final Parcelable.Creator<SavedState> CREATOR
+                = new Parcelable.Creator<SavedState>() {
+            public SavedState createFromParcel(Parcel in) {
+                return new SavedState(in);
+            }
+
+            public SavedState[] newArray(int size) {
+                return new SavedState[size];
+            }
+        };
+    }
+
+    @Override
+    public Parcelable onSaveInstanceState() {
+        if (DEBUG) Log.v(TAG, "onSaveInstanceState()");
+        Parcelable superState = super.onSaveInstanceState();
+        SavedState ss = new SavedState(superState);
+        ss.wasShowing = getVisibility() == View.VISIBLE;
+        return ss;
+    }
+
+    @Override
+    public void onRestoreInstanceState(Parcelable state) {
+        if (DEBUG) Log.v(TAG, "onRestoreInstanceState()");
+        if (!(state instanceof SavedState)) {
+            super.onRestoreInstanceState(state);
+            return;
+        }
+        SavedState ss = (SavedState) state;
+        super.onRestoreInstanceState(ss.getSuperState());
+        if (ss.wasShowing) {
+            show();
+        }
+    }
+
+    public void onClick(View v) {
+        int keyCode = -1;
+        if (v == mBtnPrev) {
+            keyCode = KeyEvent.KEYCODE_MEDIA_PREVIOUS;
+        } else if (v == mBtnNext) {
+            keyCode = KeyEvent.KEYCODE_MEDIA_NEXT;
+        } else if (v == mBtnPlay) {
+            keyCode = KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE;
+
+        }
+        if (keyCode != -1) {
+            sendMediaButtonClick(keyCode);
+            userActivity();
+        }
+    }
+
+    private void sendMediaButtonClick(int keyCode) {
+        if (mClientIntent == null) {
+            // Shouldn't be possible because this view should be hidden in this case.
+            Log.e(TAG, "sendMediaButtonClick(): No client is currently registered");
+            return;
+        }
+        // use the registered PendingIntent that will be processed by the registered
+        //    media button event receiver, which is the component of mClientIntent
+        KeyEvent keyEvent = new KeyEvent(KeyEvent.ACTION_DOWN, keyCode);
+        Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+        try {
+            mClientIntent.send(getContext(), 0, intent);
+        } catch (CanceledException e) {
+            Log.e(TAG, "Error sending intent for media button down: "+e);
+            e.printStackTrace();
+        }
+
+        keyEvent = new KeyEvent(KeyEvent.ACTION_UP, keyCode);
+        intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+        intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
+        try {
+            mClientIntent.send(getContext(), 0, intent);
+        } catch (CanceledException e) {
+            Log.e(TAG, "Error sending intent for media button up: "+e);
+            e.printStackTrace();
+        }
+    }
+
+    public boolean providesClock() {
+        return false;
+    }
+
+    private boolean wasPlayingRecently(int state, long stateChangeTimeMs) {
+        switch (state) {
+            case RemoteControlClient.PLAYSTATE_PLAYING:
+            case RemoteControlClient.PLAYSTATE_FAST_FORWARDING:
+            case RemoteControlClient.PLAYSTATE_REWINDING:
+            case RemoteControlClient.PLAYSTATE_SKIPPING_FORWARDS:
+            case RemoteControlClient.PLAYSTATE_SKIPPING_BACKWARDS:
+            case RemoteControlClient.PLAYSTATE_BUFFERING:
+                // actively playing or about to play
+                return true;
+            case RemoteControlClient.PLAYSTATE_NONE:
+                return false;
+            case RemoteControlClient.PLAYSTATE_STOPPED:
+            case RemoteControlClient.PLAYSTATE_PAUSED:
+            case RemoteControlClient.PLAYSTATE_ERROR:
+                // we have stopped playing, check how long ago
+                if (DEBUG) {
+                    if ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS) {
+                        Log.v(TAG, "wasPlayingRecently: time < TIMEOUT was playing recently");
+                    } else {
+                        Log.v(TAG, "wasPlayingRecently: time > TIMEOUT");
+                    }
+                }
+                return ((SystemClock.elapsedRealtime() - stateChangeTimeMs) < DISPLAY_TIMEOUT_MS);
+            default:
+                Log.e(TAG, "Unknown playback state " + state + " in wasPlayingRecently()");
+                return false;
+        }
+    }
+
+    public void setKeyguardCallback(TransportCallback transportCallback) {
+        mTransportCallback = transportCallback;
+    }
+}
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
index 61003bf..d9088e0 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/KeyguardViewManager.java
@@ -214,9 +214,9 @@
      */
     public synchronized void reset() {
         if (DEBUG) Log.d(TAG, "reset()");
-        if (mKeyguardView != null) {
-            mKeyguardView.reset();
-        }
+        // User might have switched, check if we need to go back to keyguard
+        // TODO: It's preferable to stay and show the correct lockscreen or unlock if none
+        maybeCreateKeyguardLocked(shouldEnableScreenRotation());
     }
 
     public synchronized void onScreenTurnedOff() {
diff --git a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
index 1b46efa..fc7c90f 100644
--- a/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
+++ b/policy/src/com/android/internal/policy/impl/keyguard/PagedView.java
@@ -595,6 +595,10 @@
 
     @Override
     public void onChildViewRemoved(View parent, View child) {
+        invalidate();
+        invalidateCachedOffsets();
+        // This prevents a crash when a child is removed that was the current page.
+        mCurrentPage = Math.min(mCurrentPage, getChildCount() - 1);
     }
 
     protected void invalidateCachedOffsets() {
diff --git a/services/common_time/common_clock_service.h b/services/common_time/common_clock_service.h
index a65e398..bd663f0 100644
--- a/services/common_time/common_clock_service.h
+++ b/services/common_time/common_clock_service.h
@@ -14,11 +14,12 @@
  * limitations under the License.
  */
 
-#include <common_time/ICommonClock.h>
-
 #ifndef ANDROID_COMMON_CLOCK_SERVICE_H
 #define ANDROID_COMMON_CLOCK_SERVICE_H
 
+#include <sys/socket.h>
+#include <common_time/ICommonClock.h>
+
 namespace android {
 
 class CommonTimeServer;
diff --git a/services/common_time/common_time_config_service.h b/services/common_time/common_time_config_service.h
index 8283c24..89806dd 100644
--- a/services/common_time/common_time_config_service.h
+++ b/services/common_time/common_time_config_service.h
@@ -13,11 +13,12 @@
  * limitations under the License.
  */
 
-#include <common_time/ICommonTimeConfig.h>
-
 #ifndef ANDROID_COMMON_TIME_CONFIG_SERVICE_H
 #define ANDROID_COMMON_TIME_CONFIG_SERVICE_H
 
+#include <sys/socket.h>
+#include <common_time/ICommonTimeConfig.h>
+
 namespace android {
 
 class String16;
diff --git a/services/common_time/common_time_server.h b/services/common_time/common_time_server.h
index f6a2419..6e18050 100644
--- a/services/common_time/common_time_server.h
+++ b/services/common_time/common_time_server.h
@@ -19,7 +19,7 @@
 
 #include <arpa/inet.h>
 #include <stdint.h>
-#include <linux/socket.h>
+#include <sys/socket.h>
 
 #include <common_time/ICommonClock.h>
 #include <common_time/local_clock.h>
diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java
index 1d40f4f..5e2b425 100644
--- a/services/java/com/android/server/BackupManagerService.java
+++ b/services/java/com/android/server/BackupManagerService.java
@@ -67,6 +67,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.os.WorkSource;
+import android.os.Environment.UserEnvironment;
 import android.os.storage.IMountService;
 import android.provider.Settings;
 import android.util.EventLog;
@@ -2720,9 +2721,13 @@
             FullBackup.backupToTar(pkg.packageName, FullBackup.APK_TREE_TOKEN, null,
                     apkDir, appSourceDir, output);
 
+            // TODO: migrate this to SharedStorageBackup, since AID_SYSTEM
+            // doesn't have access to external storage.
+
             // Save associated .obb content if it exists and we did save the apk
             // check for .obb and save those too
-            final File obbDir = Environment.getExternalStorageAppObbDirectory(pkg.packageName);
+            final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_OWNER);
+            final File obbDir = userEnv.getExternalStorageAppObbDirectory(pkg.packageName);
             if (obbDir != null) {
                 if (MORE_DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath());
                 File[] obbFiles = obbDir.listFiles();
diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java
index f40333d..32ab154 100644
--- a/services/java/com/android/server/MountService.java
+++ b/services/java/com/android/server/MountService.java
@@ -16,11 +16,7 @@
 
 package com.android.server;
 
-import com.android.internal.app.IMediaContainerService;
-import com.android.internal.util.XmlUtils;
-import com.android.server.am.ActivityManagerService;
-import com.android.server.pm.PackageManagerService;
-import com.android.server.NativeDaemonConnector.Command;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
 
 import android.Manifest;
 import android.content.BroadcastReceiver;
@@ -30,6 +26,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageManager;
+import android.content.pm.UserInfo;
 import android.content.res.ObbInfo;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -38,15 +35,14 @@
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
+import android.os.Environment.UserEnvironment;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
-import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
 import android.os.storage.IMountService;
@@ -61,9 +57,18 @@
 import android.util.Slog;
 import android.util.Xml;
 
-import org.xmlpull.v1.XmlPullParser;
+import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.XmlUtils;
+import com.android.server.NativeDaemonConnector.Command;
+import com.android.server.am.ActivityManagerService;
+import com.android.server.pm.PackageManagerService;
+import com.android.server.pm.UserManagerService;
+import com.google.android.collect.Lists;
+import com.google.android.collect.Maps;
+
 import org.xmlpull.v1.XmlPullParserException;
 
+import java.io.File;
 import java.io.FileDescriptor;
 import java.io.IOException;
 import java.io.PrintWriter;
@@ -81,7 +86,6 @@
 import java.util.Map.Entry;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
-import java.util.Set;
 
 import javax.crypto.SecretKey;
 import javax.crypto.SecretKeyFactory;
@@ -96,9 +100,11 @@
 class MountService extends IMountService.Stub
         implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
 
-    private static final boolean LOCAL_LOGD = false;
-    private static final boolean DEBUG_UNMOUNT = false;
-    private static final boolean DEBUG_EVENTS = false;
+    // TODO: listen for user creation/deletion
+
+    private static final boolean LOCAL_LOGD = true;
+    private static final boolean DEBUG_UNMOUNT = true;
+    private static final boolean DEBUG_EVENTS = true;
     private static final boolean DEBUG_OBB = false;
 
     // Disable this since it messes up long-running cryptfs operations.
@@ -166,25 +172,34 @@
         public static final int VolumeBadRemoval               = 632;
     }
 
-    private Context                               mContext;
-    private NativeDaemonConnector                 mConnector;
-    private final ArrayList<StorageVolume>        mVolumes = new ArrayList<StorageVolume>();
-    private StorageVolume                         mPrimaryVolume;
-    private final HashMap<String, String>         mVolumeStates = new HashMap<String, String>();
-    private final HashMap<String, StorageVolume>  mVolumeMap = new HashMap<String, StorageVolume>();
-    private String                                mExternalStoragePath;
+    private Context mContext;
+    private NativeDaemonConnector mConnector;
+
+    private final Object mVolumesLock = new Object();
+
+    /** When defined, base template for user-specific {@link StorageVolume}. */
+    private StorageVolume mEmulatedTemplate;
+
+    // @GuardedBy("mVolumesLock")
+    private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList();
+    /** Map from path to {@link StorageVolume} */
+    // @GuardedBy("mVolumesLock")
+    private final HashMap<String, StorageVolume> mVolumesByPath = Maps.newHashMap();
+    /** Map from path to state */
+    // @GuardedBy("mVolumesLock")
+    private final HashMap<String, String> mVolumeStates = Maps.newHashMap();
+
+    private volatile boolean mSystemReady = false;
+
     private PackageManagerService                 mPms;
     private boolean                               mUmsEnabling;
     private boolean                               mUmsAvailable = false;
     // Used as a lock for methods that register/unregister listeners.
     final private ArrayList<MountServiceBinderListener> mListeners =
             new ArrayList<MountServiceBinderListener>();
-    private boolean                               mBooted = false;
     private CountDownLatch                        mConnectedSignal = new CountDownLatch(1);
     private CountDownLatch                        mAsecsScanned = new CountDownLatch(1);
     private boolean                               mSendUmsConnectedOnBoot = false;
-    // true if we should fake MEDIA_MOUNTED state for external storage
-    private boolean                               mEmulateExternalStorage = false;
 
     /**
      * Private hash of currently mounted secure containers.
@@ -303,6 +318,8 @@
     private static final int H_UNMOUNT_PM_UPDATE = 1;
     private static final int H_UNMOUNT_PM_DONE = 2;
     private static final int H_UNMOUNT_MS = 3;
+    private static final int H_SYSTEM_READY = 4;
+
     private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
     private static final int MAX_UNMOUNT_RETRIES = 4;
 
@@ -437,17 +454,26 @@
                     }
                     break;
                 }
-                case H_UNMOUNT_MS : {
+                case H_UNMOUNT_MS: {
                     if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_MS");
                     UnmountCallBack ucb = (UnmountCallBack) msg.obj;
                     ucb.handleFinished();
                     break;
                 }
+                case H_SYSTEM_READY: {
+                    try {
+                        handleSystemReady();
+                    } catch (Exception ex) {
+                        Slog.e(TAG, "Boot-time mount exception", ex);
+                    }
+                    break;
+                }
             }
         }
     };
-    final private HandlerThread mHandlerThread;
-    final private Handler mHandler;
+
+    private final HandlerThread mHandlerThread;
+    private final Handler mHandler;
 
     void waitForAsecScan() {
         waitForLatch(mAsecsScanned);
@@ -476,90 +502,119 @@
         }
     }
 
-    private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
+    private void handleSystemReady() {
+        // Snapshot current volume states since it's not safe to call into vold
+        // while holding locks.
+        final HashMap<String, String> snapshot;
+        synchronized (mVolumesLock) {
+            snapshot = new HashMap<String, String>(mVolumeStates);
+        }
+
+        for (Map.Entry<String, String> entry : snapshot.entrySet()) {
+            final String path = entry.getKey();
+            final String state = entry.getValue();
+
+            if (state.equals(Environment.MEDIA_UNMOUNTED)) {
+                int rc = doMountVolume(path);
+                if (rc != StorageResultCode.OperationSucceeded) {
+                    Slog.e(TAG, String.format("Boot-time mount failed (%d)",
+                            rc));
+                }
+            } else if (state.equals(Environment.MEDIA_SHARED)) {
+                /*
+                 * Bootstrap UMS enabled state since vold indicates
+                 * the volume is shared (runtime restart while ums enabled)
+                 */
+                notifyVolumeStateChange(null, path, VolumeState.NoMedia,
+                        VolumeState.Shared);
+            }
+        }
+
+        // Push mounted state for all emulated storage
+        synchronized (mVolumesLock) {
+            for (StorageVolume volume : mVolumes) {
+                if (volume.isEmulated()) {
+                    updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
+                }
+            }
+        }
+
+        /*
+         * If UMS was connected on boot, send the connected event
+         * now that we're up.
+         */
+        if (mSendUmsConnectedOnBoot) {
+            sendUmsIntent(true);
+            mSendUmsConnectedOnBoot = false;
+        }
+    }
+
+    private final BroadcastReceiver mBootReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            String action = intent.getAction();
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            if (userId == -1) return;
+            final UserHandle user = new UserHandle(userId);
 
-            if (action.equals(Intent.ACTION_BOOT_COMPLETED)) {
-                mBooted = true;
+            Slog.d(TAG, "BOOT_COMPLETED for " + user);
 
-                /*
-                 * In the simulator, we need to broadcast a volume mounted event
-                 * to make the media scanner run.
-                 */
-                if ("simulator".equals(SystemProperties.get("ro.product.device"))) {
-                    notifyVolumeStateChange(null, "/sdcard", VolumeState.NoMedia,
-                            VolumeState.Mounted);
-                    return;
-                }
-                new Thread() {
-                    @Override
-                    public void run() {
-                        try {
-                            // it is not safe to call vold with mVolumeStates locked
-                            // so we make a copy of the paths and states and process them
-                            // outside the lock
-                            String[] paths;
-                            String[] states;
-                            int count;
-                            synchronized (mVolumeStates) {
-                                Set<String> keys = mVolumeStates.keySet();
-                                count = keys.size();
-                                paths = keys.toArray(new String[count]);
-                                states = new String[count];
-                                for (int i = 0; i < count; i++) {
-                                    states[i] = mVolumeStates.get(paths[i]);
-                                }
-                            }
+            // Broadcast mounted volumes to newly booted user. This kicks off
+            // media scanner when a user becomes active.
+            synchronized (mVolumesLock) {
+                for (StorageVolume volume : mVolumes) {
+                    final UserHandle owner = volume.getOwner();
+                    final boolean ownerMatch = owner == null
+                            || owner.getIdentifier() == user.getIdentifier();
 
-                            for (int i = 0; i < count; i++) {
-                                String path = paths[i];
-                                String state = states[i];
+                    final String state = mVolumeStates.get(volume.getPath());
 
-                                if (state.equals(Environment.MEDIA_UNMOUNTED)) {
-                                    int rc = doMountVolume(path);
-                                    if (rc != StorageResultCode.OperationSucceeded) {
-                                        Slog.e(TAG, String.format("Boot-time mount failed (%d)",
-                                                rc));
-                                    }
-                                } else if (state.equals(Environment.MEDIA_SHARED)) {
-                                    /*
-                                     * Bootstrap UMS enabled state since vold indicates
-                                     * the volume is shared (runtime restart while ums enabled)
-                                     */
-                                    notifyVolumeStateChange(null, path, VolumeState.NoMedia,
-                                            VolumeState.Shared);
-                                }
-                            }
-
-                            /* notify external storage has mounted to trigger media scanner */
-                            if (mEmulateExternalStorage) {
-                                notifyVolumeStateChange(null,
-                                        Environment.getExternalStorageDirectory().getPath(),
-                                        VolumeState.NoMedia, VolumeState.Mounted);
-                            }
-
-                            /*
-                             * If UMS was connected on boot, send the connected event
-                             * now that we're up.
-                             */
-                            if (mSendUmsConnectedOnBoot) {
-                                sendUmsIntent(true);
-                                mSendUmsConnectedOnBoot = false;
-                            }
-                        } catch (Exception ex) {
-                            Slog.e(TAG, "Boot-time mount exception", ex);
-                        }
+                    if (ownerMatch && (Environment.MEDIA_MOUNTED.equals(state)
+                            || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state))) {
+                        sendStorageIntent(Intent.ACTION_MEDIA_MOUNTED, volume, user);
                     }
-                }.start();
-            } else if (action.equals(UsbManager.ACTION_USB_STATE)) {
-                boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) &&
-                        intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false));
-                notifyShareAvailabilityChange(available);
+                }
             }
         }
     };
+
+    private final BroadcastReceiver mUserReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, -1);
+            if (userId == -1) return;
+            final UserHandle user = new UserHandle(userId);
+
+            final String action = intent.getAction();
+            if (Intent.ACTION_USER_ADDED.equals(action)) {
+                synchronized (mVolumesLock) {
+                    createEmulatedVolumeForUserLocked(user);
+                }
+
+            } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
+                synchronized (mVolumesLock) {
+                    final List<StorageVolume> toRemove = Lists.newArrayList();
+                    for (StorageVolume volume : mVolumes) {
+                        if (user.equals(volume.getOwner())) {
+                            toRemove.add(volume);
+                        }
+                    }
+                    for (StorageVolume volume : toRemove) {
+                        removeVolumeLocked(volume);
+                    }
+                }
+            }
+        }
+    };
+
+    private final BroadcastReceiver mUsbReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            boolean available = (intent.getBooleanExtra(UsbManager.USB_CONNECTED, false) &&
+                    intent.getBooleanExtra(UsbManager.USB_FUNCTION_MASS_STORAGE, false));
+            notifyShareAvailabilityChange(available);
+        }
+    };
+
     private final class MountServiceBinderListener implements IBinder.DeathRecipient {
         final IMountServiceListener mListener;
 
@@ -590,11 +645,13 @@
         }
     }
 
-    private void updatePublicVolumeState(String path, String state) {
-        String oldState;
-        synchronized(mVolumeStates) {
+    private void updatePublicVolumeState(StorageVolume volume, String state) {
+        final String path = volume.getPath();
+        final String oldState;
+        synchronized (mVolumesLock) {
             oldState = mVolumeStates.put(path, state);
         }
+
         if (state.equals(oldState)) {
             Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
                     state, state, path));
@@ -603,24 +660,24 @@
 
         Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
 
-        if (path.equals(mExternalStoragePath)) {
-            // Update state on PackageManager, but only of real events
-            if (!mEmulateExternalStorage) {
-                if (Environment.MEDIA_UNMOUNTED.equals(state)) {
-                    mPms.updateExternalMediaStatus(false, false);
+        // Tell PackageManager about changes to primary volume state, but only
+        // when not emulated.
+        if (volume.isPrimary() && !volume.isEmulated()) {
+            if (Environment.MEDIA_UNMOUNTED.equals(state)) {
+                mPms.updateExternalMediaStatus(false, false);
 
-                    /*
-                     * Some OBBs might have been unmounted when this volume was
-                     * unmounted, so send a message to the handler to let it know to
-                     * remove those from the list of mounted OBBS.
-                     */
-                    mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
-                            OBB_FLUSH_MOUNT_STATE, path));
-                } else if (Environment.MEDIA_MOUNTED.equals(state)) {
-                    mPms.updateExternalMediaStatus(true, false);
-                }
+                /*
+                 * Some OBBs might have been unmounted when this volume was
+                 * unmounted, so send a message to the handler to let it know to
+                 * remove those from the list of mounted OBBS.
+                 */
+                mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
+                        OBB_FLUSH_MOUNT_STATE, path));
+            } else if (Environment.MEDIA_MOUNTED.equals(state)) {
+                mPms.updateExternalMediaStatus(true, false);
             }
         }
+
         synchronized (mListeners) {
             for (int i = mListeners.size() -1; i >= 0; i--) {
                 MountServiceBinderListener bl = mListeners.get(i);
@@ -637,7 +694,6 @@
     }
 
     /**
-     *
      * Callback from NativeDaemonConnector
      */
     public void onDaemonConnected() {
@@ -661,6 +717,11 @@
                         String path = tok[1];
                         String state = Environment.MEDIA_REMOVED;
 
+                        final StorageVolume volume;
+                        synchronized (mVolumesLock) {
+                            volume = mVolumesByPath.get(path);
+                        }
+
                         int st = Integer.parseInt(tok[2]);
                         if (st == VolumeState.NoMedia) {
                             state = Environment.MEDIA_REMOVED;
@@ -678,12 +739,15 @@
 
                         if (state != null) {
                             if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
-                            updatePublicVolumeState(path, state);
+                            updatePublicVolumeState(volume, state);
                         }
                     }
                 } catch (Exception e) {
                     Slog.e(TAG, "Error processing initial volume state", e);
-                    updatePublicVolumeState(mExternalStoragePath, Environment.MEDIA_REMOVED);
+                    final StorageVolume primary = getPrimaryPhysicalVolume();
+                    if (primary != null) {
+                        updatePublicVolumeState(primary, Environment.MEDIA_REMOVED);
+                    }
                 }
 
                 /*
@@ -749,6 +813,13 @@
                 Slog.e(TAG, "Failed to parse major/minor", ex);
             }
 
+            final StorageVolume volume;
+            final String state;
+            synchronized (mVolumesLock) {
+                volume = mVolumesByPath.get(path);
+                state = mVolumeStates.get(path);
+            }
+
             if (code == VoldResponseCode.VolumeDiskInserted) {
                 new Thread() {
                     @Override
@@ -772,27 +843,27 @@
                 }
                 /* Send the media unmounted event first */
                 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
-                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
-                sendStorageIntent(Environment.MEDIA_UNMOUNTED, path);
+                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
+                sendStorageIntent(Environment.MEDIA_UNMOUNTED, volume, UserHandle.ALL);
 
                 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media removed");
-                updatePublicVolumeState(path, Environment.MEDIA_REMOVED);
+                updatePublicVolumeState(volume, Environment.MEDIA_REMOVED);
                 action = Intent.ACTION_MEDIA_REMOVED;
             } else if (code == VoldResponseCode.VolumeBadRemoval) {
                 if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
                 /* Send the media unmounted event first */
-                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
+                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
                 action = Intent.ACTION_MEDIA_UNMOUNTED;
 
                 if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
-                updatePublicVolumeState(path, Environment.MEDIA_BAD_REMOVAL);
+                updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL);
                 action = Intent.ACTION_MEDIA_BAD_REMOVAL;
             } else {
                 Slog.e(TAG, String.format("Unknown code {%d}", code));
             }
 
             if (action != null) {
-                sendStorageIntent(action, path);
+                sendStorageIntent(action, volume, UserHandle.ALL);
             }
         } else {
             return false;
@@ -802,14 +873,20 @@
     }
 
     private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
-        String vs = getVolumeState(path);
-        if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChanged::" + vs);
+        final StorageVolume volume;
+        final String state;
+        synchronized (mVolumesLock) {
+            volume = mVolumesByPath.get(path);
+            state = getVolumeState(path);
+        }
+
+        if (DEBUG_EVENTS) Slog.i(TAG, "notifyVolumeStateChange::" + state);
 
         String action = null;
 
         if (oldState == VolumeState.Shared && newState != oldState) {
             if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_UNSHARED intent");
-            sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED,  path);
+            sendStorageIntent(Intent.ACTION_MEDIA_UNSHARED, volume, UserHandle.ALL);
         }
 
         if (newState == VolumeState.Init) {
@@ -820,22 +897,22 @@
              * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
              * if we're in the process of enabling UMS
              */
-            if (!vs.equals(
-                    Environment.MEDIA_BAD_REMOVAL) && !vs.equals(
-                            Environment.MEDIA_NOFS) && !vs.equals(
+            if (!state.equals(
+                    Environment.MEDIA_BAD_REMOVAL) && !state.equals(
+                            Environment.MEDIA_NOFS) && !state.equals(
                                     Environment.MEDIA_UNMOUNTABLE) && !getUmsEnabling()) {
                 if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state for media bad removal nofs and unmountable");
-                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
+                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
                 action = Intent.ACTION_MEDIA_UNMOUNTED;
             }
         } else if (newState == VolumeState.Pending) {
         } else if (newState == VolumeState.Checking) {
             if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state checking");
-            updatePublicVolumeState(path, Environment.MEDIA_CHECKING);
+            updatePublicVolumeState(volume, Environment.MEDIA_CHECKING);
             action = Intent.ACTION_MEDIA_CHECKING;
         } else if (newState == VolumeState.Mounted) {
             if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state mounted");
-            updatePublicVolumeState(path, Environment.MEDIA_MOUNTED);
+            updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
             action = Intent.ACTION_MEDIA_MOUNTED;
         } else if (newState == VolumeState.Unmounting) {
             action = Intent.ACTION_MEDIA_EJECT;
@@ -843,11 +920,11 @@
         } else if (newState == VolumeState.Shared) {
             if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
             /* Send the media unmounted event first */
-            updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTED);
-            sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, path);
+            updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
+            sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
 
             if (DEBUG_EVENTS) Slog.i(TAG, "Updating media shared");
-            updatePublicVolumeState(path, Environment.MEDIA_SHARED);
+            updatePublicVolumeState(volume, Environment.MEDIA_SHARED);
             action = Intent.ACTION_MEDIA_SHARED;
             if (LOCAL_LOGD) Slog.d(TAG, "Sending ACTION_MEDIA_SHARED intent");
         } else if (newState == VolumeState.SharedMnt) {
@@ -858,13 +935,18 @@
         }
 
         if (action != null) {
-            sendStorageIntent(action, path);
+            sendStorageIntent(action, volume, UserHandle.ALL);
         }
     }
 
     private int doMountVolume(String path) {
         int rc = StorageResultCode.OperationSucceeded;
 
+        final StorageVolume volume;
+        synchronized (mVolumesLock) {
+            volume = mVolumesByPath.get(path);
+        }
+
         if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
         try {
             mConnector.execute("volume", "mount", path);
@@ -884,7 +966,7 @@
                 /*
                  * Media is blank or does not contain a supported filesystem
                  */
-                updatePublicVolumeState(path, Environment.MEDIA_NOFS);
+                updatePublicVolumeState(volume, Environment.MEDIA_NOFS);
                 action = Intent.ACTION_MEDIA_NOFS;
                 rc = StorageResultCode.OperationFailedMediaBlank;
             } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
@@ -892,7 +974,7 @@
                 /*
                  * Volume consistency check failed
                  */
-                updatePublicVolumeState(path, Environment.MEDIA_UNMOUNTABLE);
+                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE);
                 action = Intent.ACTION_MEDIA_UNMOUNTABLE;
                 rc = StorageResultCode.OperationFailedMediaCorrupt;
             } else {
@@ -903,7 +985,7 @@
              * Send broadcast intent (if required for the failure)
              */
             if (action != null) {
-                sendStorageIntent(action, path);
+                sendStorageIntent(action, volume, UserHandle.ALL);
             }
         }
 
@@ -1011,14 +1093,16 @@
             }
         }
 
-        if (mBooted == true) {
+        if (mSystemReady == true) {
             sendUmsIntent(avail);
         } else {
             mSendUmsConnectedOnBoot = avail;
         }
 
-        final String path = Environment.getExternalStorageDirectory().getPath();
-        if (avail == false && getVolumeState(path).equals(Environment.MEDIA_SHARED)) {
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (avail == false && primary != null
+                && Environment.MEDIA_SHARED.equals(getVolumeState(primary.getPath()))) {
+            final String path = primary.getPath();
             /*
              * USB mass storage disconnected while enabled
              */
@@ -1042,12 +1126,11 @@
         }
     }
 
-    private void sendStorageIntent(String action, String path) {
-        Intent intent = new Intent(action, Uri.parse("file://" + path));
-        // add StorageVolume extra
-        intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolumeMap.get(path));
-        Slog.d(TAG, "sendStorageIntent " + intent);
-        mContext.sendBroadcastAsUser(intent, UserHandle.ALL);
+    private void sendStorageIntent(String action, StorageVolume volume, UserHandle user) {
+        final Intent intent = new Intent(action, Uri.parse("file://" + volume.getPath()));
+        intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, volume);
+        Slog.d(TAG, "sendStorageIntent " + intent + " to " + user);
+        mContext.sendBroadcastAsUser(intent, user);
     }
 
     private void sendUmsIntent(boolean c) {
@@ -1066,7 +1149,10 @@
     private static final String TAG_STORAGE_LIST = "StorageList";
     private static final String TAG_STORAGE = "storage";
 
-    private void readStorageList() {
+    private void readStorageListLocked() {
+        mVolumes.clear();
+        mVolumeStates.clear();
+
         Resources resources = mContext.getResources();
 
         int id = com.android.internal.R.xml.storage_list;
@@ -1085,7 +1171,7 @@
                     TypedArray a = resources.obtainAttributes(attrs,
                             com.android.internal.R.styleable.Storage);
 
-                    CharSequence path = a.getText(
+                    String path = a.getString(
                             com.android.internal.R.styleable.Storage_mountPoint);
                     int descriptionId = a.getResourceId(
                             com.android.internal.R.styleable.Storage_storageDescription, -1);
@@ -1110,27 +1196,29 @@
                             " emulated: " + emulated +  " mtpReserve: " + mtpReserve +
                             " allowMassStorage: " + allowMassStorage +
                             " maxFileSize: " + maxFileSize);
-                    if (path == null || description == null) {
-                        Slog.e(TAG, "path or description is null in readStorageList");
+
+                    if (emulated) {
+                        // For devices with emulated storage, we create separate
+                        // volumes for each known user.
+                        mEmulatedTemplate = new StorageVolume(null, descriptionId, true, false,
+                                true, mtpReserve, false, maxFileSize, null);
+
+                        final UserManagerService userManager = UserManagerService.getInstance();
+                        for (UserInfo user : userManager.getUsers()) {
+                            createEmulatedVolumeForUserLocked(user.getUserHandle());
+                        }
+
                     } else {
-                        String pathString = path.toString();
-                        StorageVolume volume = new StorageVolume(pathString, descriptionId, primary,
-                                removable, emulated, mtpReserve, allowMassStorage, maxFileSize);
-                        if (primary) {
-                            if (mPrimaryVolume == null) {
-                                mPrimaryVolume = volume;
-                            } else {
-                                Slog.e(TAG, "multiple primary volumes in storage list");
-                            }
-                        }
-                        if (mPrimaryVolume == volume) {
-                            // primay volume must be first
-                            mVolumes.add(0, volume);
+                        if (path == null || description == null) {
+                            Slog.e(TAG, "Missing storage path or description in readStorageList");
                         } else {
-                            mVolumes.add(volume);
+                            final StorageVolume volume = new StorageVolume(new File(path),
+                                    descriptionId, primary, removable, emulated, mtpReserve,
+                                    allowMassStorage, maxFileSize, null);
+                            addVolumeLocked(volume);
                         }
-                        mVolumeMap.put(pathString, volume);
                     }
+
                     a.recycle();
                 }
             }
@@ -1139,48 +1227,105 @@
         } catch (IOException e) {
             throw new RuntimeException(e);
         } finally {
-            // compute storage ID for each volume
-            int length = mVolumes.size();
-            for (int i = 0; i < length; i++) {
-                mVolumes.get(i).setStorageId(i);
+            // Compute storage ID for each physical volume; emulated storage is
+            // always 0 when defined.
+            int index = isExternalStorageEmulated() ? 1 : 0;
+            for (StorageVolume volume : mVolumes) {
+                if (!volume.isEmulated()) {
+                    volume.setStorageId(index++);
+                }
             }
             parser.close();
         }
     }
 
     /**
+     * Create and add new {@link StorageVolume} for given {@link UserHandle}
+     * using {@link #mEmulatedTemplate} as template.
+     */
+    private void createEmulatedVolumeForUserLocked(UserHandle user) {
+        if (mEmulatedTemplate == null) {
+            throw new IllegalStateException("Missing emulated volume multi-user template");
+        }
+
+        final UserEnvironment userEnv = new UserEnvironment(user.getIdentifier());
+        final File path = userEnv.getExternalStorageDirectory();
+        final StorageVolume volume = StorageVolume.fromTemplate(mEmulatedTemplate, path, user);
+        volume.setStorageId(0);
+        addVolumeLocked(volume);
+
+        if (mSystemReady) {
+            updatePublicVolumeState(volume, Environment.MEDIA_MOUNTED);
+        } else {
+            // Place stub status for early callers to find
+            mVolumeStates.put(volume.getPath(), Environment.MEDIA_MOUNTED);
+        }
+    }
+
+    private void addVolumeLocked(StorageVolume volume) {
+        Slog.d(TAG, "addVolumeLocked() " + volume);
+        mVolumes.add(volume);
+        final StorageVolume existing = mVolumesByPath.put(volume.getPath(), volume);
+        if (existing != null) {
+            throw new IllegalStateException(
+                    "Volume at " + volume.getPath() + " already exists: " + existing);
+        }
+    }
+
+    private void removeVolumeLocked(StorageVolume volume) {
+        Slog.d(TAG, "removeVolumeLocked() " + volume);
+        mVolumes.remove(volume);
+        mVolumesByPath.remove(volume.getPath());
+        mVolumeStates.remove(volume.getPath());
+    }
+
+    private StorageVolume getPrimaryPhysicalVolume() {
+        synchronized (mVolumesLock) {
+            for (StorageVolume volume : mVolumes) {
+                if (volume.isPrimary() && !volume.isEmulated()) {
+                    return volume;
+                }
+            }
+        }
+        return null;
+    }
+
+    /**
      * Constructs a new MountService instance
      *
      * @param context  Binder context for this service
      */
     public MountService(Context context) {
         mContext = context;
-        readStorageList();
 
-        if (mPrimaryVolume != null) {
-            mExternalStoragePath = mPrimaryVolume.getPath();
-            mEmulateExternalStorage = mPrimaryVolume.isEmulated();
-            if (mEmulateExternalStorage) {
-                Slog.d(TAG, "using emulated external storage");
-                mVolumeStates.put(mExternalStoragePath, Environment.MEDIA_MOUNTED);
-            }
+        synchronized (mVolumesLock) {
+            readStorageListLocked();
         }
 
         // XXX: This will go away soon in favor of IMountServiceObserver
         mPms = (PackageManagerService) ServiceManager.getService("package");
 
-        IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_BOOT_COMPLETED);
-        // don't bother monitoring USB if mass storage is not supported on our primary volume
-        if (mPrimaryVolume != null && mPrimaryVolume.allowMassStorage()) {
-            filter.addAction(UsbManager.ACTION_USB_STATE);
-        }
-        mContext.registerReceiver(mBroadcastReceiver, filter, null, null);
-
         mHandlerThread = new HandlerThread("MountService");
         mHandlerThread.start();
         mHandler = new MountServiceHandler(mHandlerThread.getLooper());
 
+        // Watch for user boot completion
+        mContext.registerReceiverAsUser(mBootReceiver, UserHandle.ALL,
+                new IntentFilter(Intent.ACTION_BOOT_COMPLETED), null, mHandler);
+
+        // Watch for user changes
+        final IntentFilter userFilter = new IntentFilter();
+        userFilter.addAction(Intent.ACTION_USER_ADDED);
+        userFilter.addAction(Intent.ACTION_USER_REMOVED);
+        mContext.registerReceiver(mUserReceiver, userFilter, null, mHandler);
+
+        // Watch for USB changes on primary volume
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (primary != null && primary.allowMassStorage()) {
+            mContext.registerReceiver(
+                    mUsbReceiver, new IntentFilter(UsbManager.ACTION_USB_STATE), null, mHandler);
+        }
+
         // Add OBB Action Handler to MountService thread.
         mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper());
 
@@ -1200,6 +1345,11 @@
         }
     }
 
+    public void systemReady() {
+        mSystemReady = true;
+        mHandler.obtainMessage(H_SYSTEM_READY).sendToTarget();
+    }
+
     /**
      * Exposed API calls below here
      */
@@ -1232,7 +1382,7 @@
         validatePermission(android.Manifest.permission.SHUTDOWN);
 
         Slog.i(TAG, "Shutting down");
-        synchronized (mVolumeStates) {
+        synchronized (mVolumesLock) {
             for (String path : mVolumeStates.keySet()) {
                 String state = mVolumeStates.get(path);
 
@@ -1313,12 +1463,15 @@
         waitForReady();
         validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
 
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (primary == null) return;
+
         // TODO: Add support for multiple share methods
 
         /*
          * If the volume is mounted and we're enabling then unmount it
          */
-        String path = Environment.getExternalStorageDirectory().getPath();
+        String path = primary.getPath();
         String vs = getVolumeState(path);
         String method = "ums";
         if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
@@ -1348,14 +1501,20 @@
 
     public boolean isUsbMassStorageEnabled() {
         waitForReady();
-        return doGetVolumeShared(Environment.getExternalStorageDirectory().getPath(), "ums");
+
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (primary != null) {
+            return doGetVolumeShared(primary.getPath(), "ums");
+        } else {
+            return false;
+        }
     }
 
     /**
      * @return state of the volume at the specified mount point
      */
     public String getVolumeState(String mountPoint) {
-        synchronized (mVolumeStates) {
+        synchronized (mVolumesLock) {
             String state = mVolumeStates.get(mountPoint);
             if (state == null) {
                 Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
@@ -1370,8 +1529,9 @@
         }
     }
 
+    @Override
     public boolean isExternalStorageEmulated() {
-        return mEmulateExternalStorage;
+        return mEmulatedTemplate != null;
     }
 
     public int mountVolume(String path) {
@@ -1437,7 +1597,9 @@
     }
 
     private void warnOnNotMounted() {
-        if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
+        final StorageVolume primary = getPrimaryPhysicalVolume();
+        if (primary != null
+                && Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()))) {
             Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
         }
     }
@@ -1935,14 +2097,23 @@
         }
     }
 
-    public Parcelable[] getVolumeList() {
-        synchronized(mVolumes) {
-            int size = mVolumes.size();
-            Parcelable[] result = new Parcelable[size];
-            for (int i = 0; i < size; i++) {
-                result[i] = mVolumes.get(i);
+    @Override
+    public StorageVolume[] getVolumeList() {
+        final int callingUserId = UserHandle.getCallingUserId();
+        final boolean accessAll = (mContext.checkPermission(
+                android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
+                Binder.getCallingPid(), Binder.getCallingUid()) == PERMISSION_GRANTED);
+
+        synchronized (mVolumesLock) {
+            final ArrayList<StorageVolume> filtered = Lists.newArrayList();
+            for (StorageVolume volume : mVolumes) {
+                final UserHandle owner = volume.getOwner();
+                final boolean ownerMatch = owner == null || owner.getIdentifier() == callingUserId;
+                if (accessAll || ownerMatch) {
+                    filtered.add(volume);
+                }
             }
-            return result;
+            return filtered.toArray(new StorageVolume[filtered.size()]);
         }
     }
 
@@ -2458,7 +2629,7 @@
 
         pw.println("");
 
-        synchronized (mVolumes) {
+        synchronized (mVolumesLock) {
             pw.println("  mVolumes:");
 
             final int N = mVolumes.size();
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 73e82ab..4398441 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -125,6 +125,7 @@
         BatteryService battery = null;
         VibratorService vibrator = null;
         AlarmManagerService alarm = null;
+        MountService mountService = null;
         NetworkManagementService networkManagement = null;
         NetworkStatsService networkStats = null;
         NetworkPolicyManagerService networkPolicy = null;
@@ -374,7 +375,6 @@
         }
 
         if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
-            MountService mountService = null;
             if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) {
                 try {
                     /*
@@ -813,6 +813,7 @@
 
         // These are needed to propagate to the runnable below.
         final Context contextF = context;
+        final MountService mountServiceF = mountService;
         final BatteryService batteryF = battery;
         final NetworkManagementService networkManagementF = networkManagement;
         final NetworkStatsService networkStatsF = networkStats;
@@ -847,6 +848,11 @@
 
                 if (!headless) startSystemUi(contextF);
                 try {
+                    if (mountServiceF != null) mountServiceF.systemReady();
+                } catch (Throwable e) {
+                    reportWtf("making Mount Service ready", e);
+                }
+                try {
                     if (batteryF != null) batteryF.systemReady();
                 } catch (Throwable e) {
                     reportWtf("making Battery Service ready", e);
diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java
index 8301211..48781ac 100644
--- a/services/java/com/android/server/accessibility/ScreenMagnifier.java
+++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java
@@ -28,7 +28,6 @@
 import android.graphics.Canvas;
 import android.graphics.Color;
 import android.graphics.PixelFormat;
-import android.graphics.PointF;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
@@ -40,18 +39,20 @@
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
-import android.util.MathUtils;
 import android.util.Property;
 import android.util.Slog;
 import android.view.Display;
 import android.view.DisplayInfo;
+import android.view.GestureDetector;
+import android.view.GestureDetector.SimpleOnGestureListener;
 import android.view.Gravity;
 import android.view.IDisplayContentChangeListener;
 import android.view.IWindowManager;
 import android.view.MotionEvent;
 import android.view.MotionEvent.PointerCoords;
 import android.view.MotionEvent.PointerProperties;
-import android.view.ScaleGestureDetector.SimpleOnScaleGestureListener;
+import android.view.ScaleGestureDetector;
+import android.view.ScaleGestureDetector.OnScaleGestureListener;
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewConfiguration;
@@ -67,6 +68,8 @@
 import com.android.internal.os.SomeArgs;
 
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
 
 /**
  * This class handles the screen magnification when accessibility is enabled.
@@ -117,7 +120,6 @@
     private static final boolean DEBUG_VIEWPORT_WINDOW = false;
     private static final boolean DEBUG_WINDOW_TRANSITIONS = false;
     private static final boolean DEBUG_ROTATION = false;
-    private static final boolean DEBUG_GESTURE_DETECTOR = false;
     private static final boolean DEBUG_MAGNIFICATION_CONTROLLER = false;
 
     private static final String LOG_TAG = ScreenMagnifier.class.getSimpleName();
@@ -139,7 +141,7 @@
     private final DisplayProvider mDisplayProvider;
 
     private final DetectingStateHandler mDetectingStateHandler = new DetectingStateHandler();
-    private final GestureDetector mGestureDetector;
+    private final MagnifiedContentInteractonStateHandler mMagnifiedContentInteractonStateHandler;
     private final StateViewportDraggingHandler mStateViewportDraggingHandler =
             new StateViewportDraggingHandler();
 
@@ -194,14 +196,15 @@
         mScreenStateObserver = new ScreenStateObserver(mContext, mViewport,
                 mMagnificationController);
 
-        mGestureDetector = new GestureDetector(context);
+        mMagnifiedContentInteractonStateHandler = new MagnifiedContentInteractonStateHandler(
+                context);
 
         transitionToState(STATE_DETECTING);
     }
 
     @Override
     public void onMotionEvent(MotionEvent event, int policyFlags) {
-        mGestureDetector.onMotionEvent(event);
+        mMagnifiedContentInteractonStateHandler.onMotionEvent(event);
         switch (mCurrentState) {
             case STATE_DELEGATING: {
                 handleMotionEventStateDelegating(event, policyFlags);
@@ -213,9 +216,9 @@
                 mStateViewportDraggingHandler.onMotionEvent(event, policyFlags);
             } break;
             case STATE_MAGNIFIED_INTERACTION: {
-                // Handled by the gesture detector. Since the detector
-                // needs all touch events to work properly we cannot
-                // call it only for this state.
+                // mMagnifiedContentInteractonStateHandler handles events only
+                // if this is the current state since it uses ScaleGestureDetecotr
+                // and a GestureDetector which need well formed event stream.
             } break;
             default: {
                 throw new IllegalStateException("Unknown state: " + mCurrentState);
@@ -240,7 +243,7 @@
         mCurrentState = STATE_DETECTING;
         mDetectingStateHandler.clear();
         mStateViewportDraggingHandler.clear();
-        mGestureDetector.clear();
+        mMagnifiedContentInteractonStateHandler.clear();
         if (mNext != null) {
             mNext.clear();
         }
@@ -345,46 +348,29 @@
         mCurrentState = state;
     }
 
-    private final class GestureDetector implements OnScaleGestureListener {
+    private final class MagnifiedContentInteractonStateHandler
+            extends SimpleOnGestureListener implements OnScaleGestureListener {
         private static final float MIN_SCALE = 1.3f;
         private static final float MAX_SCALE = 5.0f;
 
-        private static final float DETECT_SCALING_THRESHOLD = 0.30f;
-        private static final int DETECT_PANNING_THRESHOLD_DIP = 30;
-
-        private final float mScaledDetectPanningThreshold;
+        private static final float SCALING_THRESHOLD = 0.3f;
 
         private final ScaleGestureDetector mScaleGestureDetector;
+        private final GestureDetector mGestureDetector;
 
-        private final PointF mPrevFocus = new PointF(Float.NaN, Float.NaN);
-        private final PointF mInitialFocus = new PointF(Float.NaN, Float.NaN);
-
-        private float mCurrScale = Float.NaN;
-        private float mCurrScaleFactor = 1.0f;
-        private float mPrevScaleFactor = 1.0f;
-        private float mCurrPan;
-        private float mPrevPan;
-
-        private float mScaleFocusX = Float.NaN;
-        private float mScaleFocusY = Float.NaN;
-
+        private float mInitialScaleFactor = -1;
         private boolean mScaling;
-        private boolean mPanning;
 
-        public GestureDetector(Context context) {
-            final float density = context.getResources().getDisplayMetrics().density;
-            mScaledDetectPanningThreshold = DETECT_PANNING_THRESHOLD_DIP * density;
-            mScaleGestureDetector = new ScaleGestureDetector(this);
+        public MagnifiedContentInteractonStateHandler(Context context) {
+            mScaleGestureDetector = new ScaleGestureDetector(context, this);
+            mGestureDetector = new GestureDetector(context, this);
         }
 
         public void onMotionEvent(MotionEvent event) {
             mScaleGestureDetector.onTouchEvent(event);
-            switch (mCurrentState) {
-                case STATE_DETECTING:
-                case STATE_DELEGATING:
-                case STATE_VIEWPORT_DRAGGING: {
-                    return;
-                }
+            mGestureDetector.onTouchEvent(event);
+            if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
+                return;
             }
             if (event.getActionMasked() == MotionEvent.ACTION_UP) {
                 clear();
@@ -401,121 +387,62 @@
         }
 
         @Override
-        public boolean onScale(ScaleGestureDetector detector) {
-            switch (mCurrentState) {
-                case STATE_DETECTING:
-                case STATE_DELEGATING:
-                case STATE_VIEWPORT_DRAGGING: {
-                    return true;
-                }
-                case STATE_MAGNIFIED_INTERACTION: {
-                    mCurrScaleFactor = mScaleGestureDetector.getScaleFactor();
-                    final float scaleDelta = Math.abs(1.0f - mCurrScaleFactor * mPrevScaleFactor);
-                    if (DEBUG_GESTURE_DETECTOR) {
-                        Slog.i(LOG_TAG, "scaleDelta: " + scaleDelta);
-                    }
-                    if (!mScaling && scaleDelta > DETECT_SCALING_THRESHOLD) {
-                        mScaling = true;
-                        clearContextualState();
-                        return true;
-                    }
-                    if (mScaling) {
-                        performScale(detector);
-                    }
-                    mCurrPan = (float) MathUtils.dist(
-                            mScaleGestureDetector.getFocusX(),
-                            mScaleGestureDetector.getFocusY(),
-                            mInitialFocus.x, mInitialFocus.y);
-                    final float panDelta = mCurrPan + mPrevPan;
-                    if (DEBUG_GESTURE_DETECTOR) {
-                        Slog.i(LOG_TAG, "panDelta: " + panDelta);
-                    }
-                    if (!mPanning && panDelta > mScaledDetectPanningThreshold) {
-                        mPanning = true;
-                        clearContextualState();
-                        return true;
-                    }
-                    if (mPanning) {
-                        performPan(detector);
-                    }
-                } break;
-            }
-            return false;
-        }
-
-        @Override
-        public boolean onScaleBegin(ScaleGestureDetector detector) {
-            mPrevScaleFactor *= mCurrScaleFactor;
-            mCurrScale = Float.NaN;
-            mPrevPan += mCurrPan;
-            mPrevFocus.x = mInitialFocus.x = detector.getFocusX();
-            mPrevFocus.y = mInitialFocus.y = detector.getFocusY();
-            return true;
-        }
-
-        @Override
-        public void onScaleEnd(ScaleGestureDetector detector) {
-            clearContextualState();
-        }
-
-        public void clear() {
-            clearContextualState();
-            mScaling = false;
-            mPanning = false;
-        }
-
-        private void clearContextualState() {
-            mCurrScaleFactor = 1.0f;
-            mPrevScaleFactor = 1.0f;
-            mPrevPan = 0;
-            mCurrPan = 0;
-            mInitialFocus.set(Float.NaN, Float.NaN);
-            mPrevFocus.set(Float.NaN, Float.NaN);
-            mCurrScale = Float.NaN;
-            mScaleFocusX = Float.NaN;
-            mScaleFocusY = Float.NaN;
-        }
-
-        private void performPan(ScaleGestureDetector detector) {
-            if (Float.compare(mPrevFocus.x, Float.NaN) == 0
-                    && Float.compare(mPrevFocus.y, Float.NaN) == 0) {
-                mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
-                return;
+        public boolean onScroll(MotionEvent first, MotionEvent second, float distanceX,
+                float distanceY) {
+            if (mCurrentState != STATE_MAGNIFIED_INTERACTION) {
+                return true;
             }
             final float scale = mMagnificationController.getScale();
-            final float scrollX = (detector.getFocusX() - mPrevFocus.x) / scale;
-            final float scrollY = (detector.getFocusY() - mPrevFocus.y) / scale;
-            final float centerX = mMagnificationController.getMagnifiedRegionCenterX()
-                    - scrollX;
-            final float centerY = mMagnificationController.getMagnifiedRegionCenterY()
-                    - scrollY;
+            final float scrollX = distanceX / scale;
+            final float scrollY = distanceY / scale;
+            final float centerX = mMagnificationController.getMagnifiedRegionCenterX() + scrollX;
+            final float centerY = mMagnificationController.getMagnifiedRegionCenterY() + scrollY;
             if (DEBUG_PANNING) {
                 Slog.i(LOG_TAG, "Panned content by scrollX: " + scrollX
                         + " scrollY: " + scrollY);
             }
             mMagnificationController.setMagnifiedRegionCenter(centerX, centerY, false);
-            mPrevFocus.set(detector.getFocusX(), detector.getFocusY());
+            return true;
         }
 
-        private void performScale(ScaleGestureDetector detector) {
-            if (Float.compare(mCurrScale, Float.NaN) == 0) {
-                mCurrScale = mMagnificationController.getScale();
-                return;
+        @Override
+        public boolean onScale(ScaleGestureDetector detector) {
+            if (!mScaling) {
+                if (mInitialScaleFactor < 0) {
+                    mInitialScaleFactor = detector.getScaleFactor();
+                } else {
+                    final float deltaScale = detector.getScaleFactor() - mInitialScaleFactor;
+                    if (Math.abs(deltaScale) > SCALING_THRESHOLD) {
+                        mScaling = true;
+                        return true;
+                    }
+                }
+                return false;
             }
-            final float totalScaleFactor = mPrevScaleFactor * detector.getScaleFactor();
-            final float newScale = mCurrScale * totalScaleFactor;
-            final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE),
-                    MAX_SCALE);
+            final float newScale = mMagnificationController.getScale()
+                    * detector.getScaleFactor();
+            final float normalizedNewScale = Math.min(Math.max(newScale, MIN_SCALE), MAX_SCALE);
             if (DEBUG_SCALING) {
                 Slog.i(LOG_TAG, "normalizedNewScale: " + normalizedNewScale);
             }
-            if (Float.compare(mScaleFocusX, Float.NaN) == 0
-                    && Float.compare(mScaleFocusY, Float.NaN) == 0) {
-                mScaleFocusX = detector.getFocusX();
-                mScaleFocusY = detector.getFocusY();
-            }
-            mMagnificationController.setScale(normalizedNewScale, mScaleFocusX,
-                    mScaleFocusY, false);
+            mMagnificationController.setScale(normalizedNewScale, detector.getFocusX(),
+                    detector.getFocusY(), false);
+            return true;
+        }
+
+        @Override
+        public boolean onScaleBegin(ScaleGestureDetector detector) {
+            return (mCurrentState == STATE_MAGNIFIED_INTERACTION);
+        }
+
+        @Override
+        public void onScaleEnd(ScaleGestureDetector detector) {
+            clear();
+        }
+
+        private void clear() {
+            mInitialScaleFactor = -1;
+            mScaling = false;
         }
     }
 
@@ -1020,7 +947,9 @@
                 }
                 if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
                         || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
-                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG) {
+                        || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG
+                        || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD
+                        || info.type == WindowManager.LayoutParams.TYPE_KEYGUARD_DIALOG) {
                     switch (transition) {
                         case WindowManagerPolicy.TRANSIT_ENTER:
                         case WindowManagerPolicy.TRANSIT_SHOW:
@@ -1473,7 +1402,9 @@
 
         private final ArrayList<WindowInfo> mTempWindowInfoList = new ArrayList<WindowInfo>();
 
-        private final Rect mTempRect = new Rect();
+        private final Rect mTempRect1 = new Rect();
+        private final Rect mTempRect2 = new Rect();
+        private final Rect mTempRect3 = new Rect();
 
         private final IWindowManager mWindowManagerService;
         private final DisplayProvider mDisplayProvider;
@@ -1542,31 +1473,83 @@
             recomputeBounds(false);
         }
 
+        private final Comparator<WindowInfo> mWindowInfoInverseComparator =
+                new Comparator<WindowInfo>() {
+            @Override
+            public int compare(WindowInfo lhs, WindowInfo rhs) {
+                if (lhs.layer != rhs.layer) {
+                    return rhs.layer - lhs.layer;
+                }
+                if (lhs.touchableRegion.top != rhs.touchableRegion.top) {
+                    return rhs.touchableRegion.top - lhs.touchableRegion.top;
+                }
+                if (lhs.touchableRegion.left != rhs.touchableRegion.left) {
+                    return rhs.touchableRegion.left - lhs.touchableRegion.left;
+                }
+                if (lhs.touchableRegion.right != rhs.touchableRegion.right) {
+                    return rhs.touchableRegion.right - lhs.touchableRegion.right;
+                }
+                if (lhs.touchableRegion.bottom != rhs.touchableRegion.bottom) {
+                    return rhs.touchableRegion.bottom - lhs.touchableRegion.bottom;
+                }
+                return 0;
+            }
+        };
+
         public void recomputeBounds(boolean animate) {
-            Rect frame = mTempRect;
-            frame.set(0, 0, mDisplayProvider.getDisplayInfo().logicalWidth,
-                    mDisplayProvider.getDisplayInfo().logicalHeight);
+            Rect magnifiedFrame = mTempRect1;
+            magnifiedFrame.set(0, 0, 0, 0);
+
+            Rect notMagnifiedFrame = mTempRect2;
+            notMagnifiedFrame.set(0, 0, 0, 0);
+
             ArrayList<WindowInfo> infos = mTempWindowInfoList;
             infos.clear();
+            int windowCount = 0;
             try {
                 mWindowManagerService.getVisibleWindowsForDisplay(
                         mDisplayProvider.getDisplay().getDisplayId(), infos);
-                final int windowCount = infos.size();
+                Collections.sort(infos, mWindowInfoInverseComparator);
+                windowCount = infos.size();
                 for (int i = 0; i < windowCount; i++) {
                     WindowInfo info = infos.get(i);
-                    if (info.type == WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
-                            || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD
-                            || info.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG) {
-                        subtract(frame, info.touchableRegion);
+                    if (info.type == WindowManager.LayoutParams.TYPE_MAGNIFICATION_OVERLAY) {
+                        continue;
                     }
-                    info.recycle();
+                    if (isWindowMagnified(info.type)) {
+                        Rect clippedFrame = mTempRect3;
+                        clippedFrame.set(info.touchableRegion);
+                        subtract(clippedFrame, notMagnifiedFrame);
+                        magnifiedFrame.union(clippedFrame);
+                    } else {
+                        Rect clippedFrame = mTempRect3;
+                        clippedFrame.set(info.touchableRegion);
+                        subtract(clippedFrame, magnifiedFrame);
+                        notMagnifiedFrame.union(clippedFrame);
+                    }
+                    if (magnifiedFrame.bottom >= notMagnifiedFrame.top) {
+                        break;
+                    }
                 }
             } catch (RemoteException re) {
                 /* ignore */
             } finally {
-                infos.clear();
+                for (int i = windowCount - 1; i >= 0; i--) {
+                    infos.remove(i).recycle();
+                }
             }
-            resize(frame, animate);
+
+            final int displayWidth = mDisplayProvider.getDisplayInfo().logicalWidth;
+            final int displayHeight = mDisplayProvider.getDisplayInfo().logicalHeight;
+            magnifiedFrame.intersect(0, 0, displayWidth, displayHeight);
+
+            resize(magnifiedFrame, animate);
+        }
+
+        private boolean isWindowMagnified(int type) {
+            return (type != WindowManager.LayoutParams.TYPE_NAVIGATION_BAR
+                    && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD
+                    && type != WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
         }
 
         public void rotationChanged() {
@@ -1812,482 +1795,4 @@
             updateDisplayInfo();
         }
     }
-
-    /**
-     * The listener for receiving notifications when gestures occur.
-     * If you want to listen for all the different gestures then implement
-     * this interface. If you only want to listen for a subset it might
-     * be easier to extend {@link SimpleOnScaleGestureListener}.
-     *
-     * An application will receive events in the following order:
-     * <ul>
-     *  <li>One {@link OnScaleGestureListener#onScaleBegin(ScaleGestureDetector)}
-     *  <li>Zero or more {@link OnScaleGestureListener#onScale(ScaleGestureDetector)}
-     *  <li>One {@link OnScaleGestureListener#onScaleEnd(ScaleGestureDetector)}
-     * </ul>
-     */
-    interface OnScaleGestureListener {
-        /**
-         * Responds to scaling events for a gesture in progress.
-         * Reported by pointer motion.
-         *
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         * @return Whether or not the detector should consider this event
-         *          as handled. If an event was not handled, the detector
-         *          will continue to accumulate movement until an event is
-         *          handled. This can be useful if an application, for example,
-         *          only wants to update scaling factors if the change is
-         *          greater than 0.01.
-         */
-        public boolean onScale(ScaleGestureDetector detector);
-
-        /**
-         * Responds to the beginning of a scaling gesture. Reported by
-         * new pointers going down.
-         *
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         * @return Whether or not the detector should continue recognizing
-         *          this gesture. For example, if a gesture is beginning
-         *          with a focal point outside of a region where it makes
-         *          sense, onScaleBegin() may return false to ignore the
-         *          rest of the gesture.
-         */
-        public boolean onScaleBegin(ScaleGestureDetector detector);
-
-        /**
-         * Responds to the end of a scale gesture. Reported by existing
-         * pointers going up.
-         *
-         * Once a scale has ended, {@link ScaleGestureDetector#getFocusX()}
-         * and {@link ScaleGestureDetector#getFocusY()} will return the location
-         * of the pointer remaining on the screen.
-         *
-         * @param detector The detector reporting the event - use this to
-         *          retrieve extended info about event state.
-         */
-        public void onScaleEnd(ScaleGestureDetector detector);
-    }
-
-    class ScaleGestureDetector {
-
-        private final MinCircleFinder mMinCircleFinder = new MinCircleFinder();
-
-        private final OnScaleGestureListener mListener;
-
-        private float mFocusX;
-        private float mFocusY;
-
-        private float mCurrSpan;
-        private float mPrevSpan;
-        private float mCurrSpanX;
-        private float mCurrSpanY;
-        private float mPrevSpanX;
-        private float mPrevSpanY;
-        private long mCurrTime;
-        private long mPrevTime;
-        private boolean mInProgress;
-
-        public ScaleGestureDetector(OnScaleGestureListener listener) {
-            mListener = listener;
-        }
-
-        /**
-         * Accepts MotionEvents and dispatches events to a {@link OnScaleGestureListener}
-         * when appropriate.
-         *
-         * <p>Applications should pass a complete and consistent event stream to this method.
-         * A complete and consistent event stream involves all MotionEvents from the initial
-         * ACTION_DOWN to the final ACTION_UP or ACTION_CANCEL.</p>
-         *
-         * @param event The event to process
-         * @return true if the event was processed and the detector wants to receive the
-         *         rest of the MotionEvents in this event stream.
-         */
-        public boolean onTouchEvent(MotionEvent event) {
-            boolean streamEnded = false;
-            boolean contextChanged = false;
-            int excludedPtrIdx = -1;
-            final int action = event.getActionMasked();
-            switch (action) {
-                case MotionEvent.ACTION_DOWN:
-                case MotionEvent.ACTION_POINTER_DOWN: {
-                    contextChanged = true;
-                } break;
-                case MotionEvent.ACTION_POINTER_UP: {
-                    contextChanged = true;
-                    excludedPtrIdx = event.getActionIndex();
-                } break;
-                case MotionEvent.ACTION_UP:
-                case MotionEvent.ACTION_CANCEL: {
-                    streamEnded = true;
-                } break;
-            }
-
-            if (mInProgress && (contextChanged || streamEnded)) {
-                mListener.onScaleEnd(this);
-                mInProgress = false;
-                mPrevSpan = 0;
-                mPrevSpanX = 0;
-                mPrevSpanY = 0;
-                return true;
-            }
-
-            final long currTime = mCurrTime;
-
-            mFocusX = 0;
-            mFocusY = 0;
-            mCurrSpan = 0;
-            mCurrSpanX = 0;
-            mCurrSpanY = 0;
-            mCurrTime = 0;
-            mPrevTime = 0;
-
-            if (!streamEnded) {
-                MinCircleFinder.Circle circle =
-                        mMinCircleFinder.computeMinCircleAroundPointers(event);
-                mFocusX = circle.centerX;
-                mFocusY = circle.centerY;
-
-                double sumSlope = 0;
-                final int pointerCount = event.getPointerCount();
-                for (int i = 0; i < pointerCount; i++) {
-                    if (i == excludedPtrIdx) {
-                        continue;
-                    }
-                    float x = event.getX(i) - mFocusX;
-                    float y = event.getY(i) - mFocusY;
-                    if (x == 0) {
-                        x += 0.1f;
-                    }
-                    sumSlope += y / x;
-                }
-                final double avgSlope = sumSlope
-                        / ((excludedPtrIdx < 0) ? pointerCount : pointerCount - 1);
-
-                double angle = Math.atan(avgSlope);
-                mCurrSpan = 2 * circle.radius;
-                mCurrSpanX = (float) Math.abs((Math.cos(angle) * mCurrSpan));
-                mCurrSpanY = (float) Math.abs((Math.sin(angle) * mCurrSpan));
-            }
-
-            if (contextChanged || mPrevSpan == 0 || mPrevSpanX == 0 || mPrevSpanY == 0) {
-                mPrevSpan = mCurrSpan;
-                mPrevSpanX = mCurrSpanX;
-                mPrevSpanY = mCurrSpanY;
-            }
-
-            if (!mInProgress && mCurrSpan != 0 && !streamEnded) {
-                mInProgress = mListener.onScaleBegin(this);
-            }
-
-            if (mInProgress) {
-                mPrevTime = (currTime != 0) ? currTime : event.getEventTime();
-                mCurrTime = event.getEventTime();
-                if (mCurrSpan == 0) {
-                    mListener.onScaleEnd(this);
-                    mInProgress = false;
-                } else {
-                    if (mListener.onScale(this)) {
-                        mPrevSpanX = mCurrSpanX;
-                        mPrevSpanY = mCurrSpanY;
-                        mPrevSpan = mCurrSpan;
-                    }
-                }
-            }
-
-            return true;
-        }
-
-        /**
-         * Returns {@code true} if a scale gesture is in progress.
-         */
-        public boolean isInProgress() {
-            return mInProgress;
-        }
-
-        /**
-         * Get the X coordinate of the current gesture's focal point.
-         * If a gesture is in progress, the focal point is between
-         * each of the pointers forming the gesture.
-         *
-         * If {@link #isInProgress()} would return false, the result of this
-         * function is undefined.
-         *
-         * @return X coordinate of the focal point in pixels.
-         */
-        public float getFocusX() {
-            return mFocusX;
-        }
-
-        /**
-         * Get the Y coordinate of the current gesture's focal point.
-         * If a gesture is in progress, the focal point is between
-         * each of the pointers forming the gesture.
-         *
-         * If {@link #isInProgress()} would return false, the result of this
-         * function is undefined.
-         *
-         * @return Y coordinate of the focal point in pixels.
-         */
-        public float getFocusY() {
-            return mFocusY;
-        }
-
-        /**
-         * Return the average distance between each of the pointers forming the
-         * gesture in progress through the focal point.
-         *
-         * @return Distance between pointers in pixels.
-         */
-        public float getCurrentSpan() {
-            return mCurrSpan;
-        }
-
-        /**
-         * Return the average X distance between each of the pointers forming the
-         * gesture in progress through the focal point.
-         *
-         * @return Distance between pointers in pixels.
-         */
-        public float getCurrentSpanX() {
-            return mCurrSpanX;
-        }
-
-        /**
-         * Return the average Y distance between each of the pointers forming the
-         * gesture in progress through the focal point.
-         *
-         * @return Distance between pointers in pixels.
-         */
-        public float getCurrentSpanY() {
-            return mCurrSpanY;
-        }
-
-        /**
-         * Return the previous average distance between each of the pointers forming the
-         * gesture in progress through the focal point.
-         *
-         * @return Previous distance between pointers in pixels.
-         */
-        public float getPreviousSpan() {
-            return mPrevSpan;
-        }
-
-        /**
-         * Return the previous average X distance between each of the pointers forming the
-         * gesture in progress through the focal point.
-         *
-         * @return Previous distance between pointers in pixels.
-         */
-        public float getPreviousSpanX() {
-            return mPrevSpanX;
-        }
-
-        /**
-         * Return the previous average Y distance between each of the pointers forming the
-         * gesture in progress through the focal point.
-         *
-         * @return Previous distance between pointers in pixels.
-         */
-        public float getPreviousSpanY() {
-            return mPrevSpanY;
-        }
-
-        /**
-         * Return the scaling factor from the previous scale event to the current
-         * event. This value is defined as
-         * ({@link #getCurrentSpan()} / {@link #getPreviousSpan()}).
-         *
-         * @return The current scaling factor.
-         */
-        public float getScaleFactor() {
-            return mPrevSpan > 0 ? mCurrSpan / mPrevSpan : 1;
-        }
-
-        /**
-         * Return the time difference in milliseconds between the previous
-         * accepted scaling event and the current scaling event.
-         *
-         * @return Time difference since the last scaling event in milliseconds.
-         */
-        public long getTimeDelta() {
-            return mCurrTime - mPrevTime;
-        }
-
-        /**
-         * Return the event time of the current event being processed.
-         *
-         * @return Current event time in milliseconds.
-         */
-        public long getEventTime() {
-            return mCurrTime;
-        }
-    }
-
-    private static final class MinCircleFinder {
-        private final ArrayList<PointHolder> mPoints = new ArrayList<PointHolder>();
-        private final ArrayList<PointHolder> sBoundary = new ArrayList<PointHolder>();
-        private final Circle mMinCircle = new Circle();
-
-        /**
-         * Finds the minimal circle that contains all pointers of a motion event.
-         *
-         * @param event A motion event.
-         * @return The minimal circle.
-         */
-        public Circle computeMinCircleAroundPointers(MotionEvent event) {
-            ArrayList<PointHolder> points = mPoints;
-            points.clear();
-            final int pointerCount = event.getPointerCount();
-            for (int i = 0; i < pointerCount; i++) {
-                PointHolder point = PointHolder.obtain(event.getX(i), event.getY(i));
-                points.add(point);
-            }
-            ArrayList<PointHolder> boundary = sBoundary;
-            boundary.clear();
-            computeMinCircleAroundPointsRecursive(points, boundary, mMinCircle);
-            for (int i = points.size() - 1; i >= 0; i--) {
-                points.remove(i).recycle();
-            }
-            boundary.clear();
-            return mMinCircle;
-        }
-
-        private static void computeMinCircleAroundPointsRecursive(ArrayList<PointHolder> points,
-                ArrayList<PointHolder> boundary, Circle outCircle) {
-            if (points.isEmpty()) {
-                if (boundary.size() == 0) {
-                    outCircle.initialize();
-                } else if (boundary.size() == 1) {
-                    outCircle.initialize(boundary.get(0).mData, boundary.get(0).mData);
-                } else if (boundary.size() == 2) {
-                    outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData);
-                } else if (boundary.size() == 3) {
-                    outCircle.initialize(boundary.get(0).mData, boundary.get(1).mData,
-                            boundary.get(2).mData);
-                }
-                return;
-            }
-            PointHolder point = points.remove(points.size() - 1);
-            computeMinCircleAroundPointsRecursive(points, boundary, outCircle);
-            if (!outCircle.contains(point.mData)) {
-                boundary.add(point);
-                computeMinCircleAroundPointsRecursive(points, boundary, outCircle);
-                boundary.remove(point);
-            }
-            points.add(point);
-        }
-
-        private static final class PointHolder {
-            private static final int MAX_POOL_SIZE = 20;
-            private static PointHolder sPool;
-            private static int sPoolSize;
-
-            private PointHolder mNext;
-            private boolean mIsInPool;
-
-            private final PointF mData = new PointF();
-
-            public static PointHolder obtain(float x, float y) {
-                PointHolder holder;
-                if (sPoolSize > 0) {
-                    sPoolSize--;
-                    holder = sPool;
-                    sPool = sPool.mNext;
-                    holder.mNext = null;
-                    holder.mIsInPool = false;
-                } else {
-                    holder = new PointHolder();
-                }
-                holder.mData.set(x, y);
-                return holder;
-            }
-
-            public void recycle() {
-                if (mIsInPool) {
-                    throw new IllegalStateException("Already recycled.");
-                }
-                clear();
-                if (sPoolSize < MAX_POOL_SIZE) {
-                    sPoolSize++;
-                    mNext = sPool;
-                    sPool = this;
-                    mIsInPool = true;
-                }
-            }
-
-            private void clear() {
-                mData.set(0, 0);
-            }
-        }
-
-        public static final class Circle {
-            public float centerX;
-            public float centerY;
-            public float radius;
-
-            private void initialize() {
-                centerX = 0;
-                centerY = 0;
-                radius = 0;
-            }
-
-            private void initialize(PointF first, PointF second, PointF third) {
-                if (!hasLineWithInfiniteSlope(first, second, third)) {
-                    initializeInternal(first, second, third);
-                } else if (!hasLineWithInfiniteSlope(first, third, second)) {
-                    initializeInternal(first, third, second);
-                } else if (!hasLineWithInfiniteSlope(second, first, third)) {
-                    initializeInternal(second, first, third);
-                } else if (!hasLineWithInfiniteSlope(second, third, first)) {
-                    initializeInternal(second, third, first);
-                } else if (!hasLineWithInfiniteSlope(third, first, second)) {
-                    initializeInternal(third, first, second);
-                } else if (!hasLineWithInfiniteSlope(third, second, first)) {
-                    initializeInternal(third, second, first);
-                } else {
-                    initialize();
-                }
-            }
-
-            private void initialize(PointF first, PointF second) {
-                radius = (float) (Math.hypot(second.x - first.x, second.y - first.y) / 2);
-                centerX = (float) (second.x + first.x) / 2;
-                centerY = (float) (second.y + first.y) / 2;
-            }
-
-            public boolean contains(PointF point) {
-                return (int) (Math.hypot(point.x - centerX, point.y - centerY)) <= radius;
-            }
-
-            private void initializeInternal(PointF first, PointF second, PointF third) {
-                final float x1 = first.x;
-                final float y1 = first.y;
-                final float x2 = second.x;
-                final float y2 = second.y;
-                final float x3 = third.x;
-                final float y3 = third.y;
-
-                final float sl1 = (y2 - y1) / (x2 - x1);
-                final float sl2 = (y3 - y2) / (x3 - x2);
-
-                centerX = (int) ((sl1 * sl2 * (y1 - y3) + sl2 * (x1 + x2) - sl1 * (x2 + x3))
-                        / (2 * (sl2 - sl1)));
-                centerY = (int) (-1 / sl1 * (centerX - (x1 + x2) / 2) + (y1 + y2) / 2);
-                radius = (int) Math.hypot(x1 - centerX, y1 - centerY);
-            }
-
-            private boolean hasLineWithInfiniteSlope(PointF first, PointF second, PointF third) {
-                return (second.x - first.x == 0 || third.x - second.x == 0
-                        || second.y - first.y == 0 || third.y - second.y == 0);
-            }
-
-            @Override
-            public String toString() {
-                return "cetner: [" + centerX + ", " + centerY + "] radius: " + radius;
-            }
-        }
-    }
 }
diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java
index ee82050..b75940e 100644
--- a/services/java/com/android/server/display/WifiDisplayAdapter.java
+++ b/services/java/com/android/server/display/WifiDisplayAdapter.java
@@ -27,7 +27,6 @@
 import android.media.RemoteDisplay;
 import android.os.Handler;
 import android.os.IBinder;
-import android.util.Slog;
 import android.view.Surface;
 
 import java.io.PrintWriter;
@@ -50,8 +49,8 @@
 final class WifiDisplayAdapter extends DisplayAdapter {
     private static final String TAG = "WifiDisplayAdapter";
 
-    private WifiDisplayHandle mDisplayHandle;
     private WifiDisplayController mDisplayController;
+    private WifiDisplayDevice mDisplayDevice;
 
     private WifiDisplayStatus mCurrentStatus;
     private boolean mEnabled;
@@ -71,13 +70,6 @@
     public void dumpLocked(PrintWriter pw) {
         super.dumpLocked(pw);
 
-        if (mDisplayHandle == null) {
-            pw.println("mDisplayHandle=null");
-        } else {
-            pw.println("mDisplayHandle:");
-            mDisplayHandle.dumpLocked(pw);
-        }
-
         pw.println("mCurrentStatus=" + getWifiDisplayStatusLocked());
         pw.println("mEnabled=" + mEnabled);
         pw.println("mScanState=" + mScanState);
@@ -151,16 +143,29 @@
         return mCurrentStatus;
     }
 
-    private void handleConnectLocked(WifiDisplay display, String iface) {
+    private void handleConnectLocked(WifiDisplay display,
+            Surface surface, int width, int height, int flags) {
         handleDisconnectLocked();
 
-        mDisplayHandle = new WifiDisplayHandle(display.getDeviceName(), iface);
+        int deviceFlags = 0;
+        if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
+            deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
+        }
+
+        float refreshRate = 60.0f; // TODO: get this for real
+
+        String name = display.getDeviceName();
+        IBinder displayToken = Surface.createDisplay(name);
+        mDisplayDevice = new WifiDisplayDevice(displayToken, name, width, height,
+                refreshRate, deviceFlags, surface);
+        sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_ADDED);
     }
 
     private void handleDisconnectLocked() {
-        if (mDisplayHandle != null) {
-            mDisplayHandle.disposeLocked();
-            mDisplayHandle = null;
+        if (mDisplayDevice != null) {
+            mDisplayDevice.clearSurfaceLocked();
+            sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED);
+            mDisplayDevice = null;
         }
     }
 
@@ -258,9 +263,10 @@
         }
 
         @Override
-        public void onDisplayConnected(WifiDisplay display, String iface) {
+        public void onDisplayConnected(WifiDisplay display, Surface surface,
+                int width, int height, int flags) {
             synchronized (getSyncRoot()) {
-                handleConnectLocked(display, iface);
+                handleConnectLocked(display, surface, width, height, flags);
 
                 if (mActiveDisplayState != WifiDisplayStatus.DISPLAY_STATE_CONNECTED
                         || mActiveDisplay == null
@@ -337,92 +343,4 @@
             return mInfo;
         }
     }
-
-    private final class WifiDisplayHandle implements RemoteDisplay.Listener {
-        private final String mName;
-        private final String mIface;
-        private final RemoteDisplay mRemoteDisplay;
-
-        private WifiDisplayDevice mDevice;
-        private int mLastError;
-
-        public WifiDisplayHandle(String name, String iface) {
-            mName = name;
-            mIface = iface;
-            mRemoteDisplay = RemoteDisplay.listen(iface, this, getHandler());
-
-            Slog.i(TAG, "Listening for Wifi display connections on " + iface
-                    + " from " + mName);
-        }
-
-        public void disposeLocked() {
-            Slog.i(TAG, "Stopped listening for Wifi display connections on " + mIface
-                    + " from " + mName);
-
-            removeDisplayLocked();
-            mRemoteDisplay.dispose();
-        }
-
-        public void dumpLocked(PrintWriter pw) {
-            pw.println("  " + mName + ": " + (mDevice != null ? "connected" : "disconnected"));
-            pw.println("    mIface=" + mIface);
-            pw.println("    mLastError=" + mLastError);
-        }
-
-        // Called on the handler thread.
-        @Override
-        public void onDisplayConnected(Surface surface, int width, int height, int flags) {
-            synchronized (getSyncRoot()) {
-                mLastError = 0;
-                removeDisplayLocked();
-                addDisplayLocked(surface, width, height, flags);
-
-                Slog.i(TAG, "Wifi display connected: " + mName);
-            }
-        }
-
-        // Called on the handler thread.
-        @Override
-        public void onDisplayDisconnected() {
-            synchronized (getSyncRoot()) {
-                mLastError = 0;
-                removeDisplayLocked();
-
-                Slog.i(TAG, "Wifi display disconnected: " + mName);
-            }
-        }
-
-        // Called on the handler thread.
-        @Override
-        public void onDisplayError(int error) {
-            synchronized (getSyncRoot()) {
-                mLastError = error;
-                removeDisplayLocked();
-
-                Slog.i(TAG, "Wifi display disconnected due to error " + error + ": " + mName);
-            }
-        }
-
-        private void addDisplayLocked(Surface surface, int width, int height, int flags) {
-            int deviceFlags = 0;
-            if ((flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0) {
-                deviceFlags |= DisplayDeviceInfo.FLAG_SECURE;
-            }
-
-            float refreshRate = 60.0f; // TODO: get this for real
-
-            IBinder displayToken = Surface.createDisplay(mName);
-            mDevice = new WifiDisplayDevice(displayToken, mName, width, height,
-                    refreshRate, deviceFlags, surface);
-            sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED);
-        }
-
-        private void removeDisplayLocked() {
-            if (mDevice != null) {
-                mDevice.clearSurfaceLocked();
-                sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED);
-                mDevice = null;
-            }
-        }
-    }
 }
diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java
index 144b391..6e0be55 100644
--- a/services/java/com/android/server/display/WifiDisplayController.java
+++ b/services/java/com/android/server/display/WifiDisplayController.java
@@ -23,6 +23,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.hardware.display.WifiDisplay;
+import android.media.RemoteDisplay;
 import android.net.NetworkInfo;
 import android.net.wifi.p2p.WifiP2pConfig;
 import android.net.wifi.p2p.WifiP2pDevice;
@@ -36,6 +37,7 @@
 import android.net.wifi.p2p.WifiP2pManager.PeerListListener;
 import android.os.Handler;
 import android.util.Slog;
+import android.view.Surface;
 
 import java.io.PrintWriter;
 import java.net.Inet4Address;
@@ -64,6 +66,7 @@
     private static final int DEFAULT_CONTROL_PORT = 7236;
     private static final int MAX_THROUGHPUT = 50;
     private static final int CONNECTION_TIMEOUT_SECONDS = 30;
+    private static final int RTSP_TIMEOUT_SECONDS = 15;
 
     private static final int DISCOVER_PEERS_MAX_RETRIES = 10;
     private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500;
@@ -104,12 +107,19 @@
     // The group info obtained after connecting.
     private WifiP2pGroup mConnectedDeviceGroupInfo;
 
-    // The device that we announced to the rest of the system.
-    private WifiP2pDevice mPublishedDevice;
-
     // Number of connection retries remaining.
     private int mConnectionRetriesLeft;
 
+    // The remote display that is listening on the connection.
+    // Created after the Wifi P2P network is connected.
+    private RemoteDisplay mRemoteDisplay;
+
+    // The remote display interface.
+    private String mRemoteDisplayInterface;
+
+    // True if RTSP has connected.
+    private boolean mRemoteDisplayConnected;
+
     public WifiDisplayController(Context context, Handler handler, Listener listener) {
         mContext = context;
         mHandler = handler;
@@ -135,8 +145,10 @@
         pw.println("mDesiredDevice=" + describeWifiP2pDevice(mDesiredDevice));
         pw.println("mConnectingDisplay=" + describeWifiP2pDevice(mConnectingDevice));
         pw.println("mConnectedDevice=" + describeWifiP2pDevice(mConnectedDevice));
-        pw.println("mPublishedDevice=" + describeWifiP2pDevice(mPublishedDevice));
         pw.println("mConnectionRetriesLeft=" + mConnectionRetriesLeft);
+        pw.println("mRemoteDisplay=" + mRemoteDisplay);
+        pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface);
+        pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected);
 
         pw.println("mKnownWifiDisplayPeers: size=" + mKnownWifiDisplayPeers.size());
         for (WifiP2pDevice device : mKnownWifiDisplayPeers) {
@@ -341,7 +353,7 @@
     }
 
     private void retryConnection() {
-        if (mDesiredDevice != null && mPublishedDevice != mDesiredDevice
+        if (mDesiredDevice != null && mConnectedDevice != mDesiredDevice
                 && mConnectionRetriesLeft > 0) {
             mConnectionRetriesLeft -= 1;
             Slog.i(TAG, "Retrying Wifi display connection.  Retries left: "
@@ -363,14 +375,22 @@
     private void updateConnection() {
         // Step 1. Before we try to connect to a new device, tell the system we
         // have disconnected from the old one.
-        if (mPublishedDevice != null && mPublishedDevice != mDesiredDevice) {
+        if (mRemoteDisplay != null && mConnectedDevice != mDesiredDevice) {
+            Slog.i(TAG, "Stopped listening for RTSP connection on " + mRemoteDisplayInterface
+                    + " from Wifi display: " + mConnectedDevice.deviceName);
+
+            mRemoteDisplay.dispose();
+            mRemoteDisplay = null;
+            mRemoteDisplayInterface = null;
+            mRemoteDisplayConnected = false;
+            mHandler.removeCallbacks(mRtspTimeout);
+
             mHandler.post(new Runnable() {
                 @Override
                 public void run() {
                     mListener.onDisplayDisconnected();
                 }
             });
-            mPublishedDevice = null;
 
             // continue to next step
         }
@@ -471,9 +491,9 @@
 
                 @Override
                 public void onFailure(int reason) {
-                    Slog.i(TAG, "Failed to initiate connection to Wifi display: "
-                            + newDevice.deviceName + ", reason=" + reason);
                     if (mConnectingDevice == newDevice) {
+                        Slog.i(TAG, "Failed to initiate connection to Wifi display: "
+                                + newDevice.deviceName + ", reason=" + reason);
                         mConnectingDevice = null;
                         handleConnectionFailure(false);
                     }
@@ -482,8 +502,8 @@
             return; // wait for asynchronous callback
         }
 
-        // Step 6. Publish the new connection.
-        if (mConnectedDevice != null && mPublishedDevice == null) {
+        // Step 6. Listen for incoming connections.
+        if (mConnectedDevice != null && mRemoteDisplay == null) {
             Inet4Address addr = getInterfaceAddress(mConnectedDeviceGroupInfo);
             if (addr == null) {
                 Slog.i(TAG, "Failed to get local interface address for communicating "
@@ -492,17 +512,57 @@
                 return; // done
             }
 
-            final WifiDisplay display = createWifiDisplay(mConnectedDevice);
+            final WifiP2pDevice oldDevice = mConnectedDevice;
             final int port = getPortNumber(mConnectedDevice);
             final String iface = addr.getHostAddress() + ":" + port;
+            mRemoteDisplayInterface = iface;
 
-            mPublishedDevice = mConnectedDevice;
-            mHandler.post(new Runnable() {
+            Slog.i(TAG, "Listening for RTSP connection on " + iface
+                    + " from Wifi display: " + mConnectedDevice.deviceName);
+
+            mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() {
                 @Override
-                public void run() {
-                    mListener.onDisplayConnected(display, iface);
+                public void onDisplayConnected(final Surface surface,
+                        final int width, final int height, final int flags) {
+                    if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) {
+                        Slog.i(TAG, "Opened RTSP connection with Wifi display: "
+                                + mConnectedDevice.deviceName);
+                        mRemoteDisplayConnected = true;
+                        mHandler.removeCallbacks(mRtspTimeout);
+
+                        final WifiDisplay display = createWifiDisplay(mConnectedDevice);
+                        mHandler.post(new Runnable() {
+                            @Override
+                            public void run() {
+                                mListener.onDisplayConnected(display,
+                                        surface, width, height, flags);
+                            }
+                        });
+                    }
                 }
-            });
+
+                @Override
+                public void onDisplayDisconnected() {
+                    if (mConnectedDevice == oldDevice) {
+                        Slog.i(TAG, "Closed RTSP connection with Wifi display: "
+                                + mConnectedDevice.deviceName);
+                        mHandler.removeCallbacks(mRtspTimeout);
+                        disconnect();
+                    }
+                }
+
+                @Override
+                public void onDisplayError(int error) {
+                    if (mConnectedDevice == oldDevice) {
+                        Slog.i(TAG, "Lost RTSP connection with Wifi display due to error "
+                                + error + ": " + mConnectedDevice.deviceName);
+                        mHandler.removeCallbacks(mRtspTimeout);
+                        handleConnectionFailure(false);
+                    }
+                }
+            }, mHandler);
+
+            mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000);
         }
     }
 
@@ -591,17 +651,30 @@
         }
     };
 
+    private final Runnable mRtspTimeout = new Runnable() {
+        @Override
+        public void run() {
+            if (mConnectedDevice != null
+                    && mRemoteDisplay != null && !mRemoteDisplayConnected) {
+                Slog.i(TAG, "Timed out waiting for Wifi display RTSP connection after "
+                        + RTSP_TIMEOUT_SECONDS + " seconds: "
+                        + mConnectedDevice.deviceName);
+                handleConnectionFailure(true);
+            }
+        }
+    };
+
     private void handleConnectionFailure(boolean timeoutOccurred) {
+        Slog.i(TAG, "Wifi display connection failed!");
+
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                mListener.onDisplayConnectionFailed();
+            }
+        });
+
         if (mDesiredDevice != null) {
-            Slog.i(TAG, "Wifi display connection failed!");
-
-            mHandler.post(new Runnable() {
-                @Override
-                public void run() {
-                    mListener.onDisplayConnectionFailed();
-                }
-            });
-
             if (mConnectionRetriesLeft > 0) {
                 mHandler.postDelayed(new Runnable() {
                     @Override
@@ -714,7 +787,8 @@
 
         void onDisplayConnecting(WifiDisplay display);
         void onDisplayConnectionFailed();
-        void onDisplayConnected(WifiDisplay display, String iface);
+        void onDisplayConnected(WifiDisplay display,
+                Surface surface, int width, int height, int flags);
         void onDisplayDisconnected();
     }
 }
diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java
index e19a803..01a9b4b 100644
--- a/services/java/com/android/server/pm/PackageManagerService.java
+++ b/services/java/com/android/server/pm/PackageManagerService.java
@@ -109,6 +109,7 @@
 import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.os.UserHandle;
+import android.os.Environment.UserEnvironment;
 import android.provider.Settings.Secure;
 import android.security.SystemKeyStore;
 import android.util.DisplayMetrics;
@@ -214,7 +215,7 @@
     /**
      * Whether verification is enabled by default.
      */
-    private static final boolean DEFAULT_VERIFY_ENABLE = true;
+    private static final boolean DEFAULT_VERIFY_ENABLE = false;
 
     /**
      * The default maximum time to wait for the verification agent to return in
@@ -6135,19 +6136,20 @@
                 mounted = true;
             } else {
                 final String status = Environment.getExternalStorageState();
-
-                mounted = status.equals(Environment.MEDIA_MOUNTED)
-                        || status.equals(Environment.MEDIA_MOUNTED_READ_ONLY);
+                mounted = (Environment.MEDIA_MOUNTED.equals(status)
+                        || Environment.MEDIA_MOUNTED_READ_ONLY.equals(status));
             }
 
             if (mounted) {
-                final File externalCacheDir = Environment
+                final UserEnvironment userEnv = new UserEnvironment(mStats.userHandle);
+
+                final File externalCacheDir = userEnv
                         .getExternalStorageAppCacheDirectory(mStats.packageName);
                 final long externalCacheSize = mContainerService
                         .calculateDirectorySize(externalCacheDir.getPath());
                 mStats.externalCacheSize = externalCacheSize;
 
-                final File externalDataDir = Environment
+                final File externalDataDir = userEnv
                         .getExternalStorageAppDataDirectory(mStats.packageName);
                 long externalDataSize = mContainerService.calculateDirectorySize(externalDataDir
                         .getPath());
@@ -6157,12 +6159,12 @@
                 }
                 mStats.externalDataSize = externalDataSize;
 
-                final File externalMediaDir = Environment
+                final File externalMediaDir = userEnv
                         .getExternalStorageAppMediaDirectory(mStats.packageName);
                 mStats.externalMediaSize = mContainerService
                         .calculateDirectorySize(externalMediaDir.getPath());
 
-                final File externalObbDir = Environment
+                final File externalObbDir = userEnv
                         .getExternalStorageAppObbDirectory(mStats.packageName);
                 mStats.externalObbSize = mContainerService.calculateDirectorySize(externalObbDir
                         .getPath());
@@ -8361,20 +8363,22 @@
                     if (conn.mContainerService == null) {
                         return;
                     }
-                    final File externalCacheDir = Environment
+
+                    final UserEnvironment userEnv = new UserEnvironment(curUser);
+                    final File externalCacheDir = userEnv
                             .getExternalStorageAppCacheDirectory(packageName);
                     try {
                         conn.mContainerService.clearDirectory(externalCacheDir.toString());
                     } catch (RemoteException e) {
                     }
                     if (allData) {
-                        final File externalDataDir = Environment
+                        final File externalDataDir = userEnv
                                 .getExternalStorageAppDataDirectory(packageName);
                         try {
                             conn.mContainerService.clearDirectory(externalDataDir.toString());
                         } catch (RemoteException e) {
                         }
-                        final File externalMediaDir = Environment
+                        final File externalMediaDir = userEnv
                                 .getExternalStorageAppMediaDirectory(packageName);
                         try {
                             conn.mContainerService.clearDirectory(externalMediaDir.toString());
diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java
index 607ff39..3ef6d4c 100644
--- a/services/java/com/android/server/usb/UsbDeviceManager.java
+++ b/services/java/com/android/server/usb/UsbDeviceManager.java
@@ -183,12 +183,9 @@
         // We do not show the USB notification if the primary volume supports mass storage.
         // The legacy mass storage UI will be used instead.
         boolean massStorageSupported = false;
-        StorageManager storageManager = (StorageManager)
-                mContext.getSystemService(Context.STORAGE_SERVICE);
-        StorageVolume[] volumes = storageManager.getVolumeList();
-        if (volumes.length > 0) {
-            massStorageSupported = volumes[0].allowMassStorage();
-        }
+        final StorageManager storageManager = StorageManager.from(mContext);
+        final StorageVolume primary = storageManager.getPrimaryVolume();
+        massStorageSupported = primary != null && primary.allowMassStorage();
         mUseUsbNotification = !massStorageSupported;
 
         // make sure the ADB_ENABLED setting value matches the current state
diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java
index 6951879..b1e4f4b 100755
--- a/services/java/com/android/server/wm/WindowManagerService.java
+++ b/services/java/com/android/server/wm/WindowManagerService.java
@@ -3153,6 +3153,7 @@
         info.compatibilityScale = window.mGlobalScale;
         info.visible = window.isVisibleLw()
                 || info.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND;
+        info.layer = window.mLayer;
         window.getTouchableRegion(mTempRegion);
         mTempRegion.getBounds(info.touchableRegion);
         return info;
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Blur25.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Blur25.java
index 697bbb1..9728c12 100644
--- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Blur25.java
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Blur25.java
@@ -21,14 +21,16 @@
 import android.renderscript.Allocation;
 import android.renderscript.Element;
 import android.renderscript.RenderScript;
-import android.renderscript.Script;
-import android.renderscript.ScriptC;
+import android.renderscript.ScriptIntrinsicBlur;
 import android.renderscript.Type;
 import android.util.Log;
 import android.widget.SeekBar;
 import android.widget.TextView;
 
 public class Blur25 extends TestBase {
+    private boolean mUseIntrinsic = false;
+    private ScriptIntrinsicBlur mIntrinsic;
+
     private int MAX_RADIUS = 25;
     private ScriptC_threshold mScript;
     private ScriptC_vertical_blur mScriptVBlur;
@@ -39,6 +41,10 @@
     private Allocation mScratchPixelsAllocation2;
 
 
+    public Blur25(boolean useIntrinsic) {
+        mUseIntrinsic = useIntrinsic;
+    }
+
     public boolean onBar1Setup(SeekBar b, TextView t) {
         t.setText("Radius");
         b.setProgress(100);
@@ -67,40 +73,59 @@
         int width = mInPixelsAllocation.getType().getX();
         int height = mInPixelsAllocation.getType().getY();
 
-        Type.Builder tb = new Type.Builder(mRS, Element.F32_4(mRS));
-        tb.setX(width);
-        tb.setY(height);
-        mScratchPixelsAllocation1 = Allocation.createTyped(mRS, tb.create());
-        mScratchPixelsAllocation2 = Allocation.createTyped(mRS, tb.create());
+        if (mUseIntrinsic) {
+            mIntrinsic = ScriptIntrinsicBlur.create(mRS, Element.U8_4(mRS));
+            mIntrinsic.setRadius(25.f);
+            mIntrinsic.setInput(mInPixelsAllocation);
+        } else {
 
-        mScriptVBlur = new ScriptC_vertical_blur(mRS, res, R.raw.vertical_blur);
-        mScriptHBlur = new ScriptC_horizontal_blur(mRS, res, R.raw.horizontal_blur);
+            Type.Builder tb = new Type.Builder(mRS, Element.F32_4(mRS));
+            tb.setX(width);
+            tb.setY(height);
+            mScratchPixelsAllocation1 = Allocation.createTyped(mRS, tb.create());
+            mScratchPixelsAllocation2 = Allocation.createTyped(mRS, tb.create());
 
-        mScript = new ScriptC_threshold(mRS, res, R.raw.threshold);
-        mScript.set_width(width);
-        mScript.set_height(height);
-        mScript.set_radius(mRadius);
+            mScriptVBlur = new ScriptC_vertical_blur(mRS, res, R.raw.vertical_blur);
+            mScriptHBlur = new ScriptC_horizontal_blur(mRS, res, R.raw.horizontal_blur);
 
-        mScriptVBlur.invoke_setSaturation(mSaturation);
+            mScript = new ScriptC_threshold(mRS, res, R.raw.threshold);
+            mScript.set_width(width);
+            mScript.set_height(height);
+            mScript.set_radius(mRadius);
 
-        mScript.bind_InPixel(mInPixelsAllocation);
-        mScript.bind_OutPixel(mOutPixelsAllocation);
-        mScript.bind_ScratchPixel1(mScratchPixelsAllocation1);
-        mScript.bind_ScratchPixel2(mScratchPixelsAllocation2);
+            mScriptVBlur.invoke_setSaturation(mSaturation);
 
-        mScript.set_vBlurScript(mScriptVBlur);
-        mScript.set_hBlurScript(mScriptHBlur);
+            mScript.bind_InPixel(mInPixelsAllocation);
+            mScript.bind_OutPixel(mOutPixelsAllocation);
+            mScript.bind_ScratchPixel1(mScratchPixelsAllocation1);
+            mScript.bind_ScratchPixel2(mScratchPixelsAllocation2);
+
+            mScript.set_vBlurScript(mScriptVBlur);
+            mScript.set_hBlurScript(mScriptHBlur);
+        }
     }
 
     public void runTest() {
-        mScript.invoke_filter();
+        if (mUseIntrinsic) {
+            mIntrinsic.forEach(mOutPixelsAllocation);
+        } else {
+            mScript.invoke_filter();
+        }
     }
 
     public void setupBenchmark() {
-        mScript.set_radius(MAX_RADIUS);
+        if (mUseIntrinsic) {
+            mIntrinsic.setRadius(MAX_RADIUS);
+        } else {
+            mScript.set_radius(MAX_RADIUS);
+        }
     }
 
     public void exitBenchmark() {
-        mScript.set_radius(mRadius);
+        if (mUseIntrinsic) {
+            mIntrinsic.setRadius(mRadius);
+        } else {
+            mScript.set_radius(mRadius);
+        }
     }
 }
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Convolve5x5.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Convolve5x5.java
new file mode 100644
index 0000000..b3914d1
--- /dev/null
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/Convolve5x5.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image;
+
+import java.lang.Math;
+
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.Matrix4f;
+import android.renderscript.RenderScript;
+import android.renderscript.Script;
+import android.renderscript.ScriptC;
+import android.renderscript.ScriptGroup;
+import android.renderscript.ScriptIntrinsicConvolve5x5;
+import android.renderscript.Type;
+import android.util.Log;
+
+public class Convolve5x5 extends TestBase {
+    private ScriptC_convolve5x5 mScript;
+    private ScriptIntrinsicConvolve5x5 mIntrinsic;
+
+    private int mWidth;
+    private int mHeight;
+    private boolean mUseIntrinsic;
+
+    public Convolve5x5(boolean useIntrinsic) {
+        mUseIntrinsic = useIntrinsic;
+    }
+
+    public void createTest(android.content.res.Resources res) {
+        mWidth = mInPixelsAllocation.getType().getX();
+        mHeight = mInPixelsAllocation.getType().getY();
+
+        float f[] = new float[25];
+        f[0] = 0.012f; f[1] = 0.025f; f[2] = 0.031f; f[3] = 0.025f; f[4] = 0.012f;
+        f[5] = 0.025f; f[6] = 0.057f; f[7] = 0.075f; f[8] = 0.057f; f[9] = 0.025f;
+        f[10]= 0.031f; f[11]= 0.075f; f[12]= 0.095f; f[13]= 0.075f; f[14]= 0.031f;
+        f[15]= 0.025f; f[16]= 0.057f; f[17]= 0.075f; f[18]= 0.057f; f[19]= 0.025f;
+        f[20]= 0.012f; f[21]= 0.025f; f[22]= 0.031f; f[23]= 0.025f; f[24]= 0.012f;
+
+        if (mUseIntrinsic) {
+            mIntrinsic = ScriptIntrinsicConvolve5x5.create(mRS, Element.U8_4(mRS));
+            mIntrinsic.setCoefficients(f);
+            mIntrinsic.setInput(mInPixelsAllocation);
+        } else {
+            mScript = new ScriptC_convolve5x5(mRS, res, R.raw.convolve5x5);
+            mScript.set_gCoeffs(f);
+            mScript.set_gIn(mInPixelsAllocation);
+            mScript.set_gWidth(mWidth);
+            mScript.set_gHeight(mHeight);
+        }
+    }
+
+    public void runTest() {
+        if (mUseIntrinsic) {
+            mIntrinsic.forEach(mOutPixelsAllocation);
+        } else {
+            mScript.forEach_root(mOutPixelsAllocation);
+        }
+    }
+
+}
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/CrossProcess.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/CrossProcess.java
new file mode 100644
index 0000000..b9e3524
--- /dev/null
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/CrossProcess.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.rs.image;
+
+import java.lang.Math;
+
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicLUT;
+import android.util.Log;
+
+public class CrossProcess extends TestBase {
+    private ScriptIntrinsicLUT mIntrinsic;
+
+    public void createTest(android.content.res.Resources res) {
+        mIntrinsic = ScriptIntrinsicLUT.create(mRS, Element.U8_4(mRS));
+        for (int ct=0; ct < 256; ct++) {
+            float f = ((float)ct) / 255.f;
+
+            float r = f;
+            if (r < 0.5f) {
+                r = 4.0f * r * r * r;
+            } else {
+                r = 1.0f - r;
+                r = 1.0f - (4.0f * r * r * r);
+            }
+            mIntrinsic.setRed(ct, (int)(r * 255.f + 0.5f));
+
+            float g = f;
+            if (g < 0.5f) {
+                g = 2.0f * g * g;
+            } else {
+                g = 1.0f - g;
+                g = 1.0f - (2.0f * g * g);
+            }
+            mIntrinsic.setGreen(ct, (int)(g * 255.f + 0.5f));
+
+            float b = f * 0.5f + 0.25f;
+            mIntrinsic.setBlue(ct, (int)(b * 255.f + 0.5f));
+        }
+
+    }
+
+    public void runTest() {
+        mIntrinsic.forEach(mInPixelsAllocation, mOutPixelsAllocation);
+    }
+
+}
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
index 37577eb..7b84355 100644
--- a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/ImageProcessingActivity.java
@@ -135,62 +135,74 @@
             mTest = new LevelsV4(true, true);
             break;
         case 4:
-            mTest = new Blur25();
+            mTest = new Blur25(false);
             break;
         case 5:
-            mTest = new Greyscale();
+            mTest = new Blur25(true);
             break;
         case 6:
-            mTest = new Grain();
+            mTest = new Greyscale();
             break;
         case 7:
-            mTest = new Fisheye(false, false);
+            mTest = new Grain();
             break;
         case 8:
-            mTest = new Fisheye(false, true);
+            mTest = new Fisheye(false, false);
             break;
         case 9:
-            mTest = new Fisheye(true, false);
+            mTest = new Fisheye(false, true);
             break;
         case 10:
-            mTest = new Fisheye(true, true);
+            mTest = new Fisheye(true, false);
             break;
         case 11:
-            mTest = new Vignette(false, false);
+            mTest = new Fisheye(true, true);
             break;
         case 12:
-            mTest = new Vignette(false, true);
+            mTest = new Vignette(false, false);
             break;
         case 13:
-            mTest = new Vignette(true, false);
+            mTest = new Vignette(false, true);
             break;
         case 14:
-            mTest = new Vignette(true, true);
+            mTest = new Vignette(true, false);
             break;
         case 15:
-            mTest = new GroupTest(false);
+            mTest = new Vignette(true, true);
             break;
         case 16:
-            mTest = new GroupTest(true);
+            mTest = new GroupTest(false);
             break;
         case 17:
-            mTest = new Convolve3x3(false);
+            mTest = new GroupTest(true);
             break;
         case 18:
-            mTest = new Convolve3x3(true);
+            mTest = new Convolve3x3(false);
             break;
         case 19:
-            mTest = new ColorMatrix(false, false);
+            mTest = new Convolve3x3(true);
             break;
         case 20:
-            mTest = new ColorMatrix(true, false);
+            mTest = new ColorMatrix(false, false);
             break;
         case 21:
-            mTest = new ColorMatrix(true, true);
+            mTest = new ColorMatrix(true, false);
             break;
         case 22:
+            mTest = new ColorMatrix(true, true);
+            break;
+        case 23:
             mTest = new Copy();
             break;
+        case 24:
+            mTest = new CrossProcess();
+            break;
+        case 25:
+            mTest = new Convolve5x5(false);
+            break;
+        case 26:
+            mTest = new Convolve5x5(true);
+            break;
         }
 
         mTest.createBaseTest(this, mBitmapIn);
@@ -203,30 +215,34 @@
     }
 
     void setupTests() {
-        mTestNames = new String[23];
+        mTestNames = new String[27];
         mTestNames[0] = "Levels Vec3 Relaxed";
         mTestNames[1] = "Levels Vec4 Relaxed";
         mTestNames[2] = "Levels Vec3 Full";
         mTestNames[3] = "Levels Vec4 Full";
         mTestNames[4] = "Blur radius 25";
-        mTestNames[5] = "Greyscale";
-        mTestNames[6] = "Grain";
-        mTestNames[7] = "Fisheye Full";
-        mTestNames[8] = "Fisheye Relaxed";
-        mTestNames[9] = "Fisheye Approximate Full";
-        mTestNames[10] = "Fisheye Approximate Relaxed";
-        mTestNames[11] = "Vignette Full";
-        mTestNames[12] = "Vignette Relaxed";
-        mTestNames[13] = "Vignette Approximate Full";
-        mTestNames[14] = "Vignette Approximate Relaxed";
-        mTestNames[15] = "Group Test (emulated)";
-        mTestNames[16] = "Group Test (native)";
-        mTestNames[17] = "Convolve 3x3";
-        mTestNames[18] = "Intrinsics Convolve 3x3";
-        mTestNames[19] = "ColorMatrix";
-        mTestNames[20] = "Intrinsics ColorMatrix";
-        mTestNames[21] = "Intrinsics ColorMatrix Grey";
-        mTestNames[22] = "Copy";
+        mTestNames[5] = "Intrinsic Blur radius 25";
+        mTestNames[6] = "Greyscale";
+        mTestNames[7] = "Grain";
+        mTestNames[8] = "Fisheye Full";
+        mTestNames[9] = "Fisheye Relaxed";
+        mTestNames[10] = "Fisheye Approximate Full";
+        mTestNames[11] = "Fisheye Approximate Relaxed";
+        mTestNames[12] = "Vignette Full";
+        mTestNames[13] = "Vignette Relaxed";
+        mTestNames[14] = "Vignette Approximate Full";
+        mTestNames[15] = "Vignette Approximate Relaxed";
+        mTestNames[16] = "Group Test (emulated)";
+        mTestNames[17] = "Group Test (native)";
+        mTestNames[18] = "Convolve 3x3";
+        mTestNames[19] = "Intrinsics Convolve 3x3";
+        mTestNames[20] = "ColorMatrix";
+        mTestNames[21] = "Intrinsics ColorMatrix";
+        mTestNames[22] = "Intrinsics ColorMatrix Grey";
+        mTestNames[23] = "Copy";
+        mTestNames[24] = "CrossProcess (using LUT)";
+        mTestNames[25] = "Convolve 5x5";
+        mTestNames[26] = "Intrinsics Convolve 5x5";
         mTestSpinner.setAdapter(new ArrayAdapter<String>(
             this, R.layout.spinner_layout, mTestNames));
     }
diff --git a/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/convolve5x5.rs b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/convolve5x5.rs
new file mode 100644
index 0000000..fe6cf31
--- /dev/null
+++ b/tests/RenderScriptTests/ImageProcessing/src/com/android/rs/image/convolve5x5.rs
@@ -0,0 +1,74 @@
+/*
+ * 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.
+ */
+
+#pragma version(1)
+#pragma rs java_package_name(com.android.rs.image)
+#pragma rs_fp_relaxed
+
+int32_t gWidth;
+int32_t gHeight;
+rs_allocation gIn;
+
+float gCoeffs[25];
+
+void root(uchar4 *out, uint32_t x, uint32_t y) {
+    uint32_t x0 = max((int32_t)x-2, 0);
+    uint32_t x1 = max((int32_t)x-1, 0);
+    uint32_t x2 = x;
+    uint32_t x3 = min((int32_t)x+1, gWidth-1);
+    uint32_t x4 = min((int32_t)x+2, gWidth-1);
+
+    uint32_t y0 = max((int32_t)y-2, 0);
+    uint32_t y1 = max((int32_t)y-1, 0);
+    uint32_t y2 = y;
+    uint32_t y3 = min((int32_t)y+1, gHeight-1);
+    uint32_t y4 = min((int32_t)y+2, gHeight-1);
+
+    float4 p0 = convert_float4(((uchar4 *)rsGetElementAt(gIn, x0, y0))[0]) * gCoeffs[0]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x1, y0))[0]) * gCoeffs[1]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x2, y0))[0]) * gCoeffs[2]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x3, y0))[0]) * gCoeffs[3]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x4, y0))[0]) * gCoeffs[4];
+
+    float4 p1 = convert_float4(((uchar4 *)rsGetElementAt(gIn, x0, y1))[0]) * gCoeffs[5]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x1, y1))[0]) * gCoeffs[6]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x2, y1))[0]) * gCoeffs[7]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x3, y1))[0]) * gCoeffs[8]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x4, y1))[0]) * gCoeffs[9];
+
+    float4 p2 = convert_float4(((uchar4 *)rsGetElementAt(gIn, x0, y2))[0]) * gCoeffs[10]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x1, y2))[0]) * gCoeffs[11]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x2, y2))[0]) * gCoeffs[12]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x3, y2))[0]) * gCoeffs[13]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x4, y2))[0]) * gCoeffs[14];
+
+    float4 p3 = convert_float4(((uchar4 *)rsGetElementAt(gIn, x0, y3))[0]) * gCoeffs[15]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x1, y3))[0]) * gCoeffs[16]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x2, y3))[0]) * gCoeffs[17]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x3, y3))[0]) * gCoeffs[18]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x4, y3))[0]) * gCoeffs[19];
+
+    float4 p4 = convert_float4(((uchar4 *)rsGetElementAt(gIn, x0, y4))[0]) * gCoeffs[20]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x1, y4))[0]) * gCoeffs[21]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x2, y4))[0]) * gCoeffs[22]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x3, y4))[0]) * gCoeffs[23]
+              + convert_float4(((uchar4 *)rsGetElementAt(gIn, x4, y4))[0]) * gCoeffs[24];
+
+    p0 = clamp(p0 + p1 + p2 + p3 + p4, 0.f, 255.f);
+    *out = convert_uchar4(p0);
+}
+
+
diff --git a/wifi/java/android/net/wifi/WifiStateMachine.java b/wifi/java/android/net/wifi/WifiStateMachine.java
index 2e50b08..4cbb824 100644
--- a/wifi/java/android/net/wifi/WifiStateMachine.java
+++ b/wifi/java/android/net/wifi/WifiStateMachine.java
@@ -138,10 +138,6 @@
     private boolean mScanResultIsPending = false;
     /* Tracks if the current scan settings are active */
     private boolean mSetScanActive = false;
-    /* High perf mode is true if an app has held a high perf Wifi Lock */
-    private boolean mHighPerfMode = false;
-    /* Tracks if user has disabled suspend optimizations through settings */
-    private AtomicBoolean mSuspendOptEnabled = new AtomicBoolean(true);
 
     private boolean mBluetoothConnectionActive = false;
 
@@ -338,10 +334,8 @@
     static final int CMD_START_PACKET_FILTERING           = BASE + 84;
     /* Clear packet filter */
     static final int CMD_STOP_PACKET_FILTERING            = BASE + 85;
-    /* Set suspend mode optimizations in the driver */
-    static final int CMD_SET_SUSPEND_OPTIMIZATIONS        = BASE + 86;
-    /* Clear suspend mode optimizations in the driver */
-    static final int CMD_CLEAR_SUSPEND_OPTIMIZATIONS      = BASE + 87;
+    /* Enable suspend mode optimizations in the driver */
+    static final int CMD_SET_SUSPEND_OPT_ENABLED          = BASE + 86;
     /* When there are no saved networks, we do a periodic scan to notify user of
      * an open network */
     static final int CMD_NO_NETWORKS_PERIODIC_SCAN        = BASE + 88;
@@ -386,8 +380,19 @@
      */
     private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
 
-    /* Tracks if power save is enabled in driver */
-    private boolean mPowerSaveEnabled = true;;
+    /* Tracks if suspend optimizations need to be disabled by DHCP,
+     * screen or due to high perf mode.
+     * When any of them needs to disable it, we keep the suspend optimizations
+     * disabled
+     */
+    private int mSuspendOptNeedsDisabled = 0;
+
+    private static final int SUSPEND_DUE_TO_DHCP       = 1;
+    private static final int SUSPEND_DUE_TO_HIGH_PERF  = 1<<1;
+    private static final int SUSPEND_DUE_TO_SCREEN     = 1<<2;
+
+    /* Tracks if user has enabled suspend optimizations through settings */
+    private AtomicBoolean mUserWantsSuspendOpt = new AtomicBoolean(true);
 
     /**
      * Default framework scan interval in milliseconds. This is used in the scenario in which
@@ -599,7 +604,7 @@
         mPrimaryDeviceType = mContext.getResources().getString(
                 com.android.internal.R.string.config_wifi_p2p_device_type);
 
-        mSuspendOptEnabled.set(Settings.Secure.getInt(mContext.getContentResolver(),
+        mUserWantsSuspendOpt.set(Settings.Secure.getInt(mContext.getContentResolver(),
                     Settings.Secure.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
 
         mContext.registerReceiver(
@@ -637,20 +642,20 @@
                         enableBackgroundScanCommand(false);
                     }
                     enableAllNetworks();
-                    if (mSuspendOptEnabled.get()) {
+                    if (mUserWantsSuspendOpt.get()) {
                         if (DBG) log("Clear suspend optimizations");
-                        sendMessage(CMD_CLEAR_SUSPEND_OPTIMIZATIONS);
+                        sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 0, 0));
                     }
                 } else if (action.equals(Intent.ACTION_SCREEN_OFF)) {
                     enableRssiPolling(false);
                     if (mBackgroundScanSupported) {
                         enableBackgroundScanCommand(true);
                     }
-                    if (mSuspendOptEnabled.get()) {
+                    if (mUserWantsSuspendOpt.get()) {
                         if (DBG) log("Enable suspend optimizations");
                         //Allow 2s for suspend optimizations to be set
                         mSuspendWakeLock.acquire(2000);
-                        sendMessage(CMD_SET_SUSPEND_OPTIMIZATIONS);
+                        sendMessage(obtainMessage(CMD_SET_SUSPEND_OPT_ENABLED, 1, 0));
                     }
                 }
             }
@@ -671,7 +676,7 @@
                 new ContentObserver(getHandler()) {
                     @Override
                     public void onChange(boolean selfChange) {
-                        mSuspendOptEnabled.set(Settings.Secure.getInt(mContext.getContentResolver(),
+                        mUserWantsSuspendOpt.set(Settings.Secure.getInt(mContext.getContentResolver(),
                                 Settings.Secure.WIFI_SUSPEND_OPTIMIZATIONS_ENABLED, 1) == 1);
                     }
                 });
@@ -1172,8 +1177,8 @@
         sb.append("mLastNetworkId ").append(mLastNetworkId).append(LS);
         sb.append("mReconnectCount ").append(mReconnectCount).append(LS);
         sb.append("mIsScanMode ").append(mIsScanMode).append(LS);
-        sb.append("mHighPerfMode").append(mHighPerfMode).append(LS);
-        sb.append("mSuspendOptEnabled").append(mSuspendOptEnabled).append(LS);
+        sb.append("mUserWantsSuspendOpt ").append(mUserWantsSuspendOpt).append(LS);
+        sb.append("mSuspendOptNeedsDisabled ").append(mSuspendOptNeedsDisabled).append(LS);
         sb.append("Supplicant status").append(LS)
                 .append(mWifiNative.status()).append(LS).append(LS);
 
@@ -1191,8 +1196,7 @@
                 case CMD_START_DRIVER:
                 case CMD_SET_SCAN_MODE:
                 case CMD_SET_HIGH_PERF_MODE:
-                case CMD_SET_SUSPEND_OPTIMIZATIONS:
-                case CMD_CLEAR_SUSPEND_OPTIMIZATIONS:
+                case CMD_SET_SUSPEND_OPT_ENABLED:
                 case CMD_ENABLE_BACKGROUND_SCAN:
                 case CMD_ENABLE_ALL_NETWORKS:
                 return false;
@@ -1324,6 +1328,32 @@
         setFrequencyBand(band, false);
     }
 
+    private void setSuspendOptimizationsNative(int reason, boolean enabled) {
+        if (DBG) log("setSuspendOptimizationsNative: " + reason + " " + enabled);
+        if (enabled) {
+            mSuspendOptNeedsDisabled &= ~reason;
+            /* None of dhcp, screen or highperf need it disabled and user wants it enabled */
+            if (mSuspendOptNeedsDisabled == 0 && mUserWantsSuspendOpt.get()) {
+                mWifiNative.setSuspendOptimizations(true);
+                if (DBG) log("Enabled, mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
+            }
+        } else {
+            mSuspendOptNeedsDisabled |= reason;
+            mWifiNative.setSuspendOptimizations(false);
+            if (DBG) log("Disabled, mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
+        }
+    }
+
+    private void setSuspendOptimizations(int reason, boolean enabled) {
+        if (DBG) log("setSuspendOptimizations: " + reason + " " + enabled);
+        if (enabled) {
+            mSuspendOptNeedsDisabled &= ~reason;
+        } else {
+            mSuspendOptNeedsDisabled |= reason;
+        }
+        if (DBG) log("mSuspendOptNeedsDisabled " + mSuspendOptNeedsDisabled);
+    }
+
     private void setWifiState(int wifiState) {
         final int previousWifiState = mWifiState.get();
 
@@ -1736,18 +1766,16 @@
                     mWifiNative.BLUETOOTH_COEXISTENCE_MODE_DISABLED);
         }
 
-        /* Disable power save during DHCP */
-        if (mPowerSaveEnabled) {
-            mPowerSaveEnabled = false;
-            mWifiNative.setPowerSave(mPowerSaveEnabled);
-        }
+        /* Disable power save and suspend optimizations during DHCP */
+        mWifiNative.setPowerSave(false);
+        setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, false);
     }
 
 
     void handlePostDhcpSetup() {
-        /* Restore power save */
-        mPowerSaveEnabled = true;
-        mWifiNative.setPowerSave(mPowerSaveEnabled);
+        /* Restore power save and suspend optimizations */
+        mWifiNative.setPowerSave(true);
+        setSuspendOptimizationsNative(SUSPEND_DUE_TO_DHCP, true);
 
         // Set the coexistence mode back to its default value
         mWifiNative.setBluetoothCoexistenceMode(
@@ -1880,7 +1908,11 @@
                     mEnableBackgroundScan = (message.arg1 == 1);
                     break;
                 case CMD_SET_HIGH_PERF_MODE:
-                    mHighPerfMode = (message.arg1 == 1);
+                    if (message.arg1 == 1) {
+                        setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, false);
+                    } else {
+                        setSuspendOptimizations(SUSPEND_DUE_TO_HIGH_PERF, true);
+                    }
                     break;
                     /* Discard */
                 case CMD_LOAD_DRIVER:
@@ -1927,14 +1959,18 @@
                 case CMD_RESPONSE_AP_CONFIG:
                 case WifiWatchdogStateMachine.POOR_LINK_DETECTED:
                 case WifiWatchdogStateMachine.GOOD_LINK_DETECTED:
-                case CMD_CLEAR_SUSPEND_OPTIMIZATIONS:
                 case CMD_NO_NETWORKS_PERIODIC_SCAN:
                     break;
                 case DhcpStateMachine.CMD_ON_QUIT:
                     mDhcpStateMachine = null;
                     break;
-                case CMD_SET_SUSPEND_OPTIMIZATIONS:
-                    mSuspendWakeLock.release();
+                case CMD_SET_SUSPEND_OPT_ENABLED:
+                    if (message.arg1 == 1) {
+                        mSuspendWakeLock.release();
+                        setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, true);
+                    } else {
+                        setSuspendOptimizations(SUSPEND_DUE_TO_SCREEN, false);
+                    }
                     break;
                 case WifiMonitor.DRIVER_HUNG_EVENT:
                     setWifiEnabled(false);
@@ -2670,7 +2706,7 @@
                 mWifiNative.stopFilteringMulticastV4Packets();
             }
 
-            mWifiNative.setPowerSave(mPowerSaveEnabled);
+            mWifiNative.setPowerSave(true);
 
             if (mIsScanMode) {
                 mWifiNative.setScanResultHandling(SCAN_ONLY_MODE);
@@ -2797,20 +2833,19 @@
                         loge("Illegal arugments to CMD_STOP_PACKET_FILTERING");
                     }
                     break;
-                case CMD_SET_SUSPEND_OPTIMIZATIONS:
-                    if (!mHighPerfMode) {
-                        mWifiNative.setSuspendOptimizations(true);
+                case CMD_SET_SUSPEND_OPT_ENABLED:
+                    if (message.arg1 == 1) {
+                        setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, true);
+                        mSuspendWakeLock.release();
+                    } else {
+                        setSuspendOptimizationsNative(SUSPEND_DUE_TO_SCREEN, false);
                     }
-                    mSuspendWakeLock.release();
-                    break;
-                case CMD_CLEAR_SUSPEND_OPTIMIZATIONS:
-                    mWifiNative.setSuspendOptimizations(false);
                     break;
                 case CMD_SET_HIGH_PERF_MODE:
-                    mHighPerfMode = (message.arg1 == 1);
-                    if (mHighPerfMode) {
-                        //Disable any suspend optimizations
-                        mWifiNative.setSuspendOptimizations(false);
+                    if (message.arg1 == 1) {
+                        setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, false);
+                    } else {
+                        setSuspendOptimizationsNative(SUSPEND_DUE_TO_HIGH_PERF, true);
                     }
                     break;
                 default: