Merge "Support for nested bundles in setApplicationRestrictions"
diff --git a/api/current.txt b/api/current.txt
index 8dd8f20..b3d5fd0 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -2736,6 +2736,7 @@
   }
 
   public class AccountManager {
+    method public boolean accountAuthenticated(android.accounts.Account);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle);
     method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean);
@@ -2796,6 +2797,7 @@
     field public static final java.lang.String KEY_ERROR_CODE = "errorCode";
     field public static final java.lang.String KEY_ERROR_MESSAGE = "errorMessage";
     field public static final java.lang.String KEY_INTENT = "intent";
+    field public static final java.lang.String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH = "lastAuthenticatedTimeMillisEpoch";
     field public static final java.lang.String KEY_PASSWORD = "password";
     field public static final java.lang.String KEY_USERDATA = "userdata";
     field public static final java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
@@ -11169,6 +11171,7 @@
     field public static final int NV21 = 17; // 0x11
     field public static final int PRIVATE = 34; // 0x22
     field public static final int RAW10 = 37; // 0x25
+    field public static final int RAW12 = 38; // 0x26
     field public static final int RAW_SENSOR = 32; // 0x20
     field public static final int RGB_565 = 4; // 0x4
     field public static final int UNKNOWN = 0; // 0x0
@@ -22541,6 +22544,7 @@
     field public static java.lang.String DIRECTORY_RINGTONES;
     field public static final java.lang.String MEDIA_BAD_REMOVAL = "bad_removal";
     field public static final java.lang.String MEDIA_CHECKING = "checking";
+    field public static final java.lang.String MEDIA_EJECTING = "ejecting";
     field public static final java.lang.String MEDIA_MOUNTED = "mounted";
     field public static final java.lang.String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
     field public static final java.lang.String MEDIA_NOFS = "nofs";
@@ -26855,6 +26859,7 @@
     method public void ioReceive();
     method public void ioSend();
     method public deprecated synchronized void resize(int);
+    method public void setAutoPadding(boolean);
     method public void setFromFieldPacker(int, android.renderscript.FieldPacker);
     method public void setFromFieldPacker(int, int, android.renderscript.FieldPacker);
     method public void setFromFieldPacker(int, int, int, int, android.renderscript.FieldPacker);
@@ -27502,6 +27507,41 @@
     method public android.renderscript.ScriptGroup create();
   }
 
+  public class ScriptGroup2 extends android.renderscript.BaseObj {
+    ctor public ScriptGroup2(long, android.renderscript.RenderScript);
+    method public java.lang.Object[] execute(java.lang.Object...);
+  }
+
+  public static final class ScriptGroup2.Binding {
+    ctor public ScriptGroup2.Binding(android.renderscript.Script.FieldID, java.lang.Object);
+    method public android.renderscript.Script.FieldID getField();
+    method public java.lang.Object getValue();
+  }
+
+  public static final class ScriptGroup2.Builder {
+    ctor public ScriptGroup2.Builder(android.renderscript.RenderScript);
+    method public android.renderscript.ScriptGroup2.UnboundValue addInput();
+    method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+    method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object...);
+    method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+    method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object...);
+    method public android.renderscript.ScriptGroup2 create(android.renderscript.ScriptGroup2.Future...);
+  }
+
+  public static class ScriptGroup2.Closure extends android.renderscript.BaseObj {
+    ctor public ScriptGroup2.Closure(long, android.renderscript.RenderScript);
+    ctor public ScriptGroup2.Closure(android.renderscript.RenderScript, android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+    ctor public ScriptGroup2.Closure(android.renderscript.RenderScript, android.renderscript.Script.InvokeID, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+    method public android.renderscript.ScriptGroup2.Future getGlobal(android.renderscript.Script.FieldID);
+    method public android.renderscript.ScriptGroup2.Future getReturn();
+  }
+
+  public static class ScriptGroup2.Future {
+  }
+
+  public static class ScriptGroup2.UnboundValue {
+  }
+
   public abstract class ScriptIntrinsic extends android.renderscript.Script {
   }
 
@@ -34294,8 +34334,10 @@
     method public long getTimeDelta();
     method public boolean isInProgress();
     method public boolean isQuickScaleEnabled();
+    method public boolean isSecondaryButtonScaleEnabled();
     method public boolean onTouchEvent(android.view.MotionEvent);
     method public void setQuickScaleEnabled(boolean);
+    method public void setSecondaryButtonScaleEnabled(boolean);
   }
 
   public static abstract interface ScaleGestureDetector.OnScaleGestureListener {
diff --git a/api/system-current.txt b/api/system-current.txt
index b1b5cf3..9462054 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -2815,6 +2815,7 @@
   }
 
   public class AccountManager {
+    method public boolean accountAuthenticated(android.accounts.Account);
     method public android.accounts.AccountManagerFuture<android.os.Bundle> addAccount(java.lang.String, java.lang.String, java.lang.String[], android.os.Bundle, android.app.Activity, android.accounts.AccountManagerCallback<android.os.Bundle>, android.os.Handler);
     method public boolean addAccountExplicitly(android.accounts.Account, java.lang.String, android.os.Bundle);
     method public void addOnAccountsUpdatedListener(android.accounts.OnAccountsUpdateListener, android.os.Handler, boolean);
@@ -2875,6 +2876,7 @@
     field public static final java.lang.String KEY_ERROR_CODE = "errorCode";
     field public static final java.lang.String KEY_ERROR_MESSAGE = "errorMessage";
     field public static final java.lang.String KEY_INTENT = "intent";
+    field public static final java.lang.String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH = "lastAuthenticatedTimeMillisEpoch";
     field public static final java.lang.String KEY_PASSWORD = "password";
     field public static final java.lang.String KEY_USERDATA = "userdata";
     field public static final java.lang.String LOGIN_ACCOUNTS_CHANGED_ACTION = "android.accounts.LOGIN_ACCOUNTS_CHANGED";
@@ -11455,6 +11457,7 @@
     field public static final int NV21 = 17; // 0x11
     field public static final int PRIVATE = 34; // 0x22
     field public static final int RAW10 = 37; // 0x25
+    field public static final int RAW12 = 38; // 0x26
     field public static final int RAW_SENSOR = 32; // 0x20
     field public static final int RGB_565 = 4; // 0x4
     field public static final int UNKNOWN = 0; // 0x0
@@ -14204,6 +14207,7 @@
     method public void setHdmiMhlVendorCommandListener(android.hardware.hdmi.HdmiTvClient.HdmiMhlVendorCommandListener);
     method public void setInputChangeListener(android.hardware.hdmi.HdmiTvClient.InputChangeListener);
     method public void setRecordListener(android.hardware.hdmi.HdmiRecordListener);
+    method public void setSystemAudioMode(boolean, android.hardware.hdmi.HdmiTvClient.SelectCallback);
     method public void setSystemAudioMute(boolean);
     method public void setSystemAudioVolume(int, int, int);
     method public void startOneTouchRecord(int, android.hardware.hdmi.HdmiRecordSources.RecordSource);
@@ -24404,6 +24408,7 @@
     field public static java.lang.String DIRECTORY_RINGTONES;
     field public static final java.lang.String MEDIA_BAD_REMOVAL = "bad_removal";
     field public static final java.lang.String MEDIA_CHECKING = "checking";
+    field public static final java.lang.String MEDIA_EJECTING = "ejecting";
     field public static final java.lang.String MEDIA_MOUNTED = "mounted";
     field public static final java.lang.String MEDIA_MOUNTED_READ_ONLY = "mounted_ro";
     field public static final java.lang.String MEDIA_NOFS = "nofs";
@@ -28833,6 +28838,7 @@
     method public void ioReceive();
     method public void ioSend();
     method public deprecated synchronized void resize(int);
+    method public void setAutoPadding(boolean);
     method public void setFromFieldPacker(int, android.renderscript.FieldPacker);
     method public void setFromFieldPacker(int, int, android.renderscript.FieldPacker);
     method public void setFromFieldPacker(int, int, int, int, android.renderscript.FieldPacker);
@@ -29480,6 +29486,41 @@
     method public android.renderscript.ScriptGroup create();
   }
 
+  public class ScriptGroup2 extends android.renderscript.BaseObj {
+    ctor public ScriptGroup2(long, android.renderscript.RenderScript);
+    method public java.lang.Object[] execute(java.lang.Object...);
+  }
+
+  public static final class ScriptGroup2.Binding {
+    ctor public ScriptGroup2.Binding(android.renderscript.Script.FieldID, java.lang.Object);
+    method public android.renderscript.Script.FieldID getField();
+    method public java.lang.Object getValue();
+  }
+
+  public static final class ScriptGroup2.Builder {
+    ctor public ScriptGroup2.Builder(android.renderscript.RenderScript);
+    method public android.renderscript.ScriptGroup2.UnboundValue addInput();
+    method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+    method public android.renderscript.ScriptGroup2.Closure addInvoke(android.renderscript.Script.InvokeID, java.lang.Object...);
+    method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+    method public android.renderscript.ScriptGroup2.Closure addKernel(android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object...);
+    method public android.renderscript.ScriptGroup2 create(android.renderscript.ScriptGroup2.Future...);
+  }
+
+  public static class ScriptGroup2.Closure extends android.renderscript.BaseObj {
+    ctor public ScriptGroup2.Closure(long, android.renderscript.RenderScript);
+    ctor public ScriptGroup2.Closure(android.renderscript.RenderScript, android.renderscript.Script.KernelID, android.renderscript.Type, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+    ctor public ScriptGroup2.Closure(android.renderscript.RenderScript, android.renderscript.Script.InvokeID, java.lang.Object[], java.util.Map<android.renderscript.Script.FieldID, java.lang.Object>);
+    method public android.renderscript.ScriptGroup2.Future getGlobal(android.renderscript.Script.FieldID);
+    method public android.renderscript.ScriptGroup2.Future getReturn();
+  }
+
+  public static class ScriptGroup2.Future {
+  }
+
+  public static class ScriptGroup2.UnboundValue {
+  }
+
   public abstract class ScriptIntrinsic extends android.renderscript.Script {
   }
 
@@ -36835,8 +36876,10 @@
     method public long getTimeDelta();
     method public boolean isInProgress();
     method public boolean isQuickScaleEnabled();
+    method public boolean isSecondaryButtonScaleEnabled();
     method public boolean onTouchEvent(android.view.MotionEvent);
     method public void setQuickScaleEnabled(boolean);
+    method public void setSecondaryButtonScaleEnabled(boolean);
   }
 
   public static abstract interface ScaleGestureDetector.OnScaleGestureListener {
diff --git a/core/java/android/accounts/AccountManager.java b/core/java/android/accounts/AccountManager.java
index 6957435c..480d171 100644
--- a/core/java/android/accounts/AccountManager.java
+++ b/core/java/android/accounts/AccountManager.java
@@ -203,6 +203,14 @@
     public static final String KEY_USERDATA = "userdata";
 
     /**
+     * Bundle key used to supply the last time the credentials of the account
+     * were authenticated successfully. Time is specified in milliseconds since
+     * epoch.
+     */
+    public static final String KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH =
+            "lastAuthenticatedTimeMillisEpoch";
+
+    /**
      * Authenticators using 'customTokens' option will also get the UID of the
      * caller
      */
@@ -663,6 +671,31 @@
     }
 
     /**
+     * Informs the system that the account has been authenticated recently. This
+     * recency may be used by other applications to verify the account. This
+     * should be called only when the user has entered correct credentials for
+     * the account.
+     * <p>
+     * It is not safe to call this method from the main thread. As such, call it
+     * from another thread.
+     * <p>
+     * This method requires the caller to hold the permission
+     * {@link android.Manifest.permission#AUTHENTICATE_ACCOUNTS} and should be
+     * called from the account's authenticator.
+     *
+     * @param account The {@link Account} to be updated.
+     */
+    public boolean accountAuthenticated(Account account) {
+        if (account == null)
+            throw new IllegalArgumentException("account is null");
+        try {
+            return mService.accountAuthenticated(account);
+        } catch (RemoteException e) {
+            throw new RuntimeException(e);
+        }
+    }
+
+    /**
      * Rename the specified {@link Account}.  This is equivalent to removing
      * the existing account and adding a new renamed account with the old
      * account's user data.
@@ -1544,15 +1577,20 @@
      *     with these fields if activity or password was supplied and
      *     the account was successfully verified:
      * <ul>
-     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account created
+     * <li> {@link #KEY_ACCOUNT_NAME} - the name of the account verified
      * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
      * <li> {@link #KEY_BOOLEAN_RESULT} - true to indicate success
      * </ul>
      *
      * If no activity or password was specified, the returned Bundle contains
-     * only {@link #KEY_INTENT} with the {@link Intent} needed to launch the
-     * password prompt.  If an error occurred,
-     * {@link AccountManagerFuture#getResult()} throws:
+     * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
+     * password prompt.
+     * 
+     * <p>Also the returning Bundle may contain {@link
+     * #KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH} indicating the last time the
+     * credential was validated/created.
+     * 
+     * If an error occurred,{@link AccountManagerFuture#getResult()} throws:
      * <ul>
      * <li> {@link AuthenticatorException} if the authenticator failed to respond
      * <li> {@link OperationCanceledException} if the operation was canceled for
@@ -1625,9 +1663,9 @@
      * <li> {@link #KEY_ACCOUNT_TYPE} - the type of the account
      * </ul>
      *
-     * If no activity was specified, the returned Bundle contains only
+     * If no activity was specified, the returned Bundle contains
      * {@link #KEY_INTENT} with the {@link Intent} needed to launch the
-     * password prompt.  If an error occurred,
+     * password prompt. If an error occurred,
      * {@link AccountManagerFuture#getResult()} throws:
      * <ul>
      * <li> {@link AuthenticatorException} if the authenticator failed to respond
diff --git a/core/java/android/accounts/IAccountManager.aidl b/core/java/android/accounts/IAccountManager.aidl
index aa41161..04b3c88 100644
--- a/core/java/android/accounts/IAccountManager.aidl
+++ b/core/java/android/accounts/IAccountManager.aidl
@@ -67,6 +67,7 @@
         boolean expectActivityLaunch);
     void confirmCredentialsAsUser(in IAccountManagerResponse response, in Account account,
         in Bundle options, boolean expectActivityLaunch, int userId);
+    boolean accountAuthenticated(in Account account);
     void getAuthTokenLabel(in IAccountManagerResponse response, String accountType,
         String authTokenType);
 
diff --git a/core/java/android/animation/FloatKeyframeSet.java b/core/java/android/animation/FloatKeyframeSet.java
index 56da940..0173079 100644
--- a/core/java/android/animation/FloatKeyframeSet.java
+++ b/core/java/android/animation/FloatKeyframeSet.java
@@ -118,13 +118,14 @@
             FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
             if (fraction < nextKeyframe.getFraction()) {
                 final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
-                if (interpolator != null) {
-                    fraction = interpolator.getInterpolation(fraction);
-                }
                 float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                     (nextKeyframe.getFraction() - prevKeyframe.getFraction());
                 float prevValue = prevKeyframe.getFloatValue();
                 float nextValue = nextKeyframe.getFloatValue();
+                // Apply interpolator on the proportional duration.
+                if (interpolator != null) {
+                    intervalFraction = interpolator.getInterpolation(intervalFraction);
+                }
                 return mEvaluator == null ?
                         prevValue + intervalFraction * (nextValue - prevValue) :
                         ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
diff --git a/core/java/android/animation/IntKeyframeSet.java b/core/java/android/animation/IntKeyframeSet.java
index 12a4bf9..73f9af1 100644
--- a/core/java/android/animation/IntKeyframeSet.java
+++ b/core/java/android/animation/IntKeyframeSet.java
@@ -117,13 +117,14 @@
             IntKeyframe nextKeyframe = (IntKeyframe) mKeyframes.get(i);
             if (fraction < nextKeyframe.getFraction()) {
                 final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
-                if (interpolator != null) {
-                    fraction = interpolator.getInterpolation(fraction);
-                }
                 float intervalFraction = (fraction - prevKeyframe.getFraction()) /
                     (nextKeyframe.getFraction() - prevKeyframe.getFraction());
                 int prevValue = prevKeyframe.getIntValue();
                 int nextValue = nextKeyframe.getIntValue();
+                // Apply interpolator on the proportional duration.
+                if (interpolator != null) {
+                    intervalFraction = interpolator.getInterpolation(intervalFraction);
+                }
                 return mEvaluator == null ?
                         prevValue + (int)(intervalFraction * (nextValue - prevValue)) :
                         ((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
diff --git a/core/java/android/animation/KeyframeSet.java b/core/java/android/animation/KeyframeSet.java
index c80e162..32edd4d 100644
--- a/core/java/android/animation/KeyframeSet.java
+++ b/core/java/android/animation/KeyframeSet.java
@@ -201,7 +201,6 @@
      * @return The animated value.
      */
     public Object getValue(float fraction) {
-
         // Special-case optimization for the common case of only two keyframes
         if (mNumKeyframes == 2) {
             if (mInterpolator != null) {
@@ -238,12 +237,13 @@
             Keyframe nextKeyframe = mKeyframes.get(i);
             if (fraction < nextKeyframe.getFraction()) {
                 final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
-                if (interpolator != null) {
-                    fraction = interpolator.getInterpolation(fraction);
-                }
                 final float prevFraction = prevKeyframe.getFraction();
                 float intervalFraction = (fraction - prevFraction) /
                     (nextKeyframe.getFraction() - prevFraction);
+                // Apply interpolator on the proportional duration.
+                if (interpolator != null) {
+                    intervalFraction = interpolator.getInterpolation(intervalFraction);
+                }
                 return mEvaluator.evaluate(intervalFraction, prevKeyframe.getValue(),
                         nextKeyframe.getValue());
             }
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 59fe490..a0f40f6 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -402,13 +402,7 @@
                 new CachedServiceFetcher<StorageManager>() {
             @Override
             public StorageManager createService(ContextImpl ctx) {
-                try {
-                    return new StorageManager(
-                            ctx.getContentResolver(), ctx.mMainThread.getHandler().getLooper());
-                } catch (RemoteException rex) {
-                    Log.e(TAG, "Failed to create StorageManager", rex);
-                    return null;
-                }
+                return new StorageManager(ctx, ctx.mMainThread.getHandler().getLooper());
             }});
 
         registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class,
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index e20057d..4b81fd4 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -2750,15 +2750,21 @@
             }
         }
 
-        addSharedLibrariesForBackwardCompatibility(owner);
+        modifySharedLibrariesForBackwardCompatibility(owner);
 
         return true;
     }
 
-    private static void addSharedLibrariesForBackwardCompatibility(Package owner) {
-        if (owner.applicationInfo.targetSdkVersion <= Build.VERSION_CODES.LOLLIPOP_MR1) {
-            owner.usesLibraries = ArrayUtils.add(owner.usesLibraries, "org.apache.http.legacy");
-        }
+    private static void modifySharedLibrariesForBackwardCompatibility(Package owner) {
+        // "org.apache.http.legacy" is now a part of the boot classpath so it doesn't need
+        // to be an explicit dependency.
+        //
+        // A future change will remove this library from the boot classpath, at which point
+        // all apps that target SDK 21 and earlier will have it automatically added to their
+        // dependency lists.
+        owner.usesLibraries = ArrayUtils.remove(owner.usesLibraries, "org.apache.http.legacy");
+        owner.usesOptionalLibraries = ArrayUtils.remove(owner.usesOptionalLibraries,
+                "org.apache.http.legacy");
     }
 
     /**
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
index d5dfaf6..9bc2f46 100644
--- a/core/java/android/hardware/ICameraService.aidl
+++ b/core/java/android/hardware/ICameraService.aidl
@@ -75,4 +75,11 @@
                     out BinderHolder device);
 
     int setTorchMode(String CameraId, boolean enabled, IBinder clientBinder);
+
+    /**
+     * Notify the camera service of a system event.  Should only be called from system_server.
+     *
+     * Callers require the android.permission.CAMERA_SEND_SYSTEM_EVENTS permission.
+     */
+    oneway void notifySystemEvent(int eventId, int arg0);
 }
diff --git a/core/java/android/hardware/camera2/CameraCharacteristics.java b/core/java/android/hardware/camera2/CameraCharacteristics.java
index a0217c2..1503bf5 100644
--- a/core/java/android/hardware/camera2/CameraCharacteristics.java
+++ b/core/java/android/hardware/camera2/CameraCharacteristics.java
@@ -991,7 +991,8 @@
      * <ul>
      * <li>Processed (but stalling): any non-RAW format with a stallDurations &gt; 0.
      * Typically JPEG format (ImageFormat#JPEG).</li>
-     * <li>Raw formats: ImageFormat#RAW_SENSOR, ImageFormat#RAW10 and ImageFormat#RAW_OPAQUE.</li>
+     * <li>Raw formats: ImageFormat#RAW_SENSOR, ImageFormat#RAW10, ImageFormat#RAW12,
+     * and ImageFormat#RAW_OPAQUE.</li>
      * <li>Processed (but not-stalling): any non-RAW format without a stall duration.
      * Typically ImageFormat#YUV_420_888, ImageFormat#NV21, ImageFormat#YV12.</li>
      * </ul>
@@ -1023,6 +1024,7 @@
      * <ul>
      * <li>ImageFormat#RAW_SENSOR</li>
      * <li>ImageFormat#RAW10</li>
+     * <li>ImageFormat#RAW12</li>
      * <li>Opaque <code>RAW</code></li>
      * </ul>
      * <p>LEGACY mode devices ({@link CameraCharacteristics#INFO_SUPPORTED_HARDWARE_LEVEL android.info.supportedHardwareLevel} <code>==</code> LEGACY)
diff --git a/core/java/android/hardware/hdmi/HdmiTvClient.java b/core/java/android/hardware/hdmi/HdmiTvClient.java
index a94c1da..a336e5c 100644
--- a/core/java/android/hardware/hdmi/HdmiTvClient.java
+++ b/core/java/android/hardware/hdmi/HdmiTvClient.java
@@ -168,7 +168,22 @@
     }
 
     /**
-     * Sets system audio volume
+     * Sets system audio mode.
+     *
+     * @param enabled set to {@code true} to enable the mode; otherwise {@code false}
+     * @param callback callback to get the result with
+     * @throws {@link IllegalArgumentException} if the {@code callback} is null
+     */
+    public void setSystemAudioMode(boolean enabled, SelectCallback callback) {
+        try {
+            mService.setSystemAudioMode(enabled, getCallbackWrapper(callback));
+        } catch (RemoteException e) {
+            Log.e(TAG, "failed to set system audio mode:", e);
+        }
+    }
+
+    /**
+     * Sets system audio volume.
      *
      * @param oldIndex current volume index
      * @param newIndex volume index to be set
@@ -183,7 +198,7 @@
     }
 
     /**
-     * Sets system audio mute status
+     * Sets system audio mute status.
      *
      * @param mute {@code true} if muted; otherwise, {@code false}
      */
@@ -196,7 +211,7 @@
     }
 
     /**
-     * Sets record listener
+     * Sets record listener.
      *
      * @param listener
      */
diff --git a/core/java/android/net/ConnectivityManager.java b/core/java/android/net/ConnectivityManager.java
index a0e2bf8..3abccbc 100644
--- a/core/java/android/net/ConnectivityManager.java
+++ b/core/java/android/net/ConnectivityManager.java
@@ -40,6 +40,7 @@
 import android.util.ArrayMap;
 import android.util.Log;
 
+import com.android.internal.net.VpnConfig;
 import com.android.internal.telephony.ITelephony;
 import com.android.internal.telephony.PhoneConstants;
 import com.android.internal.util.Protocol;
@@ -2447,6 +2448,38 @@
     }
 
     /**
+     * Resets all connectivity manager settings back to factory defaults.
+     * @hide
+     */
+    public void factoryReset() {
+        // Turn airplane mode off
+        setAirplaneMode(false);
+
+        // Untether
+        for (String tether : getTetheredIfaces()) {
+            untether(tether);
+        }
+
+        // Turn VPN off
+        try {
+            VpnConfig vpnConfig = mService.getVpnConfig();
+            if (vpnConfig != null) {
+                if (vpnConfig.legacy) {
+                    mService.prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN);
+                } else {
+                    // Prevent this app from initiating VPN connections in the future without
+                    // user intervention.
+                    mService.setVpnPackageAuthorization(false);
+
+                    mService.prepareVpn(vpnConfig.user, VpnConfig.LEGACY_VPN);
+                }
+            }
+        } catch (RemoteException e) {
+            // Well, we tried
+        }
+    }
+
+    /**
      * Binds the current process to {@code network}.  All Sockets created in the future
      * (and not explicitly bound via a bound SocketFactory from
      * {@link Network#getSocketFactory() Network.getSocketFactory()}) will be bound to
diff --git a/core/java/android/net/NetworkPolicyManager.java b/core/java/android/net/NetworkPolicyManager.java
index a8e7757..a7ffee9 100644
--- a/core/java/android/net/NetworkPolicyManager.java
+++ b/core/java/android/net/NetworkPolicyManager.java
@@ -180,6 +180,33 @@
     }
 
     /**
+     * Resets network policy settings back to factory defaults.
+     *
+     * @hide
+     */
+    public void factoryReset(String subscriber) {
+        // Turn mobile data limit off
+        NetworkPolicy[] policies = getNetworkPolicies();
+        NetworkTemplate template = NetworkTemplate.buildTemplateMobileAll(subscriber);
+        for (NetworkPolicy policy : policies) {
+            if (policy.template.equals(template)) {
+                policy.limitBytes = NetworkPolicy.LIMIT_DISABLED;
+                policy.inferred = false;
+                policy.clearSnooze();
+            }
+        }
+        setNetworkPolicies(policies);
+
+        // Turn restrict background data off
+        setRestrictBackground(false);
+
+        // Remove app's "restrict background data" flag
+        for (int uid : getUidsWithPolicy(POLICY_REJECT_METERED_BACKGROUND)) {
+            setUidPolicy(uid, NetworkPolicyManager.POLICY_NONE);
+        }
+    }
+
+    /**
      * Compute the last cycle boundary for the given {@link NetworkPolicy}. For
      * example, if cycle day is 20th, and today is June 15th, it will return May
      * 20th. When cycle day doesn't exist in current month, it snaps to the 1st
diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java
index 3f18519..ff3de2b 100644
--- a/core/java/android/net/TrafficStats.java
+++ b/core/java/android/net/TrafficStats.java
@@ -51,6 +51,8 @@
     public static final long MB_IN_BYTES = KB_IN_BYTES * 1024;
     /** @hide */
     public static final long GB_IN_BYTES = MB_IN_BYTES * 1024;
+    /** @hide */
+    public static final long TB_IN_BYTES = GB_IN_BYTES * 1024;
 
     /**
      * Special UID value used when collecting {@link NetworkStatsHistory} for
diff --git a/core/java/android/os/Environment.java b/core/java/android/os/Environment.java
index 975bfc2..2db976e 100644
--- a/core/java/android/os/Environment.java
+++ b/core/java/android/os/Environment.java
@@ -18,16 +18,11 @@
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
-import android.os.storage.IMountService;
+import android.os.storage.StorageManager;
 import android.os.storage.StorageVolume;
-import android.text.TextUtils;
 import android.util.Log;
 
-import com.google.android.collect.Lists;
-
 import java.io.File;
-import java.io.IOException;
-import java.util.ArrayList;
 
 /**
  * Provides access to environment variables.
@@ -36,11 +31,9 @@
     private static final String TAG = "Environment";
 
     private static final String ENV_EXTERNAL_STORAGE = "EXTERNAL_STORAGE";
-    private static final String ENV_EMULATED_STORAGE_SOURCE = "EMULATED_STORAGE_SOURCE";
-    private static final String ENV_EMULATED_STORAGE_TARGET = "EMULATED_STORAGE_TARGET";
-    private static final String ENV_MEDIA_STORAGE = "MEDIA_STORAGE";
-    private static final String ENV_SECONDARY_STORAGE = "SECONDARY_STORAGE";
     private static final String ENV_ANDROID_ROOT = "ANDROID_ROOT";
+    private static final String ENV_ANDROID_DATA = "ANDROID_DATA";
+    private static final String ENV_ANDROID_STORAGE = "ANDROID_STORAGE";
     private static final String ENV_OEM_ROOT = "OEM_ROOT";
     private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";
 
@@ -57,12 +50,10 @@
     public static final String DIRECTORY_ANDROID = DIR_ANDROID;
 
     private static final File DIR_ANDROID_ROOT = getDirectory(ENV_ANDROID_ROOT, "/system");
+    private static final File DIR_ANDROID_DATA = getDirectory(ENV_ANDROID_DATA, "/data");
+    private static final File DIR_ANDROID_STORAGE = getDirectory(ENV_ANDROID_STORAGE, "/storage");
     private static final File DIR_OEM_ROOT = getDirectory(ENV_OEM_ROOT, "/oem");
     private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor");
-    private static final File DIR_MEDIA_STORAGE = getDirectory(ENV_MEDIA_STORAGE, "/data/media");
-
-    private static final String CANONCIAL_EMULATED_STORAGE_TARGET = getCanonicalPathOrNull(
-            ENV_EMULATED_STORAGE_TARGET);
 
     private static final String SYSTEM_PROPERTY_EFS_ENABLED = "persist.security.efs.enabled";
 
@@ -81,73 +72,24 @@
 
     /** {@hide} */
     public static class UserEnvironment {
-        // TODO: generalize further to create package-specific environment
-
-        /** External storage dirs, as visible to vold */
-        private final File[] mExternalDirsForVold;
-        /** External storage dirs, as visible to apps */
-        private final File[] mExternalDirsForApp;
-        /** Primary emulated storage dir for direct access */
-        private final File mEmulatedDirForDirect;
+        private final int mUserId;
 
         public UserEnvironment(int userId) {
-            // See storage config details at http://source.android.com/tech/storage/
-            String rawExternalStorage = System.getenv(ENV_EXTERNAL_STORAGE);
-            String rawEmulatedSource = System.getenv(ENV_EMULATED_STORAGE_SOURCE);
-            String rawEmulatedTarget = System.getenv(ENV_EMULATED_STORAGE_TARGET);
+            mUserId = userId;
+        }
 
-            String rawMediaStorage = System.getenv(ENV_MEDIA_STORAGE);
-            if (TextUtils.isEmpty(rawMediaStorage)) {
-                rawMediaStorage = "/data/media";
+        public File[] getExternalDirs() {
+            final StorageVolume[] volumes = StorageManager.getVolumeList(mUserId);
+            final File[] dirs = new File[volumes.length];
+            for (int i = 0; i < volumes.length; i++) {
+                dirs[i] = volumes[i].getPathFile();
             }
-
-            ArrayList<File> externalForVold = Lists.newArrayList();
-            ArrayList<File> externalForApp = Lists.newArrayList();
-
-            if (!TextUtils.isEmpty(rawEmulatedTarget)) {
-                // Device has emulated storage; external storage paths should have
-                // userId burned into them.
-                final String rawUserId = Integer.toString(userId);
-                final File emulatedSourceBase = new File(rawEmulatedSource);
-                final File emulatedTargetBase = new File(rawEmulatedTarget);
-                final File mediaBase = new File(rawMediaStorage);
-
-                // /storage/emulated/0
-                externalForVold.add(buildPath(emulatedSourceBase, rawUserId));
-                externalForApp.add(buildPath(emulatedTargetBase, rawUserId));
-                // /data/media/0
-                mEmulatedDirForDirect = buildPath(mediaBase, rawUserId);
-
-            } 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
-                externalForVold.add(new File(rawExternalStorage));
-                externalForApp.add(new File(rawExternalStorage));
-                // /data/media
-                mEmulatedDirForDirect = new File(rawMediaStorage);
-            }
-
-            // Splice in any secondary storage paths, but only for owner
-            final String rawSecondaryStorage = System.getenv(ENV_SECONDARY_STORAGE);
-            if (!TextUtils.isEmpty(rawSecondaryStorage) && userId == UserHandle.USER_OWNER) {
-                for (String secondaryPath : rawSecondaryStorage.split(":")) {
-                    externalForVold.add(new File(secondaryPath));
-                    externalForApp.add(new File(secondaryPath));
-                }
-            }
-
-            mExternalDirsForVold = externalForVold.toArray(new File[externalForVold.size()]);
-            mExternalDirsForApp = externalForApp.toArray(new File[externalForApp.size()]);
+            return dirs;
         }
 
         @Deprecated
         public File getExternalStorageDirectory() {
-            return mExternalDirsForApp[0];
+            return getExternalDirs()[0];
         }
 
         @Deprecated
@@ -155,60 +97,36 @@
             return buildExternalStoragePublicDirs(type)[0];
         }
 
-        public File[] getExternalDirsForVold() {
-            return mExternalDirsForVold;
-        }
-
-        public File[] getExternalDirsForApp() {
-            return mExternalDirsForApp;
-        }
-
-        public File getMediaDir() {
-            return mEmulatedDirForDirect;
-        }
-
         public File[] buildExternalStoragePublicDirs(String type) {
-            return buildPaths(mExternalDirsForApp, type);
+            return buildPaths(getExternalDirs(), type);
         }
 
         public File[] buildExternalStorageAndroidDataDirs() {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA);
         }
 
         public File[] buildExternalStorageAndroidObbDirs() {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB);
         }
 
         public File[] buildExternalStorageAppDataDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName);
-        }
-
-        public File[] buildExternalStorageAppDataDirsForVold(String packageName) {
-            return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_DATA, packageName);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName);
         }
 
         public File[] buildExternalStorageAppMediaDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_MEDIA, packageName);
-        }
-
-        public File[] buildExternalStorageAppMediaDirsForVold(String packageName) {
-            return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_MEDIA, packageName);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_MEDIA, packageName);
         }
 
         public File[] buildExternalStorageAppObbDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_OBB, packageName);
-        }
-
-        public File[] buildExternalStorageAppObbDirsForVold(String packageName) {
-            return buildPaths(mExternalDirsForVold, DIR_ANDROID, DIR_OBB, packageName);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_OBB, packageName);
         }
 
         public File[] buildExternalStorageAppFilesDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_FILES);
         }
 
         public File[] buildExternalStorageAppCacheDirs(String packageName) {
-            return buildPaths(mExternalDirsForApp, DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
+            return buildPaths(getExternalDirs(), DIR_ANDROID, DIR_DATA, packageName, DIR_CACHE);
         }
     }
 
@@ -220,6 +138,11 @@
         return DIR_ANDROID_ROOT;
     }
 
+    /** {@hide} */
+    public static File getStorageDirectory() {
+        return DIR_ANDROID_STORAGE;
+    }
+
     /**
      * Return root directory of the "oem" partition holding OEM customizations,
      * if any. If present, the partition is mounted read-only.
@@ -270,17 +193,6 @@
     }
 
     /**
-     * Return directory used for internal media storage, which is protected by
-     * {@link android.Manifest.permission#WRITE_MEDIA_STORAGE}.
-     *
-     * @hide
-     */
-    public static File getMediaStorageDirectory() {
-        throwIfUserRequired();
-        return sCurrentUser.getMediaDir();
-    }
-
-    /**
      * Return the system directory for a user. This is for use by system services to store
      * files relating to the user. This directory will be automatically deleted when the user
      * is removed.
@@ -389,7 +301,7 @@
      */
     public static File getExternalStorageDirectory() {
         throwIfUserRequired();
-        return sCurrentUser.getExternalDirsForApp()[0];
+        return sCurrentUser.getExternalDirs()[0];
     }
 
     /** {@hide} */
@@ -402,18 +314,6 @@
         return buildPath(getLegacyExternalStorageDirectory(), DIR_ANDROID, DIR_OBB);
     }
 
-    /** {@hide} */
-    public static File getEmulatedStorageSource(int userId) {
-        // /mnt/shell/emulated/0
-        return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), String.valueOf(userId));
-    }
-
-    /** {@hide} */
-    public static File getEmulatedStorageObbSource() {
-        // /mnt/shell/emulated/obb
-        return new File(System.getenv(ENV_EMULATED_STORAGE_SOURCE), DIR_OBB);
-    }
-
     /**
      * Standard directory in which to place any audio files that should be
      * in the regular list of music for the user.
@@ -683,6 +583,13 @@
     public static final String MEDIA_UNMOUNTABLE = "unmountable";
 
     /**
+     * Storage state if the media is in the process of being ejected.
+     *
+     * @see #getExternalStorageState(File)
+     */
+    public static final String MEDIA_EJECTING = "ejecting";
+
+    /**
      * Returns the current state of the primary "external" storage device.
      * 
      * @see #getExternalStorageDirectory()
@@ -693,7 +600,7 @@
      *         {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
      */
     public static String getExternalStorageState() {
-        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
         return getExternalStorageState(externalDir);
     }
 
@@ -716,17 +623,12 @@
      *         {@link #MEDIA_BAD_REMOVAL}, or {@link #MEDIA_UNMOUNTABLE}.
      */
     public static String getExternalStorageState(File path) {
-        final StorageVolume volume = getStorageVolume(path);
+        final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
         if (volume != null) {
-            final IMountService mountService = IMountService.Stub.asInterface(
-                    ServiceManager.getService("mount"));
-            try {
-                return mountService.getVolumeState(volume.getPath());
-            } catch (RemoteException e) {
-            }
+            return volume.getState();
+        } else {
+            return MEDIA_UNKNOWN;
         }
-
-        return Environment.MEDIA_UNKNOWN;
     }
 
     /**
@@ -738,7 +640,7 @@
      */
     public static boolean isExternalStorageRemovable() {
         if (isStorageDisabled()) return false;
-        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
         return isExternalStorageRemovable(externalDir);
     }
 
@@ -753,7 +655,7 @@
      *             device.
      */
     public static boolean isExternalStorageRemovable(File path) {
-        final StorageVolume volume = getStorageVolume(path);
+        final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
         if (volume != null) {
             return volume.isRemovable();
         } else {
@@ -771,7 +673,7 @@
      */
     public static boolean isExternalStorageEmulated() {
         if (isStorageDisabled()) return false;
-        final File externalDir = sCurrentUser.getExternalDirsForApp()[0];
+        final File externalDir = sCurrentUser.getExternalDirs()[0];
         return isExternalStorageEmulated(externalDir);
     }
 
@@ -784,7 +686,7 @@
      *             device.
      */
     public static boolean isExternalStorageEmulated(File path) {
-        final StorageVolume volume = getStorageVolume(path);
+        final StorageVolume volume = StorageManager.getStorageVolume(path, UserHandle.myUserId());
         if (volume != null) {
             return volume.isEmulated();
         } else {
@@ -797,19 +699,6 @@
         return path == null ? new File(defaultPath) : new File(path);
     }
 
-    private static String getCanonicalPathOrNull(String variableName) {
-        String path = System.getenv(variableName);
-        if (path == null) {
-            return null;
-        }
-        try {
-            return new File(path).getCanonicalPath();
-        } catch (IOException e) {
-            Log.w(TAG, "Unable to resolve canonical path for " + path);
-            return null;
-        }
-    }
-
     /** {@hide} */
     public static void setUserRequired(boolean userRequired) {
         sUserRequired = userRequired;
@@ -856,28 +745,6 @@
         return SystemProperties.getBoolean("config.disable_storage", false);
     }
 
-    private static StorageVolume getStorageVolume(File path) {
-        try {
-            path = path.getCanonicalFile();
-        } catch (IOException e) {
-            return null;
-        }
-
-        try {
-            final IMountService mountService = IMountService.Stub.asInterface(
-                    ServiceManager.getService("mount"));
-            final StorageVolume[] volumes = mountService.getVolumeList();
-            for (StorageVolume volume : volumes) {
-                if (FileUtils.contains(volume.getPathFile(), path)) {
-                    return volume;
-                }
-            }
-        } catch (RemoteException e) {
-        }
-
-        return null;
-    }
-
     /**
      * If the given path exists on emulated external storage, return the
      * translated backing path hosted on internal storage. This bypasses any
@@ -891,26 +758,7 @@
      * @hide
      */
     public static File maybeTranslateEmulatedPathToInternal(File path) {
-        // Fast return if not emulated, or missing variables
-        if (!Environment.isExternalStorageEmulated()
-                || CANONCIAL_EMULATED_STORAGE_TARGET == null) {
-            return path;
-        }
-
-        try {
-            final String rawPath = path.getCanonicalPath();
-            if (rawPath.startsWith(CANONCIAL_EMULATED_STORAGE_TARGET)) {
-                final File internalPath = new File(DIR_MEDIA_STORAGE,
-                        rawPath.substring(CANONCIAL_EMULATED_STORAGE_TARGET.length()));
-                if (internalPath.exists()) {
-                    return internalPath;
-                }
-            }
-        } catch (IOException e) {
-            Log.w(TAG, "Failed to resolve canonical path for " + path);
-        }
-
-        // Unable to translate to internal path; use original
+        // TODO: bring back this optimization
         return path;
     }
 }
diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java
index 0a724a1..b302f95 100644
--- a/core/java/android/os/FileUtils.java
+++ b/core/java/android/os/FileUtils.java
@@ -369,6 +369,23 @@
      * {@link File#getCanonicalFile()} to avoid symlink or path traversal
      * attacks.
      */
+    public static boolean contains(File[] dirs, File file) {
+        for (File dir : dirs) {
+            if (contains(dir, file)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Test if a file lives under the given directory, either as a direct child
+     * or a distant grandchild.
+     * <p>
+     * Both files <em>must</em> have been resolved using
+     * {@link File#getCanonicalFile()} to avoid symlink or path traversal
+     * attacks.
+     */
     public static boolean contains(File dir, File file) {
         if (file == null) return false;
 
diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java
index 0de9c70..355ec8c 100644
--- a/core/java/android/os/Process.java
+++ b/core/java/android/os/Process.java
@@ -637,10 +637,8 @@
             if ((debugFlags & Zygote.DEBUG_ENABLE_ASSERT) != 0) {
                 argsForZygote.add("--enable-assert");
             }
-            if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER) {
-                argsForZygote.add("--mount-external-multiuser");
-            } else if (mountExternal == Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL) {
-                argsForZygote.add("--mount-external-multiuser-all");
+            if (mountExternal == Zygote.MOUNT_EXTERNAL_DEFAULT) {
+                argsForZygote.add("--mount-external-default");
             }
             argsForZygote.add("--target-sdk-version=" + targetSdkVersion);
 
diff --git a/core/java/android/os/storage/IMountService.java b/core/java/android/os/storage/IMountService.java
index 6209c2a..fef12d1 100644
--- a/core/java/android/os/storage/IMountService.java
+++ b/core/java/android/os/storage/IMountService.java
@@ -757,12 +757,13 @@
                 return _result;
             }
 
-            public StorageVolume[] getVolumeList() throws RemoteException {
+            public StorageVolume[] getVolumeList(int userId) throws RemoteException {
                 Parcel _data = Parcel.obtain();
                 Parcel _reply = Parcel.obtain();
                 StorageVolume[] _result;
                 try {
                     _data.writeInterfaceToken(DESCRIPTOR);
+                    _data.writeInt(userId);
                     mRemote.transact(Stub.TRANSACTION_getVolumeList, _data, _reply, 0);
                     _reply.readException();
                     _result = _reply.createTypedArray(StorageVolume.CREATOR);
@@ -1308,7 +1309,8 @@
                 }
                 case TRANSACTION_getVolumeList: {
                     data.enforceInterface(DESCRIPTOR);
-                    StorageVolume[] result = getVolumeList();
+                    int userId = data.readInt();
+                    StorageVolume[] result = getVolumeList(userId);
                     reply.writeNoException();
                     reply.writeTypedArray(result, android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
                     return true;
@@ -1630,7 +1632,7 @@
     /**
      * Returns list of all mountable volumes.
      */
-    public StorageVolume[] getVolumeList() throws RemoteException;
+    public StorageVolume[] getVolumeList(int userId) 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 2785ee8..532bf2c 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -18,19 +18,23 @@
 
 import static android.net.TrafficStats.MB_IN_BYTES;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.os.Environment;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
-import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.provider.Settings;
 import android.util.Log;
 import android.util.SparseArray;
 
+import libcore.util.EmptyArray;
+
 import com.android.internal.util.Preconditions;
 
 import java.io.File;
@@ -60,6 +64,7 @@
 public class StorageManager {
     private static final String TAG = "StorageManager";
 
+    private final Context mContext;
     private final ContentResolver mResolver;
 
     /*
@@ -311,8 +316,9 @@
      *
      * @hide
      */
-    public StorageManager(ContentResolver resolver, Looper tgtLooper) throws RemoteException {
-        mResolver = resolver;
+    public StorageManager(Context context, Looper tgtLooper) {
+        mContext = context;
+        mResolver = context.getContentResolver();
         mTgtLooper = tgtLooper;
         mMountService = IMountService.Stub.asInterface(ServiceManager.getService("mount"));
         if (mMountService == null) {
@@ -548,17 +554,46 @@
         return null;
     }
 
+    /** {@hide} */
+    public @Nullable StorageVolume getStorageVolume(File file) {
+        return getStorageVolume(getVolumeList(), file);
+    }
+
+    /** {@hide} */
+    public static @Nullable StorageVolume getStorageVolume(File file, int userId) {
+        return getStorageVolume(getVolumeList(userId), file);
+    }
+
+    /** {@hide} */
+    private static @Nullable StorageVolume getStorageVolume(StorageVolume[] volumes, File file) {
+        File canonicalFile = null;
+        try {
+            canonicalFile = file.getCanonicalFile();
+        } catch (IOException ignored) {
+            canonicalFile = null;
+        }
+        for (StorageVolume volume : volumes) {
+            if (volume.getPathFile().equals(file)) {
+                return volume;
+            }
+            if (FileUtils.contains(volume.getPathFile(), canonicalFile)) {
+                return volume;
+            }
+        }
+        return null;
+    }
+
     /**
      * Gets the state of a volume via its mountpoint.
      * @hide
      */
-    public String getVolumeState(String mountPoint) {
-         if (mMountService == null) return Environment.MEDIA_REMOVED;
-        try {
-            return mMountService.getVolumeState(mountPoint);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get volume state", e);
-            return null;
+    @Deprecated
+    public @NonNull String getVolumeState(String mountPoint) {
+        final StorageVolume vol = getStorageVolume(new File(mountPoint));
+        if (vol != null) {
+            return vol.getState();
+        } else {
+            return Environment.MEDIA_UNKNOWN;
         }
     }
 
@@ -566,20 +601,22 @@
      * Returns list of all mountable volumes.
      * @hide
      */
-    public StorageVolume[] getVolumeList() {
-        if (mMountService == null) return new StorageVolume[0];
+    public @NonNull StorageVolume[] getVolumeList() {
         try {
-            Parcelable[] list = mMountService.getVolumeList();
-            if (list == null) return new StorageVolume[0];
-            int length = list.length;
-            StorageVolume[] result = new StorageVolume[length];
-            for (int i = 0; i < length; i++) {
-                result[i] = (StorageVolume)list[i];
-            }
-            return result;
+            return mMountService.getVolumeList(mContext.getUserId());
         } catch (RemoteException e) {
-            Log.e(TAG, "Failed to get volume list", e);
-            return null;
+            throw e.rethrowAsRuntimeException();
+        }
+    }
+
+    /** {@hide} */
+    public static @NonNull StorageVolume[] getVolumeList(int userId) {
+        final IMountService mountService = IMountService.Stub.asInterface(
+                ServiceManager.getService("mount"));
+        try {
+            return mountService.getVolumeList(userId);
+        } catch (RemoteException e) {
+            throw e.rethrowAsRuntimeException();
         }
     }
 
@@ -587,9 +624,9 @@
      * Returns list of paths for all mountable volumes.
      * @hide
      */
-    public String[] getVolumePaths() {
+    @Deprecated
+    public @NonNull String[] getVolumePaths() {
         StorageVolume[] volumes = getVolumeList();
-        if (volumes == null) return null;
         int count = volumes.length;
         String[] paths = new String[count];
         for (int i = 0; i < count; i++) {
@@ -599,21 +636,21 @@
     }
 
     /** {@hide} */
-    public StorageVolume getPrimaryVolume() {
+    public @NonNull StorageVolume getPrimaryVolume() {
         return getPrimaryVolume(getVolumeList());
     }
 
     /** {@hide} */
-    public static StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
+    public static @NonNull StorageVolume getPrimaryVolume(StorageVolume[] volumes) {
         for (StorageVolume volume : volumes) {
             if (volume.isPrimary()) {
                 return volume;
             }
         }
-        Log.w(TAG, "No primary storage defined");
-        return null;
+        throw new IllegalStateException("Missing primary storage");
     }
 
+    /** {@hide} */
     private static final int DEFAULT_THRESHOLD_PERCENTAGE = 10;
     private static final long DEFAULT_THRESHOLD_MAX_BYTES = 500 * MB_IN_BYTES;
     private static final long DEFAULT_FULL_THRESHOLD_BYTES = MB_IN_BYTES;
diff --git a/core/java/android/os/storage/StorageVolume.java b/core/java/android/os/storage/StorageVolume.java
index 06565f1..0c391ca 100644
--- a/core/java/android/os/storage/StorageVolume.java
+++ b/core/java/android/os/storage/StorageVolume.java
@@ -17,6 +17,7 @@
 package android.os.storage;
 
 import android.content.Context;
+import android.net.TrafficStats;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
@@ -34,52 +35,58 @@
  */
 public class StorageVolume implements Parcelable {
 
-    // TODO: switch to more durable token
-    private int mStorageId;
+    private final String mId;
+    private final int mStorageId;
 
     private final File mPath;
     private final int mDescriptionId;
     private final boolean mPrimary;
     private final boolean mRemovable;
     private final boolean mEmulated;
-    private final int mMtpReserveSpace;
+    private final long mMtpReserveSize;
     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;
 
-    private String mUuid;
-    private String mUserLabel;
-    private String mState;
+    private final String mUuid;
+    private final String mUserLabel;
+    private final String mState;
 
     // 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(File path, int descriptionId, boolean primary, boolean removable,
-            boolean emulated, int mtpReserveSpace, boolean allowMassStorage, long maxFileSize,
-            UserHandle owner) {
+    public StorageVolume(String id, int storageId, File path, int descriptionId, boolean primary,
+            boolean removable, boolean emulated, long mtpReserveSize, boolean allowMassStorage,
+            long maxFileSize, UserHandle owner, String uuid, String userLabel, String state) {
+        mId = id;
+        mStorageId = storageId;
         mPath = path;
         mDescriptionId = descriptionId;
         mPrimary = primary;
         mRemovable = removable;
         mEmulated = emulated;
-        mMtpReserveSpace = mtpReserveSpace;
+        mMtpReserveSize = mtpReserveSize;
         mAllowMassStorage = allowMassStorage;
         mMaxFileSize = maxFileSize;
         mOwner = owner;
+        mUuid = uuid;
+        mUserLabel = userLabel;
+        mState = state;
     }
 
     private StorageVolume(Parcel in) {
+        mId = in.readString();
         mStorageId = in.readInt();
         mPath = new File(in.readString());
         mDescriptionId = in.readInt();
         mPrimary = in.readInt() != 0;
         mRemovable = in.readInt() != 0;
         mEmulated = in.readInt() != 0;
-        mMtpReserveSpace = in.readInt();
+        mMtpReserveSize = in.readLong();
         mAllowMassStorage = in.readInt() != 0;
         mMaxFileSize = in.readLong();
         mOwner = in.readParcelable(null);
@@ -88,10 +95,8 @@
         mState = in.readString();
     }
 
-    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);
+    public String getId() {
+        return mId;
     }
 
     /**
@@ -153,15 +158,6 @@
     }
 
     /**
-     * Do not call this unless you are MountService
-     */
-    public void setStorageId(int index) {
-        // storage ID is 0x00010001 for primary storage,
-        // then 0x00020001, 0x00030001, etc. for secondary storages
-        mStorageId = ((index + 1) << 16) + 1;
-    }
-
-    /**
      * Number of megabytes of space to leave unallocated by MTP.
      * MTP will subtract this value from the free space it reports back
      * to the host via GetStorageInfo, and will not allow new files to
@@ -174,7 +170,7 @@
      * @return MTP reserve space
      */
     public int getMtpReserveSpace() {
-        return mMtpReserveSpace;
+        return (int) (mMtpReserveSize / TrafficStats.MB_IN_BYTES);
     }
 
     /**
@@ -199,10 +195,6 @@
         return mOwner;
     }
 
-    public void setUuid(String uuid) {
-        mUuid = uuid;
-    }
-
     public String getUuid() {
         return mUuid;
     }
@@ -222,18 +214,10 @@
         }
     }
 
-    public void setUserLabel(String userLabel) {
-        mUserLabel = userLabel;
-    }
-
     public String getUserLabel() {
         return mUserLabel;
     }
 
-    public void setState(String state) {
-        mState = state;
-    }
-
     public String getState() {
         return mState;
     }
@@ -262,13 +246,14 @@
     public void dump(IndentingPrintWriter pw) {
         pw.println("StorageVolume:");
         pw.increaseIndent();
+        pw.printPair("mId", mId);
         pw.printPair("mStorageId", mStorageId);
         pw.printPair("mPath", mPath);
         pw.printPair("mDescriptionId", mDescriptionId);
         pw.printPair("mPrimary", mPrimary);
         pw.printPair("mRemovable", mRemovable);
         pw.printPair("mEmulated", mEmulated);
-        pw.printPair("mMtpReserveSpace", mMtpReserveSpace);
+        pw.printPair("mMtpReserveSize", mMtpReserveSize);
         pw.printPair("mAllowMassStorage", mAllowMassStorage);
         pw.printPair("mMaxFileSize", mMaxFileSize);
         pw.printPair("mOwner", mOwner);
@@ -297,13 +282,14 @@
 
     @Override
     public void writeToParcel(Parcel parcel, int flags) {
+        parcel.writeString(mId);
         parcel.writeInt(mStorageId);
         parcel.writeString(mPath.toString());
         parcel.writeInt(mDescriptionId);
         parcel.writeInt(mPrimary ? 1 : 0);
         parcel.writeInt(mRemovable ? 1 : 0);
         parcel.writeInt(mEmulated ? 1 : 0);
-        parcel.writeInt(mMtpReserveSpace);
+        parcel.writeLong(mMtpReserveSize);
         parcel.writeInt(mAllowMassStorage ? 1 : 0);
         parcel.writeLong(mMaxFileSize);
         parcel.writeParcelable(mOwner, flags);
diff --git a/core/java/android/security/keymaster/OperationResult.java b/core/java/android/security/keymaster/OperationResult.java
index 4fc9d24..9b46ad3 100644
--- a/core/java/android/security/keymaster/OperationResult.java
+++ b/core/java/android/security/keymaster/OperationResult.java
@@ -28,6 +28,7 @@
 public class OperationResult implements Parcelable {
     public final int resultCode;
     public final IBinder token;
+    public final long operationHandle;
     public final int inputConsumed;
     public final byte[] output;
 
@@ -45,6 +46,7 @@
     protected OperationResult(Parcel in) {
         resultCode = in.readInt();
         token = in.readStrongBinder();
+        operationHandle = in.readLong();
         inputConsumed = in.readInt();
         output = in.createByteArray();
     }
@@ -58,6 +60,7 @@
     public void writeToParcel(Parcel out, int flags) {
         out.writeInt(resultCode);
         out.writeStrongBinder(token);
+        out.writeLong(operationHandle);
         out.writeInt(inputConsumed);
         out.writeByteArray(output);
     }
diff --git a/core/java/android/util/DebugUtils.java b/core/java/android/util/DebugUtils.java
index 84d9ce8..c44f42b 100644
--- a/core/java/android/util/DebugUtils.java
+++ b/core/java/android/util/DebugUtils.java
@@ -17,8 +17,10 @@
 package android.util;
 
 import java.io.PrintWriter;
-import java.lang.reflect.Method;
+import java.lang.reflect.Field;
 import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.lang.reflect.Modifier;
 import java.util.Locale;
 
 /**
@@ -203,4 +205,57 @@
         outBuilder.append(suffix);
         return outBuilder.toString();
     }
+
+    /**
+     * Use prefixed constants (static final values) on given class to turn value
+     * into human-readable string.
+     *
+     * @hide
+     */
+    public static String valueToString(Class<?> clazz, String prefix, int value) {
+        for (Field field : clazz.getDeclaredFields()) {
+            final int modifiers = field.getModifiers();
+            if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+                    && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+                try {
+                    if (value == field.getInt(null)) {
+                        return field.getName().substring(prefix.length());
+                    }
+                } catch (IllegalAccessException ignored) {
+                }
+            }
+        }
+        return Integer.toString(value);
+    }
+
+    /**
+     * Use prefixed constants (static final values) on given class to turn flags
+     * into human-readable string.
+     *
+     * @hide
+     */
+    public static String flagsToString(Class<?> clazz, String prefix, int flags) {
+        final StringBuilder res = new StringBuilder();
+
+        for (Field field : clazz.getDeclaredFields()) {
+            final int modifiers = field.getModifiers();
+            if (Modifier.isStatic(modifiers) && Modifier.isFinal(modifiers)
+                    && field.getType().equals(int.class) && field.getName().startsWith(prefix)) {
+                try {
+                    final int value = field.getInt(null);
+                    if ((flags & value) != 0) {
+                        flags &= ~value;
+                        res.append(field.getName().substring(prefix.length())).append('|');
+                    }
+                } catch (IllegalAccessException ignored) {
+                }
+            }
+        }
+        if (flags != 0 || res.length() == 0) {
+            res.append(Integer.toHexString(flags));
+        } else {
+            res.deleteCharAt(res.length() - 1);
+        }
+        return res.toString();
+    }
 }
diff --git a/core/java/android/view/ScaleGestureDetector.java b/core/java/android/view/ScaleGestureDetector.java
index 6508cca..5cf2c5c 100644
--- a/core/java/android/view/ScaleGestureDetector.java
+++ b/core/java/android/view/ScaleGestureDetector.java
@@ -130,6 +130,7 @@
     private float mFocusY;
 
     private boolean mQuickScaleEnabled;
+    private boolean mButtonScaleEnabled;
 
     private float mCurrSpan;
     private float mPrevSpan;
@@ -151,14 +152,17 @@
     private int mTouchHistoryDirection;
     private long mTouchHistoryLastAcceptedTime;
     private int mTouchMinMajor;
-    private MotionEvent mDoubleTapEvent;
-    private int mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
     private final Handler mHandler;
 
+    private float mAnchoredScaleStartX;
+    private float mAnchoredScaleStartY;
+    private int mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+
     private static final long TOUCH_STABILIZE_TIME = 128; // ms
-    private static final int DOUBLE_TAP_MODE_NONE = 0;
-    private static final int DOUBLE_TAP_MODE_IN_PROGRESS = 1;
     private static final float SCALE_FACTOR = .5f;
+    private static final int ANCHORED_SCALE_MODE_NONE = 0;
+    private static final int ANCHORED_SCALE_MODE_DOUBLE_TAP = 1;
+    private static final int ANCHORED_SCALE_MODE_BUTTON = 2;
 
 
     /**
@@ -310,8 +314,17 @@
             mGestureDetector.onTouchEvent(event);
         }
 
+        final int count = event.getPointerCount();
+        final int toolType = event.getToolType(0);
+        final boolean isButtonTool = toolType == MotionEvent.TOOL_TYPE_STYLUS
+                || toolType == MotionEvent.TOOL_TYPE_MOUSE;
+        final boolean isAnchoredScaleButtonDown = isButtonTool && (count == 1)
+                && (event.getButtonState() & MotionEvent.BUTTON_SECONDARY) != 0;
+
+        final boolean anchoredScaleCancelled =
+                mAnchoredScaleMode == ANCHORED_SCALE_MODE_BUTTON && !isAnchoredScaleButtonDown;
         final boolean streamComplete = action == MotionEvent.ACTION_UP ||
-                action == MotionEvent.ACTION_CANCEL;
+                action == MotionEvent.ACTION_CANCEL || anchoredScaleCancelled;
 
         if (action == MotionEvent.ACTION_DOWN || streamComplete) {
             // Reset any scale in progress with the listener.
@@ -321,11 +334,11 @@
                 mListener.onScaleEnd(this);
                 mInProgress = false;
                 mInitialSpan = 0;
-                mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
-            } else if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS && streamComplete) {
+                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
+            } else if (inAnchoredScaleMode() && streamComplete) {
                 mInProgress = false;
                 mInitialSpan = 0;
-                mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
+                mAnchoredScaleMode = ANCHORED_SCALE_MODE_NONE;
             }
 
             if (streamComplete) {
@@ -334,25 +347,32 @@
             }
         }
 
+        if (!mInProgress && mButtonScaleEnabled && !inAnchoredScaleMode()
+                && !streamComplete && isAnchoredScaleButtonDown) {
+            // Start of a button scale gesture
+            mAnchoredScaleStartX = event.getX();
+            mAnchoredScaleStartY = event.getY();
+            mAnchoredScaleMode = ANCHORED_SCALE_MODE_BUTTON;
+            mInitialSpan = 0;
+        }
+
         final boolean configChanged = action == MotionEvent.ACTION_DOWN ||
                 action == MotionEvent.ACTION_POINTER_UP ||
-                action == MotionEvent.ACTION_POINTER_DOWN;
-
+                action == MotionEvent.ACTION_POINTER_DOWN || anchoredScaleCancelled;
 
         final boolean pointerUp = action == MotionEvent.ACTION_POINTER_UP;
         final int skipIndex = pointerUp ? event.getActionIndex() : -1;
 
         // Determine focal point
         float sumX = 0, sumY = 0;
-        final int count = event.getPointerCount();
         final int div = pointerUp ? count - 1 : count;
         final float focusX;
         final float focusY;
-        if (mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS) {
-            // In double tap mode, the focal pt is always where the double tap
-            // gesture started
-            focusX = mDoubleTapEvent.getX();
-            focusY = mDoubleTapEvent.getY();
+        if (inAnchoredScaleMode()) {
+            // In anchored scale mode, the focal pt is always where the double tap
+            // or button down gesture started
+            focusX = mAnchoredScaleStartX;
+            focusY = mAnchoredScaleStartY;
             if (event.getY() < focusY) {
                 mEventBeforeOrAboveStartingGestureEvent = true;
             } else {
@@ -390,7 +410,7 @@
         final float spanX = devX * 2;
         final float spanY = devY * 2;
         final float span;
-        if (inDoubleTapMode()) {
+        if (inAnchoredScaleMode()) {
             span = spanY;
         } else {
             span = (float) Math.hypot(spanX, spanY);
@@ -402,11 +422,10 @@
         final boolean wasInProgress = mInProgress;
         mFocusX = focusX;
         mFocusY = focusY;
-        if (!inDoubleTapMode() && mInProgress && (span < mMinSpan || configChanged)) {
+        if (!inAnchoredScaleMode() && mInProgress && (span < mMinSpan || configChanged)) {
             mListener.onScaleEnd(this);
             mInProgress = false;
             mInitialSpan = span;
-            mDoubleTapMode = DOUBLE_TAP_MODE_NONE;
         }
         if (configChanged) {
             mPrevSpanX = mCurrSpanX = spanX;
@@ -414,7 +433,7 @@
             mInitialSpan = mPrevSpan = mCurrSpan = span;
         }
 
-        final int minSpan = inDoubleTapMode() ? mSpanSlop : mMinSpan;
+        final int minSpan = inAnchoredScaleMode() ? mSpanSlop : mMinSpan;
         if (!mInProgress && span >=  minSpan &&
                 (wasInProgress || Math.abs(span - mInitialSpan) > mSpanSlop)) {
             mPrevSpanX = mCurrSpanX = spanX;
@@ -447,9 +466,8 @@
         return true;
     }
 
-
-    private boolean inDoubleTapMode() {
-        return mDoubleTapMode == DOUBLE_TAP_MODE_IN_PROGRESS;
+    private boolean inAnchoredScaleMode() {
+        return mAnchoredScaleMode != ANCHORED_SCALE_MODE_NONE;
     }
 
     /**
@@ -466,8 +484,9 @@
                         @Override
                         public boolean onDoubleTap(MotionEvent e) {
                             // Double tap: start watching for a swipe
-                            mDoubleTapEvent = e;
-                            mDoubleTapMode = DOUBLE_TAP_MODE_IN_PROGRESS;
+                            mAnchoredScaleStartX = e.getX();
+                            mAnchoredScaleStartY = e.getY();
+                            mAnchoredScaleMode = ANCHORED_SCALE_MODE_DOUBLE_TAP;
                             return true;
                         }
                     };
@@ -484,6 +503,27 @@
     }
 
     /**
+     * Sets whether the associates {@link OnScaleGestureListener} should receive onScale callbacks
+     * when the user presses a {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus
+     * first button) and drags the pointer on the screen. Note that this is enabled by default if
+     * the app targets API 23 and newer.
+     *
+     * @param scales true to enable stylus or mouse scaling, false to disable.
+     */
+    public void setSecondaryButtonScaleEnabled(boolean scales) {
+        mButtonScaleEnabled = scales;
+    }
+
+    /**
+     * Return whether the button scale gesture, in which the user presses a
+     * {@value MotionEvent#BUTTON_SECONDARY} (right mouse button, stylus first button) and drags the
+     * pointer on the screen, should perform scaling. {@see #setButtonScaleEnabled(boolean)}.
+     */
+    public boolean isSecondaryButtonScaleEnabled() {
+        return mButtonScaleEnabled;
+    }
+
+    /**
      * Returns {@code true} if a scale gesture is in progress.
      */
     public boolean isInProgress() {
@@ -586,7 +626,7 @@
      * @return The current scaling factor.
      */
     public float getScaleFactor() {
-        if (inDoubleTapMode()) {
+        if (inAnchoredScaleMode()) {
             // Drag is moving up; the further away from the gesture
             // start, the smaller the span should be, the closer,
             // the larger the span, and therefore the larger the scale
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index f9729a2..32b99a8 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -2939,7 +2939,27 @@
      * The default callback provides a subset of Select All, Cut, Copy and Paste actions, depending
      * on which of these this TextView supports.
      */
-    private class SelectionActionModeCallback implements ActionMode.Callback {
+    private class SelectionActionModeCallback extends ActionMode.Callback2 {
+        private final Path mSelectionPath = new Path();
+        private final RectF mSelectionBounds = new RectF();
+
+        private int mSelectionHandleHeight;
+        private int mInsertionHandleHeight;
+
+        public SelectionActionModeCallback() {
+            SelectionModifierCursorController selectionController = getSelectionController();
+            if (selectionController.mStartHandle == null) {
+                selectionController.initDrawables();
+                selectionController.initHandles();
+            }
+            mSelectionHandleHeight = Math.max(
+                    mSelectHandleLeft.getMinimumHeight(), mSelectHandleRight.getMinimumHeight());
+            InsertionPointCursorController insertionController = getInsertionController();
+            if (insertionController != null) {
+                insertionController.getHandle();
+                mInsertionHandleHeight = mSelectHandleCenter.getMinimumHeight();
+            }
+        }
 
         @Override
         public boolean onCreateActionMode(ActionMode mode, Menu menu) {
@@ -2956,13 +2976,6 @@
             mode.setSubtitle(null);
             mode.setTitleOptionalHint(true);
 
-            menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
-                    setIcon(styledAttributes.getResourceId(
-                            R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0)).
-                    setAlphabeticShortcut('a').
-                    setShowAsAction(
-                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
-
             if (mTextView.canCut()) {
                 menu.add(0, TextView.ID_CUT, 0, com.android.internal.R.string.cut).
                     setIcon(styledAttributes.getResourceId(
@@ -2990,6 +3003,13 @@
                                 MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
             }
 
+            menu.add(0, TextView.ID_SELECT_ALL, 0, com.android.internal.R.string.selectAll).
+                    setIcon(styledAttributes.getResourceId(
+                            R.styleable.SelectionModeDrawables_actionModeSelectAllDrawable, 0)).
+                    setAlphabeticShortcut('a').
+                    setShowAsAction(
+                            MenuItem.SHOW_AS_ACTION_ALWAYS | MenuItem.SHOW_AS_ACTION_WITH_TEXT);
+
             if (mTextView.isSuggestionsEnabled() && isCursorInsideSuggestionSpan()) {
                 menu.add(0, TextView.ID_REPLACE, 0, com.android.internal.R.string.replace).
                         setShowAsAction(
@@ -3057,6 +3077,40 @@
 
             mSelectionActionMode = null;
         }
+
+        @Override
+        public void onGetContentRect(ActionMode mode, View view, Rect outRect) {
+            if (!view.equals(mTextView) || mTextView.getLayout() == null) {
+                super.onGetContentRect(mode, view, outRect);
+                return;
+            }
+            if (mTextView.getSelectionStart() != mTextView.getSelectionEnd()) {
+                // We have a selection.
+                mSelectionPath.reset();
+                mTextView.getLayout().getSelectionPath(
+                        mTextView.getSelectionStart(), mTextView.getSelectionEnd(), mSelectionPath);
+                mSelectionPath.computeBounds(mSelectionBounds, true);
+                mSelectionBounds.bottom += mSelectionHandleHeight;
+            } else {
+                // We have a single cursor.
+                int line = mTextView.getLayout().getLineForOffset(mTextView.getSelectionStart());
+                float primaryHorizontal =
+                        mTextView.getLayout().getPrimaryHorizontal(mTextView.getSelectionStart());
+                mSelectionBounds.set(
+                        primaryHorizontal,
+                        mTextView.getLayout().getLineTop(line),
+                        primaryHorizontal + 1,
+                        mTextView.getLayout().getLineTop(line + 1) + mInsertionHandleHeight);
+            }
+            // Take TextView's padding and scroll into account.
+            int textHorizontalOffset = mTextView.viewportToContentHorizontalOffset();
+            int textVerticalOffset = mTextView.viewportToContentVerticalOffset();
+            outRect.set(
+                    (int) Math.floor(mSelectionBounds.left + textHorizontalOffset),
+                    (int) Math.floor(mSelectionBounds.top + textVerticalOffset),
+                    (int) Math.ceil(mSelectionBounds.right + textHorizontalOffset),
+                    (int) Math.ceil(mSelectionBounds.bottom + textVerticalOffset));
+        }
     }
 
     private void onReplace() {
diff --git a/core/java/com/android/internal/midi/EventScheduler.java b/core/java/com/android/internal/midi/EventScheduler.java
index 7526609..7b9a48c 100644
--- a/core/java/com/android/internal/midi/EventScheduler.java
+++ b/core/java/com/android/internal/midi/EventScheduler.java
@@ -27,10 +27,11 @@
 public class EventScheduler {
     private static final long NANOS_PER_MILLI = 1000000;
 
-    private final Object lock = new Object();
+    private final Object mLock = new Object();
     private SortedMap<Long, FastEventQueue> mEventBuffer;
     private FastEventQueue mEventPool = null;
     private int mMaxPoolSize = 200;
+    private boolean mClosed;
 
     public EventScheduler() {
         mEventBuffer = new TreeMap<Long, FastEventQueue>();
@@ -146,7 +147,7 @@
      * @param event
      */
     public void add(SchedulableEvent event) {
-        synchronized (lock) {
+        synchronized (mLock) {
             FastEventQueue list = mEventBuffer.get(event.getTimestamp());
             if (list == null) {
                 long lowestTime = mEventBuffer.isEmpty() ? Long.MAX_VALUE
@@ -156,7 +157,7 @@
                 // If the event we added is earlier than the previous earliest
                 // event then notify any threads waiting for the next event.
                 if (event.getTimestamp() < lowestTime) {
-                    lock.notify();
+                    mLock.notify();
                 }
             } else {
                 list.add(event);
@@ -183,7 +184,7 @@
      */
     public SchedulableEvent getNextEvent(long time) {
         SchedulableEvent event = null;
-        synchronized (lock) {
+        synchronized (mLock) {
             if (!mEventBuffer.isEmpty()) {
                 long lowestTime = mEventBuffer.firstKey();
                 // Is it time for this list to be processed?
@@ -206,9 +207,9 @@
      */
     public SchedulableEvent waitNextEvent() throws InterruptedException {
         SchedulableEvent event = null;
-        while (true) {
-            long millisToWait = Integer.MAX_VALUE;
-            synchronized (lock) {
+        synchronized (mLock) {
+            while (!mClosed) {
+                long millisToWait = Integer.MAX_VALUE;
                 if (!mEventBuffer.isEmpty()) {
                     long now = System.nanoTime();
                     long lowestTime = mEventBuffer.firstKey();
@@ -228,9 +229,16 @@
                         }
                     }
                 }
-                lock.wait((int) millisToWait);
+                mLock.wait((int) millisToWait);
             }
         }
         return event;
     }
+
+    public void close() {
+        synchronized (mLock) {
+            mClosed = true;
+            mLock.notify();
+        }
+    }
 }
diff --git a/core/java/com/android/internal/midi/MidiEventScheduler.java b/core/java/com/android/internal/midi/MidiEventScheduler.java
index 3a1d3fc..42d70f6 100644
--- a/core/java/com/android/internal/midi/MidiEventScheduler.java
+++ b/core/java/com/android/internal/midi/MidiEventScheduler.java
@@ -28,10 +28,16 @@
     // Maintain a pool of scheduled events to reduce memory allocation.
     // This pool increases performance by about 14%.
     private final static int POOL_EVENT_SIZE = 16;
-    private MidiReceiver mReceiver = new SchedulingReceiver();
 
-    private class SchedulingReceiver extends MidiReceiver
-    {
+    private final MidiReceiver[] mReceivers;
+
+    private class SchedulingReceiver extends MidiReceiver {
+        private final int mPortNumber;
+
+        public SchedulingReceiver(int portNumber) {
+            mPortNumber = portNumber;
+        }
+
         /**
          * Store these bytes in the EventScheduler to be delivered at the specified
          * time.
@@ -41,12 +47,14 @@
                 throws IOException {
             MidiEvent event = createScheduledEvent(msg, offset, count, timestamp);
             if (event != null) {
+                event.portNumber = mPortNumber;
                 add(event);
             }
         }
     }
 
     public static class MidiEvent extends SchedulableEvent {
+        public int portNumber;
         public int count = 0;
         public byte[] data;
 
@@ -72,6 +80,17 @@
         }
     }
 
+    public MidiEventScheduler() {
+        this(0);
+    }
+
+    public MidiEventScheduler(int portCount) {
+        mReceivers = new MidiReceiver[portCount];
+        for (int i = 0; i < portCount; i++) {
+            mReceivers[i] = new SchedulingReceiver(i);
+        }
+    }
+
     /**
      * Create an event that contains the message.
      */
@@ -113,7 +132,15 @@
      * @return the MidiReceiver
      */
     public MidiReceiver getReceiver() {
-        return mReceiver;
+        return mReceivers[0];
+    }
+
+    /**
+     * This MidiReceiver will write date to the scheduling buffer.
+     * @return the MidiReceiver
+     */
+    public MidiReceiver getReceiver(int portNumber) {
+        return mReceivers[portNumber];
     }
 
 }
diff --git a/core/java/com/android/internal/os/Zygote.java b/core/java/com/android/internal/os/Zygote.java
index 8674a21..75b6446 100644
--- a/core/java/com/android/internal/os/Zygote.java
+++ b/core/java/com/android/internal/os/Zygote.java
@@ -41,15 +41,10 @@
     /** enable the JIT compiler */
     public static final int DEBUG_ENABLE_JIT         = 1 << 5;
 
-
     /** No external storage should be mounted. */
     public static final int MOUNT_EXTERNAL_NONE = 0;
-    /** Single-user external storage should be mounted. */
-    public static final int MOUNT_EXTERNAL_SINGLEUSER = 1;
-    /** Multi-user external storage should be mounted. */
-    public static final int MOUNT_EXTERNAL_MULTIUSER = 2;
-    /** All multi-user external storage should be mounted. */
-    public static final int MOUNT_EXTERNAL_MULTIUSER_ALL = 3;
+    /** Default user-specific external storage should be mounted. */
+    public static final int MOUNT_EXTERNAL_DEFAULT = 1;
 
     private static final ZygoteHooks VM_HOOKS = new ZygoteHooks();
 
diff --git a/core/java/com/android/internal/os/ZygoteConnection.java b/core/java/com/android/internal/os/ZygoteConnection.java
index 4d405b2..9106ccd 100644
--- a/core/java/com/android/internal/os/ZygoteConnection.java
+++ b/core/java/com/android/internal/os/ZygoteConnection.java
@@ -514,10 +514,8 @@
                                 "Duplicate arg specified");
                     }
                     niceName = arg.substring(arg.indexOf('=') + 1);
-                } else if (arg.equals("--mount-external-multiuser")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
-                } else if (arg.equals("--mount-external-multiuser-all")) {
-                    mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
+                } else if (arg.equals("--mount-external-default")) {
+                    mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
                 } else if (arg.equals("--query-abi-list")) {
                     abiListQuery = true;
                 } else if (arg.startsWith("--instruction-set=")) {
diff --git a/core/jni/AndroidRuntime.cpp b/core/jni/AndroidRuntime.cpp
index 34de6c5..a4c91b3 100644
--- a/core/jni/AndroidRuntime.cpp
+++ b/core/jni/AndroidRuntime.cpp
@@ -558,6 +558,8 @@
     char dex2oatXmxFlagsBuf[sizeof("-Xmx")-1 + PROPERTY_VALUE_MAX];
     char dex2oatCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
     char dex2oatImageCompilerFilterBuf[sizeof("--compiler-filter=")-1 + PROPERTY_VALUE_MAX];
+    char dex2oatThreadsBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX];
+    char dex2oatThreadsImageBuf[sizeof("-j")-1 + PROPERTY_VALUE_MAX];
     char dex2oatFlagsBuf[PROPERTY_VALUE_MAX];
     char dex2oatImageFlagsBuf[PROPERTY_VALUE_MAX];
     char extraOptsBuf[PROPERTY_VALUE_MAX];
@@ -733,6 +735,9 @@
         parseCompilerOption("dalvik.vm.dex2oat-filter", dex2oatCompilerFilterBuf,
                             "--compiler-filter=", "-Xcompiler-option");
     }
+    parseCompilerOption("dalvik.vm.dex2oat-threads", dex2oatThreadsBuf, "-j", "-Xcompiler-option");
+    parseCompilerOption("dalvik.vm.image-dex2oat-threads", dex2oatThreadsImageBuf, "-j",
+                        "-Ximage-compiler-option");
     property_get("dalvik.vm.dex2oat-flags", dex2oatFlagsBuf, "");
     parseExtraOpts(dex2oatFlagsBuf, "-Xcompiler-option");
 
diff --git a/core/jni/android_text_StaticLayout.cpp b/core/jni/android_text_StaticLayout.cpp
index c020020..87c58d6 100644
--- a/core/jni/android_text_StaticLayout.cpp
+++ b/core/jni/android_text_StaticLayout.cpp
@@ -48,11 +48,6 @@
 static jclass gLineBreaks_class;
 static JLineBreaksID gLineBreaks_fieldID;
 
-static const int CHAR_SPACE = 0x20;
-static const int CHAR_TAB = 0x09;
-static const int CHAR_NEWLINE = 0x0a;
-static const int CHAR_ZWSP = 0x200b;
-
 // set text and set a number of parameters for creating a layout (width, tabstops, strategy)
 static void nSetupParagraph(JNIEnv* env, jclass, jlong nativePtr, jcharArray text, jint length,
         jfloat firstWidth, jint firstWidthLineLimit, jfloat restWidth,
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 2bfeadb..76db5d3 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -65,9 +65,7 @@
 // Must match values in com.android.internal.os.Zygote.
 enum MountExternalKind {
   MOUNT_EXTERNAL_NONE = 0,
-  MOUNT_EXTERNAL_SINGLEUSER = 1,
-  MOUNT_EXTERNAL_MULTIUSER = 2,
-  MOUNT_EXTERNAL_MULTIUSER_ALL = 3,
+  MOUNT_EXTERNAL_DEFAULT = 1,
 };
 
 static void RuntimeAbort(JNIEnv* env) {
@@ -269,57 +267,16 @@
   // See storage config details at http://source.android.com/tech/storage/
   userid_t user_id = multiuser_get_user_id(uid);
 
-  // Create bind mounts to expose external storage
-  if (mount_mode == MOUNT_EXTERNAL_MULTIUSER || mount_mode == MOUNT_EXTERNAL_MULTIUSER_ALL) {
-    // These paths must already be created by init.rc
-    const char* source = getenv("EMULATED_STORAGE_SOURCE");
-    const char* target = getenv("EMULATED_STORAGE_TARGET");
-    const char* legacy = getenv("EXTERNAL_STORAGE");
-    if (source == NULL || target == NULL || legacy == NULL) {
-      ALOGW("Storage environment undefined; unable to provide external storage");
-      return false;
-    }
+  // Bind mount user-specific storage into place
+  const String8 source(String8::format("/mnt/user/%d", user_id));
+  const String8 target(String8::format("/storage/self"));
 
-    // Prepare source paths
+  if (fs_prepare_dir(source.string(), 0755, 0, 0) == -1) {
+    return false;
+  }
 
-    // /mnt/shell/emulated/0
-    const String8 source_user(String8::format("%s/%d", source, user_id));
-    // /storage/emulated/0
-    const String8 target_user(String8::format("%s/%d", target, user_id));
-
-    if (fs_prepare_dir(source_user.string(), 0000, 0, 0) == -1
-        || fs_prepare_dir(target_user.string(), 0000, 0, 0) == -1) {
-      return false;
-    }
-
-    if (mount_mode == MOUNT_EXTERNAL_MULTIUSER_ALL) {
-      // Mount entire external storage tree for all users
-      if (TEMP_FAILURE_RETRY(mount(source, target, NULL, MS_BIND, NULL)) == -1) {
-        ALOGW("Failed to mount %s to %s: %s", source, target, strerror(errno));
-        return false;
-      }
-    } else {
-      // Only mount user-specific external storage
-      if (TEMP_FAILURE_RETRY(mount(source_user.string(), target_user.string(), NULL,
-                                   MS_BIND, NULL)) == -1) {
-        ALOGW("Failed to mount %s to %s: %s", source_user.string(), target_user.string(),
-              strerror(errno));
-        return false;
-      }
-    }
-
-    if (fs_prepare_dir(legacy, 0000, 0, 0) == -1) {
-        return false;
-    }
-
-    // Finally, mount user-specific path into place for legacy users
-    if (TEMP_FAILURE_RETRY(
-            mount(target_user.string(), legacy, NULL, MS_BIND | MS_REC, NULL)) == -1) {
-      ALOGW("Failed to mount %s to %s: %s", target_user.string(), legacy, strerror(errno));
-      return false;
-    }
-  } else {
-    ALOGW("Mount mode %d unsupported", mount_mode);
+  if (TEMP_FAILURE_RETRY(mount(source.string(), target.string(), NULL, MS_BIND, NULL)) == -1) {
+    ALOGW("Failed to mount %s to %s: %s", source.string(), target.string(), strerror(errno));
     return false;
   }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 0b1b807..1cb0455 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -1221,6 +1221,14 @@
         android:label="@string/permlab_cameraDisableTransmitLed"
         android:description="@string/permdesc_cameraDisableTransmitLed" />
 
+    <!-- Allows sending the camera service notifications about system-wide events.
+        @hide -->
+    <permission android:name="android.permission.CAMERA_SEND_SYSTEM_EVENTS"
+        android:permissionGroup="android.permission-group.CAMERA"
+        android:protectionLevel="signature|system"
+        android:label="@string/permdesc_cameraSendSystemEvent"
+        android:description="@string/permdesc_cameraSendSystemEvent" />
+
     <!-- =========================================== -->
     <!-- Permissions associated with telephony state -->
     <!-- =========================================== -->
@@ -3179,9 +3187,9 @@
             </intent-filter>
         </receiver>
 
-        <receiver android:name="com.android.server.updates.TZInfoInstallReceiver" >
+        <receiver android:name="com.android.server.updates.TzDataInstallReceiver" >
             <intent-filter>
-                <action android:name="android.intent.action.UPDATE_TZINFO" />
+                <action android:name="android.intent.action.UPDATE_TZDATA" />
                 <data android:scheme="content" android:host="*" android:mimeType="*/*" />
             </intent-filter>
         </receiver>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 702510a..0a77014 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -1758,6 +1758,8 @@
     <string name="permlab_cameraDisableTransmitLed">disable transmit indicator LED when camera is in use</string>
     <!-- Description of a camera app permission, listed so the user can choose whether or not they want to allow it to disable the may-transmit light indicator. -->
     <string name="permdesc_cameraDisableTransmitLed">Allows a pre-installed system application to disable the camera use indicator LED.</string>
+    <!-- Description of a camera app permission, listed so that the user can send the camera service notifications about system-wide events. -->
+    <string name="permdesc_cameraSendSystemEvent">Allows a pre-installed system application to send the camera service system events.</string>
 
     <!-- Title of an application permission, listed so the user can choose whether they want to allow the application to do this. -->
     <string name="permlab_brick" product="tablet">permanently disable tablet</string>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 18302ce..19352c9 100755
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2194,6 +2194,10 @@
   <java-symbol type="dimen" name="day_picker_padding_top"/>
   <java-symbol type="dimen" name="date_picker_day_of_week_height"/>
 
+  <java-symbol type="string" name="storage_internal" />
+  <java-symbol type="string" name="storage_sd_card" />
+  <java-symbol type="string" name="storage_usb" />
+
   <java-symbol type="id" name="accessibility_action_show_on_screen" />
 
   <!-- Floating toolbar -->
diff --git a/graphics/java/android/graphics/ImageFormat.java b/graphics/java/android/graphics/ImageFormat.java
index ada8c12..c63c8ba 100644
--- a/graphics/java/android/graphics/ImageFormat.java
+++ b/graphics/java/android/graphics/ImageFormat.java
@@ -356,6 +356,108 @@
     public static final int RAW10 = 0x25;
 
     /**
+     * <p>
+     * Android 12-bit raw format
+     * </p>
+     * <p>
+     * This is a single-plane, 12-bit per pixel, densely packed (in each row),
+     * unprocessed format, usually representing raw Bayer-pattern images coming
+     * from an image sensor.
+     * </p>
+     * <p>
+     * In an image buffer with this format, starting from the first pixel of each
+     * row, each two consecutive pixels are packed into 3 bytes (24 bits). The first
+     * and second byte contains the top 8 bits of first and second pixel. The third
+     * byte contains the 4 least significant bits of the two pixels, the exact layout
+     * data for each two consecutive pixels is illustrated below (Pi[j] stands for
+     * the jth bit of the ith pixel):
+     * </p>
+     * <table>
+     * <thead>
+     * <tr>
+     * <th align="center"></th>
+     * <th align="center">bit 7</th>
+     * <th align="center">bit 6</th>
+     * <th align="center">bit 5</th>
+     * <th align="center">bit 4</th>
+     * <th align="center">bit 3</th>
+     * <th align="center">bit 2</th>
+     * <th align="center">bit 1</th>
+     * <th align="center">bit 0</th>
+     * </tr>
+     * </thead> <tbody>
+     * <tr>
+     * <td align="center">Byte 0:</td>
+     * <td align="center">P0[11]</td>
+     * <td align="center">P0[10]</td>
+     * <td align="center">P0[ 9]</td>
+     * <td align="center">P0[ 8]</td>
+     * <td align="center">P0[ 7]</td>
+     * <td align="center">P0[ 6]</td>
+     * <td align="center">P0[ 5]</td>
+     * <td align="center">P0[ 4]</td>
+     * </tr>
+     * <tr>
+     * <td align="center">Byte 1:</td>
+     * <td align="center">P1[11]</td>
+     * <td align="center">P1[10]</td>
+     * <td align="center">P1[ 9]</td>
+     * <td align="center">P1[ 8]</td>
+     * <td align="center">P1[ 7]</td>
+     * <td align="center">P1[ 6]</td>
+     * <td align="center">P1[ 5]</td>
+     * <td align="center">P1[ 4]</td>
+     * </tr>
+     * <tr>
+     * <td align="center">Byte 2:</td>
+     * <td align="center">P1[ 3]</td>
+     * <td align="center">P1[ 2]</td>
+     * <td align="center">P1[ 1]</td>
+     * <td align="center">P1[ 0]</td>
+     * <td align="center">P0[ 3]</td>
+     * <td align="center">P0[ 2]</td>
+     * <td align="center">P0[ 1]</td>
+     * <td align="center">P0[ 0]</td>
+     * </tr>
+     * </tbody>
+     * </table>
+     * <p>
+     * This format assumes
+     * <ul>
+     * <li>a width multiple of 4 pixels</li>
+     * <li>an even height</li>
+     * </ul>
+     * </p>
+     *
+     * <pre>size = row stride * height</pre> where the row stride is in <em>bytes</em>,
+     * not pixels.
+     *
+     * <p>
+     * Since this is a densely packed format, the pixel stride is always 0. The
+     * application must use the pixel data layout defined in above table to
+     * access each row data. When row stride is equal to {@code width * (12 / 8)}, there
+     * will be no padding bytes at the end of each row, the entire image data is
+     * densely packed. When stride is larger than {@code width * (12 / 8)}, padding
+     * bytes will be present at the end of each row.
+     * </p>
+     * <p>
+     * For example, the {@link android.media.Image} object can provide data in
+     * this format from a {@link android.hardware.camera2.CameraDevice} (if
+     * supported) through a {@link android.media.ImageReader} object. The
+     * {@link android.media.Image#getPlanes() Image#getPlanes()} will return a
+     * single plane containing the pixel data. The pixel stride is always 0 in
+     * {@link android.media.Image.Plane#getPixelStride()}, and the
+     * {@link android.media.Image.Plane#getRowStride()} describes the vertical
+     * neighboring pixel distance (in bytes) between adjacent rows.
+     * </p>
+     *
+     * @see android.media.Image
+     * @see android.media.ImageReader
+     * @see android.hardware.camera2.CameraDevice
+     */
+    public static final int RAW12 = 0x26;
+
+    /**
      * Android dense depth image format.
      *
      * Each pixel is 16 bits, representing a depth ranging measurement from
@@ -445,6 +547,8 @@
                 return 16;
             case RAW10:
                 return 10;
+            case RAW12:
+                return 12;
         }
         return -1;
     }
@@ -472,6 +576,7 @@
             case YUV_420_888:
             case RAW_SENSOR:
             case RAW10:
+            case RAW12:
             case DEPTH16:
             case DEPTH_POINT_CLOUD:
             case PRIVATE:
diff --git a/libs/hwui/ClipArea.cpp b/libs/hwui/ClipArea.cpp
index 852204a..eb520b4 100644
--- a/libs/hwui/ClipArea.cpp
+++ b/libs/hwui/ClipArea.cpp
@@ -234,7 +234,7 @@
 bool ClipArea::clipRegion(const SkRegion& region, SkRegion::Op op) {
     enterRegionMode();
     mClipRegion.op(region, op);
-    setClipRectToRegionBounds();
+    onClipRegionUpdated();
     return true;
 }
 
@@ -263,6 +263,9 @@
 bool ClipArea::rectangleModeClipRectWithTransform(const Rect& r,
         const mat4* transform, SkRegion::Op op) {
 
+    // TODO: we should be able to handle kReplace_Op efficiently without
+    // going through RegionMode and later falling back into RectangleMode.
+
     if (op != SkRegion::kIntersect_Op) {
         enterRegionMode();
         return regionModeClipRectWithTransform(r, transform, op);
@@ -324,15 +327,16 @@
  */
 
 void ClipArea::enterRegionMode() {
-    if (mMode != kModeRegion) {
-        if (mMode == kModeRectangle) {
+    Mode oldMode = mMode;
+    mMode = kModeRegion;
+    if (oldMode != kModeRegion) {
+        if (oldMode == kModeRectangle) {
             mClipRegion.setRect(mClipRect.left, mClipRect.top,
                     mClipRect.right, mClipRect.bottom);
         } else {
             mClipRegion = mRectangleList.convertToRegion(createViewportRegion());
-            setClipRectToRegionBounds();
+            onClipRegionUpdated();
         }
-        mMode = kModeRegion;
     }
 }
 
@@ -342,7 +346,7 @@
     SkRegion transformedRectRegion;
     regionFromPath(transformedRect, transformedRectRegion);
     mClipRegion.op(transformedRectRegion, op);
-    setClipRectToRegionBounds();
+    onClipRegionUpdated();
     return true;
 }
 
@@ -352,12 +356,13 @@
             transform, op);
 }
 
-void ClipArea::setClipRectToRegionBounds() {
+void ClipArea::onClipRegionUpdated() {
     if (!mClipRegion.isEmpty()) {
         mClipRect.set(mClipRegion.getBounds());
 
         if (mClipRegion.isRect()) {
             mClipRegion.setEmpty();
+            enterRectangleMode();
         }
     } else {
         mClipRect.setEmpty();
diff --git a/libs/hwui/ClipArea.h b/libs/hwui/ClipArea.h
index 16e6df9..e284af0 100644
--- a/libs/hwui/ClipArea.h
+++ b/libs/hwui/ClipArea.h
@@ -144,7 +144,7 @@
             float bottom, const mat4* transform, SkRegion::Op op);
 
     void ensureClipRegion();
-    void setClipRectToRegionBounds();
+    void onClipRegionUpdated();
     bool clipRegionOp(float left, float top, float right, float bottom,
             SkRegion::Op op);
 
diff --git a/libs/hwui/JankTracker.cpp b/libs/hwui/JankTracker.cpp
index 48f5dc1..65be9e1 100644
--- a/libs/hwui/JankTracker.cpp
+++ b/libs/hwui/JankTracker.cpp
@@ -138,7 +138,7 @@
     int regionSize = ashmem_get_size_region(ashmemfd);
     if (regionSize < static_cast<int>(sizeof(ProfileData))) {
         ALOGW("Ashmem region is too small! Received %d, required %u",
-                regionSize, sizeof(ProfileData));
+                regionSize, static_cast<unsigned int>(sizeof(ProfileData)));
         return;
     }
     ProfileData* newData = reinterpret_cast<ProfileData*>(
diff --git a/media/java/android/media/MediaScanner.java b/media/java/android/media/MediaScanner.java
index 1255276..9ea6722 100644
--- a/media/java/android/media/MediaScanner.java
+++ b/media/java/android/media/MediaScanner.java
@@ -553,15 +553,8 @@
                         boolean isimage = MediaFile.isImageFileType(mFileType);
 
                         if (isaudio || isvideo || isimage) {
-                            if (mExternalIsEmulated && path.startsWith(mExternalStoragePath)) {
-                                // try to rewrite the path to bypass the sd card fuse layer
-                                String directPath = Environment.getMediaStorageDirectory() +
-                                        path.substring(mExternalStoragePath.length());
-                                File f = new File(directPath);
-                                if (f.exists()) {
-                                    path = directPath;
-                                }
-                            }
+                            path = Environment.maybeTranslateEmulatedPathToInternal(new File(path))
+                                    .getAbsolutePath();
                         }
 
                         // we only extract metadata for audio and video files
diff --git a/media/java/android/mtp/MtpStorage.java b/media/java/android/mtp/MtpStorage.java
index e20eabc..7b8102b 100644
--- a/media/java/android/mtp/MtpStorage.java
+++ b/media/java/android/mtp/MtpStorage.java
@@ -59,7 +59,7 @@
      *
      * @return the storage ID
      */
-    public static int getStorageId(int index) {
+    public static int getStorageIdForIndex(int index) {
         // storage ID is 0x00010001 for primary storage,
         // then 0x00020001, 0x00030001, etc. for secondary storages
         return ((index + 1) << 16) + 1;
diff --git a/media/jni/android_media_ImageReader.cpp b/media/jni/android_media_ImageReader.cpp
index 9fc7e8e..708c083 100644
--- a/media/jni/android_media_ImageReader.cpp
+++ b/media/jni/android_media_ImageReader.cpp
@@ -431,6 +431,19 @@
             pData = buffer->data;
             dataSize = buffer->stride * buffer->height;
             break;
+        case HAL_PIXEL_FORMAT_RAW12:
+            // Single plane 10bpp bayer data.
+            ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
+            LOG_ALWAYS_FATAL_IF(buffer->width % 4,
+                                "Width is not multiple of 4 %d", buffer->width);
+            LOG_ALWAYS_FATAL_IF(buffer->height % 2,
+                                "Height is not even %d", buffer->height);
+            LOG_ALWAYS_FATAL_IF(buffer->stride < (buffer->width * 12 / 8),
+                                "stride (%d) should be at least %d",
+                                buffer->stride, buffer->width * 12 / 8);
+            pData = buffer->data;
+            dataSize = buffer->stride * buffer->height;
+            break;
         case HAL_PIXEL_FORMAT_RGBA_8888:
         case HAL_PIXEL_FORMAT_RGBX_8888:
             // Single plane, 32bpp.
@@ -492,8 +505,10 @@
             break;
         case HAL_PIXEL_FORMAT_BLOB:
         case HAL_PIXEL_FORMAT_RAW10:
-            // Blob is used for JPEG data, RAW10 is used for 10-bit raw data, they are
-            // single plane, row and pixel strides are 0.
+        case HAL_PIXEL_FORMAT_RAW12:
+            // Blob is used for JPEG data, RAW10 and RAW12 is used for 10-bit and 12-bit raw data,
+            // those are single plane data with pixel stride 0 since they don't really have a
+            // well defined pixel stride
             ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
             pixelStride = 0;
             break;
@@ -549,12 +564,14 @@
             rowStride = (idx == 0) ? buffer->stride : ALIGN(buffer->stride / 2, 16);
             break;
         case HAL_PIXEL_FORMAT_BLOB:
-            // Blob is used for JPEG data, RAW10 is used for 10-bit raw data, they are
-            // single plane, row and pixel strides are 0.
+            // Blob is used for JPEG data. It is single plane and has 0 row stride and
+            // 0 pixel stride
             ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
             rowStride = 0;
             break;
         case HAL_PIXEL_FORMAT_RAW10:
+        case HAL_PIXEL_FORMAT_RAW12:
+            // RAW10 and RAW12 are used for 10-bit and 12-bit raw data, they are single plane
             ALOG_ASSERT(idx == 0, "Wrong index: %d", idx);
             rowStride = buffer->stride;
             break;
diff --git a/packages/DocumentsUI/res/menu/mode_directory.xml b/packages/DocumentsUI/res/menu/mode_directory.xml
index 0a3645f..695060d 100644
--- a/packages/DocumentsUI/res/menu/mode_directory.xml
+++ b/packages/DocumentsUI/res/menu/mode_directory.xml
@@ -29,4 +29,8 @@
         android:icon="@drawable/ic_menu_delete"
         android:title="@string/menu_delete"
         android:showAsAction="always" />
+    <item
+        android:id="@+id/menu_select_all"
+        android:title="@string/menu_select_all"
+        android:showAsAction="never" />
 </menu>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 268ce18..4ad337d 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -46,6 +46,8 @@
     <string name="menu_delete">Delete</string>
     <!-- Menu item title that selects the current directory [CHAR LIMIT=48] -->
     <string name="menu_select">Select \"<xliff:g id="directory" example="My Directory">^1</xliff:g>\"</string>
+    <!-- Menu item title that selects all documents in the current directory [CHAR LIMIT=24] -->
+    <string name="menu_select_all">Select All</string>
 
     <!-- Menu item that reveals internal storage built into the device [CHAR LIMIT=24] -->
     <string name="menu_advanced_show" product="nosdcard">Show internal storage</string>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
index f55912c..a75dc42 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/DirectoryFragment.java
@@ -501,6 +501,14 @@
                 mode.finish();
                 return true;
 
+            } else if (id == R.id.menu_select_all) {
+                int count = mCurrentView.getCount();
+                for (int i = 0; i < count; i++) {
+                    mCurrentView.setItemChecked(i, true);
+                }
+                updateDisplayState();
+                return true;
+
             } else {
                 return false;
             }
diff --git a/rs/java/android/renderscript/Allocation.java b/rs/java/android/renderscript/Allocation.java
index c4daaa9..2b2e611 100644
--- a/rs/java/android/renderscript/Allocation.java
+++ b/rs/java/android/renderscript/Allocation.java
@@ -273,8 +273,8 @@
     }
 
     /**
-     * @hide
      * Enable/Disable AutoPadding for Vec3 elements.
+     * By default: Diabled.
      *
      * @param useAutoPadding True: enable AutoPadding; flase: disable AutoPadding
      *
diff --git a/rs/java/android/renderscript/ScriptGroup2.java b/rs/java/android/renderscript/ScriptGroup2.java
index 4a56572..a5da519 100644
--- a/rs/java/android/renderscript/ScriptGroup2.java
+++ b/rs/java/android/renderscript/ScriptGroup2.java
@@ -24,25 +24,39 @@
 import java.util.Map;
 
 /**
+ * ScriptGroup2 is a new, enhanced API for script groups.
+ * A script group is a collection of kernels or invocable functions, with
+ * data dependencies defined among them. A script group is launched for
+ * execution as a whole, rather than launching each kernel or invocable function
+ * separately. Once created, a script group can be repeatedly used with
+ * different inputs.
+ * <p>
+ * In the new ScriptGroup2 API, a script group is modeled using closures.
+ * A closure, in this context, is defined as a function call to a kernel or
+ * invocable function. Each function argument or global variable accessed inside
+ * the function is bound to 1) a known value, 2) a script group input, or 3) a
+ * future. A future is the output of a closure, i.e., the return value of the
+ * function or a global variable written by that function.
+ * <p>
+ * A script group is a directed acyclic graph (DAG), in which closures are the
+ * vertices and the dependencies among them are the edges.
+ * The way the ScriptGroup2 API is designed makes cycles impossible in a script
+ * group. For example, it is impossible to make forward references to futures,
+ * i.e., it is impossible to set as input to a closure the future from itself or
+ * a future from another closure that directly or indirectly depends on it.
+ * <p>
+ * Grouping kernels and invocable functions together allows to execute them more
+ * efficiently. Runtime and compiler optimizations are applied to script
+ * groups, to reduce computation or communication overhead, and to make more
+ * efficient use of the CPU and the GPU.
+ */
 
-******************************
-You have tried to change the API from what has been previously approved.
-
-To make these errors go away, you have two choices:
-1) You can add "@hide" javadoc comments to the methods, etc. listed in the
-errors above.
-
-2) You can update current.txt by executing the following command:
-make update-api
-
-To submit the revised current.txt to the main Android repository,
-you will need approval.
-******************************
-
-@hide Pending Android public API approval.
-*/
 public class ScriptGroup2 extends BaseObj {
 
+    /**
+     * An opaque class for closures
+     */
+
     public static class Closure extends BaseObj {
         private Allocation mReturnValue;
         private Map<Script.FieldID, Object> mBindings;
@@ -132,7 +146,6 @@
                     UnboundValue unbound = (UnboundValue)obj;
                     unbound.addReference(this, fieldID);
                 } else {
-                    // TODO(yangni): Verify obj not a future.
                     retrieveValueAndDependenceInfo(rs, i, obj, values,
                                                    sizes, depClosures, depFieldIDs);
                 }
@@ -174,6 +187,12 @@
             sizes[index] = vs.size;
         }
 
+        /**
+         * Returns the future for the return value
+         *
+         * @return a future
+         */
+
         public Future getReturn() {
             if (mReturnFuture == null) {
                 mReturnFuture = new Future(this, null, mReturnValue);
@@ -182,6 +201,13 @@
             return mReturnFuture;
         }
 
+        /**
+         * Returns the future for a global variable
+         *
+         * @param field the field ID for the global variable
+         * @return a future
+         */
+
         public Future getGlobal(Script.FieldID field) {
             Future f = mGlobalFuture.get(field);
 
@@ -234,6 +260,10 @@
         }
     }
 
+    /**
+     * An opaque class for futures
+     */
+
     public static class Future {
         Closure mClosure;
         Script.FieldID mFieldID;
@@ -250,6 +280,10 @@
         Object getValue() { return mValue; }
     }
 
+    /**
+     * An opaque class for unbound values (a.k.a. script group inputs)
+     */
+
     public static class UnboundValue {
         // Either mFieldID or mArgIndex should be set but not both.
         List<Pair<Closure, Script.FieldID>> mFieldID;
@@ -309,6 +343,13 @@
         setID(id);
     }
 
+    /**
+     * Executes a script group
+     *
+     * @param inputs inputs to the script group
+     * @return outputs of the script group as an array of objects
+     */
+
     public Object[] execute(Object... inputs) {
         if (inputs.length < mInputs.size()) {
             Log.e(TAG, this.toString() + " receives " + inputs.length + " inputs, " +
@@ -343,32 +384,95 @@
     }
 
     /**
-       @hide Pending Android public API approval.
-    */
+     * A class representing a binding of a value to a global variable in a
+     * kernel or invocable function. Such a binding can be used to create a
+     * closure.
+     */
+
     public static final class Binding {
-        public Script.FieldID mField;
-        public Object mValue;
+        private Script.FieldID mField;
+        private Object mValue;
+
+        /**
+         * Returns a Binding object that binds value to field
+         *
+         * @param field the Script.FieldID of the global variable
+         * @param value the value
+         */
+
         public Binding(Script.FieldID field, Object value) {
             mField = field;
             mValue = value;
         }
+
+        /**
+         * Returns the field ID
+         */
+
+        public Script.FieldID getField() { return mField; }
+
+        /**
+         * Returns the value
+         */
+
+        public Object getValue() { return mValue; }
     }
 
     /**
-       @hide Pending Android public API approval.
-    */
+     * The builder class to create a script group.
+     * <p>
+     * Closures are created using the {@link #addKernel} or {@link #addInvoke}
+     * methods.
+     * When a closure is created, futures from previously created closures
+     * can be used as inputs.
+     * Unbound values can be used as inputs to create closures as well.
+     * An unbound value is created using the {@link #addInput} method.
+     * Unbound values become inputs to the script group to be created,
+     * in the order that they are added.
+     * A script group is created by a call to the {@link #create} method, which
+     * accepts an array of futures as the outputs for the script group.
+     * <p>
+     * Closures in a script group can be evaluated in any order as long as the
+     * following conditions are met.
+     * First, a closure must be evaluated before any other closures that take its
+     * futures as inputs.
+     * Second, all closures added before an invoke closure must be evaluated
+     * before it.
+     * Third, all closures added after an invoke closure must be evaluated after
+     * it.
+     * <p>
+     * As a special case, the order that the closures are added is a legal
+     * evaluation order. However, other evaluation orders are allowed, including
+     * concurrently evaluating independent closures.
+     */
+
     public static final class Builder {
         RenderScript mRS;
         List<Closure> mClosures;
         List<UnboundValue> mInputs;
         private static final String TAG = "ScriptGroup2.Builder";
 
+        /**
+         * Returns a Builder object
+         *
+         * @param rs the RenderScript context
+         */
         public Builder(RenderScript rs) {
             mRS = rs;
             mClosures = new ArrayList<Closure>();
             mInputs = new ArrayList<UnboundValue>();
         }
 
+        /**
+         * Adds a closure for a kernel
+         *
+         * @param k Kernel ID for the kernel function
+         * @param returnType Allocation type for the return value
+         * @param args arguments to the kernel function
+         * @param globalBindings bindings for global variables
+         * @return a closure
+         */
+
         public Closure addKernel(Script.KernelID k, Type returnType, Object[] args,
                                  Map<Script.FieldID, Object> globalBindings) {
             Closure c = new Closure(mRS, k, returnType, args, globalBindings);
@@ -376,6 +480,15 @@
             return c;
         }
 
+        /**
+         * Adds a closure for an invocable function
+         *
+         * @param invoke Invoke ID for the invocable function
+         * @param args arguments to the invocable function
+         * @param globalBindings bindings for global variables
+         * @return a closure
+         */
+
         public Closure addInvoke(Script.InvokeID invoke, Object[] args,
                                  Map<Script.FieldID, Object> globalBindings) {
             Closure c = new Closure(mRS, invoke, args, globalBindings);
@@ -383,12 +496,25 @@
             return c;
         }
 
+        /**
+         * Adds a script group input
+         *
+         * @return a unbound value that can be used to create a closure
+         */
         public UnboundValue addInput() {
             UnboundValue unbound = new UnboundValue();
             mInputs.add(unbound);
             return unbound;
         }
 
+        /**
+         * Adds a closure for a kernel
+         *
+         * @param k Kernel ID for the kernel function
+         * @param argsAndBindings arguments followed by bindings for global variables
+         * @return a closure
+         */
+
         public Closure addKernel(Script.KernelID k, Type returnType, Object... argsAndBindings) {
             ArrayList<Object> args = new ArrayList<Object>();
             Map<Script.FieldID, Object> bindingMap = new HashMap<Script.FieldID, Object>();
@@ -398,6 +524,14 @@
             return addKernel(k, returnType, args.toArray(), bindingMap);
         }
 
+        /**
+         * Adds a closure for an invocable function
+         *
+         * @param invoke Invoke ID for the invocable function
+         * @param argsAndBindings arguments followed by bindings for global variables
+         * @return a closure
+         */
+
         public Closure addInvoke(Script.InvokeID invoke, Object... argsAndBindings) {
             ArrayList<Object> args = new ArrayList<Object>();
             Map<Script.FieldID, Object> bindingMap = new HashMap<Script.FieldID, Object>();
@@ -407,6 +541,13 @@
             return addInvoke(invoke, args.toArray(), bindingMap);
         }
 
+        /**
+         * Creates a script group
+         *
+         * @param outputs futures intended as outputs of the script group
+         * @return a script group
+         */
+
         public ScriptGroup2 create(Future... outputs) {
             ScriptGroup2 ret = new ScriptGroup2(mRS, mClosures, mInputs, outputs);
             return ret;
@@ -428,7 +569,7 @@
                     return false;
                 }
                 Binding b = (Binding)argsAndBindings[i];
-                bindingMap.put(b.mField, b.mValue);
+                bindingMap.put(b.getField(), b.getValue());
             }
 
             return true;
diff --git a/services/backup/java/com/android/server/backup/BackupManagerService.java b/services/backup/java/com/android/server/backup/BackupManagerService.java
index 45b0fb2..5cc59e5 100644
--- a/services/backup/java/com/android/server/backup/BackupManagerService.java
+++ b/services/backup/java/com/android/server/backup/BackupManagerService.java
@@ -3938,16 +3938,6 @@
                     return;
                 }
 
-                // Don't proceed unless we have already established package metadata
-                // for the current dataset via a key/value backup pass.
-                File stateDir = new File(mBaseStateDir, transport.transportDirName());
-                File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL);
-                if (pmState.length() <= 0) {
-                    Slog.i(TAG, "Full backup requested but dataset not yet initialized "
-                            + "via k/v backup pass; ignoring");
-                    return;
-                }
-
                 // Set up to send data to the transport
                 final int N = mPackages.size();
                 for (int i = 0; i < N; i++) {
@@ -4296,6 +4286,31 @@
         writeFullBackupScheduleAsync();
     }
 
+    private boolean fullBackupAllowable(IBackupTransport transport) {
+        if (transport == null) {
+            Slog.w(TAG, "Transport not present; full data backup not performed");
+            return false;
+        }
+
+        // Don't proceed unless we have already established package metadata
+        // for the current dataset via a key/value backup pass.
+        try {
+            File stateDir = new File(mBaseStateDir, transport.transportDirName());
+            File pmState = new File(stateDir, PACKAGE_MANAGER_SENTINEL);
+            if (pmState.length() <= 0) {
+                if (DEBUG) {
+                    Slog.i(TAG, "Full backup requested but dataset not yet initialized");
+                }
+                return false;
+            }
+        } catch (Exception e) {
+            Slog.w(TAG, "Unable to contact transport");
+            return false;
+        }
+
+        return true;
+    }
+
     /**
      * Conditions are right for a full backup operation, so run one.  The model we use is
      * to perform one app backup per scheduled job execution, and to reschedule the job
@@ -4307,6 +4322,7 @@
     boolean beginFullBackup(FullBackupJob scheduledJob) {
         long now = System.currentTimeMillis();
         FullBackupEntry entry = null;
+        long latency = MIN_FULL_BACKUP_INTERVAL;
 
         if (!mEnabled || !mProvisioned) {
             // Backups are globally disabled, so don't proceed.  We also don't reschedule
@@ -4338,17 +4354,41 @@
                 return false;
             }
 
-            entry = mFullBackupQueue.get(0);
-            long timeSinceRun = now - entry.lastBackup;
-            if (timeSinceRun < MIN_FULL_BACKUP_INTERVAL) {
-                // It's too early to back up the next thing in the queue, so bow out
+            // At this point we know that we have work to do, just not right now.  Any
+            // exit without actually running backups will also require that we
+            // reschedule the job.
+            boolean runBackup = true;
+
+            if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
                 if (MORE_DEBUG) {
-                    Slog.i(TAG, "Device ready but too early to back up next app");
+                    Slog.i(TAG, "Preconditions not met; not running full backup");
                 }
-                final long latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+                runBackup = false;
+                // Typically this means we haven't run a key/value backup yet.  Back off
+                // full-backup operations by the key/value job's run interval so that
+                // next time we run, we are likely to be able to make progress.
+                latency = KeyValueBackupJob.BATCH_INTERVAL;
+            }
+
+            if (runBackup) {
+                entry = mFullBackupQueue.get(0);
+                long timeSinceRun = now - entry.lastBackup;
+                runBackup = (timeSinceRun >= MIN_FULL_BACKUP_INTERVAL);
+                if (!runBackup) {
+                    // It's too early to back up the next thing in the queue, so bow out
+                    if (MORE_DEBUG) {
+                        Slog.i(TAG, "Device ready but too early to back up next app");
+                    }
+                    // Wait until the next app in the queue falls due for a full data backup
+                    latency = MIN_FULL_BACKUP_INTERVAL - timeSinceRun;
+                }
+            }
+
+            if (!runBackup) {
+                final long deferTime = latency;     // pin for the closure
                 mBackupHandler.post(new Runnable() {
                     @Override public void run() {
-                        FullBackupJob.schedule(mContext, latency);
+                        FullBackupJob.schedule(mContext, deferTime);
                     }
                 });
                 return false;
@@ -8489,27 +8529,31 @@
             throw new IllegalStateException("Restore supported only for the device owner");
         }
 
-        if (DEBUG) {
-            Slog.d(TAG, "fullTransportBackup()");
-        }
-
-        CountDownLatch latch = new CountDownLatch(1);
-        PerformFullTransportBackupTask task =
-                new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
-        (new Thread(task, "full-transport-master")).start();
-        do {
-            try {
-                latch.await();
-                break;
-            } catch (InterruptedException e) {
-                // Just go back to waiting for the latch to indicate completion
+        if (!fullBackupAllowable(getTransport(mCurrentTransport))) {
+            Slog.i(TAG, "Full backup not currently possible -- key/value backup not yet run?");
+        } else {
+            if (DEBUG) {
+                Slog.d(TAG, "fullTransportBackup()");
             }
-        } while (true);
 
-        // We just ran a backup on these packages, so kick them to the end of the queue
-        final long now = System.currentTimeMillis();
-        for (String pkg : pkgNames) {
-            enqueueFullBackup(pkg, now);
+            CountDownLatch latch = new CountDownLatch(1);
+            PerformFullTransportBackupTask task =
+                    new PerformFullTransportBackupTask(null, pkgNames, false, null, latch);
+            (new Thread(task, "full-transport-master")).start();
+            do {
+                try {
+                    latch.await();
+                    break;
+                } catch (InterruptedException e) {
+                    // Just go back to waiting for the latch to indicate completion
+                }
+            } while (true);
+
+            // We just ran a backup on these packages, so kick them to the end of the queue
+            final long now = System.currentTimeMillis();
+            for (String pkg : pkgNames) {
+                enqueueFullBackup(pkg, now);
+            }
         }
 
         if (DEBUG) {
diff --git a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
index dc1c9d5..a4489c1 100644
--- a/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
+++ b/services/backup/java/com/android/server/backup/KeyValueBackupJob.java
@@ -41,7 +41,7 @@
     // Once someone asks for a backup, this is how long we hold off, batching
     // up additional requests, before running the actual backup pass.  Privileged
     // callers can always trigger an immediate pass via BackupManager.backupNow().
-    private static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR;
+    static final long BATCH_INTERVAL = 4 * AlarmManager.INTERVAL_HOUR;
 
     // Random variation in next-backup scheduling time to avoid server load spikes
     private static final int FUZZ_MILLIS = 10 * 60 * 1000;
diff --git a/services/core/Android.mk b/services/core/Android.mk
index 1a0fa34..64b6134 100644
--- a/services/core/Android.mk
+++ b/services/core/Android.mk
@@ -10,5 +10,6 @@
     java/com/android/server/am/EventLogTags.logtags
 
 LOCAL_JAVA_LIBRARIES := telephony-common
+LOCAL_STATIC_JAVA_LIBRARIES := tzdata_update
 
 include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/services/core/java/com/android/server/MountService.java b/services/core/java/com/android/server/MountService.java
index b8d9ec5..61286e8 100644
--- a/services/core/java/com/android/server/MountService.java
+++ b/services/core/java/com/android/server/MountService.java
@@ -16,29 +16,21 @@
 
 package com.android.server;
 
-import static android.content.pm.PackageManager.PERMISSION_GRANTED;
-
 import android.Manifest;
 import android.app.ActivityManagerNative;
 import android.app.AppOpsManager;
-import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
-import android.content.IntentFilter;
 import android.content.ServiceConnection;
-import android.content.pm.PackageManager;
-import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.content.res.ObbInfo;
-import android.content.res.Resources;
-import android.content.res.TypedArray;
-import android.content.res.XmlResourceParser;
-import android.hardware.usb.UsbManager;
+import android.mtp.MtpStorage;
 import android.net.Uri;
 import android.os.Binder;
 import android.os.Environment;
 import android.os.Environment.UserEnvironment;
+import android.os.FileUtils;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
@@ -59,26 +51,24 @@
 import android.os.storage.StorageResultCode;
 import android.os.storage.StorageVolume;
 import android.text.TextUtils;
-import android.util.AttributeSet;
+import android.util.ArrayMap;
+import android.util.DebugUtils;
 import android.util.Slog;
-import android.util.Xml;
+import android.util.SparseArray;
+
+import libcore.util.EmptyArray;
+import libcore.util.HexEncoding;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
-import com.android.internal.util.XmlUtils;
 import com.android.server.NativeDaemonConnector.Command;
 import com.android.server.NativeDaemonConnector.SensitiveArg;
-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 libcore.util.HexEncoding;
-
-import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.File;
 import java.io.FileDescriptor;
@@ -101,7 +91,6 @@
 import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
-import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
@@ -110,19 +99,18 @@
 import javax.crypto.spec.PBEKeySpec;
 
 /**
- * MountService implements back-end services for platform storage
- * management.
- * @hide - Applications should use android.os.storage.StorageManager
- * to access the MountService.
+ * Service responsible for various storage media. Connects to {@code vold} to
+ * watch for and manage dynamically added storage, such as SD cards and USB mass
+ * storage. Also decides how storage should be presented to users on the device.
  */
 class MountService extends IMountService.Stub
         implements INativeDaemonConnectorCallbacks, Watchdog.Monitor {
 
+    // TODO: finish enforcing UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA
+
     // Static direct instance pointer for the tightly-coupled idle service to use
     static MountService sSelf = null;
 
-    // TODO: listen for user creation/deletion
-
     public static class Lifecycle extends SystemService {
         private MountService mMountService;
 
@@ -142,6 +130,16 @@
                 mMountService.systemReady();
             }
         }
+
+        @Override
+        public void onStartUser(int userHandle) {
+            mMountService.onStartUser(userHandle);
+        }
+
+        @Override
+        public void onCleanupUser(int userHandle) {
+            mMountService.onCleanupUser(userHandle);
+        }
     }
 
     private static final boolean LOCAL_LOGD = false;
@@ -159,21 +157,7 @@
     /** Maximum number of ASEC containers allowed to be mounted. */
     private static final int MAX_CONTAINERS = 250;
 
-    /*
-     * Internal vold volume state constants
-     */
-    class VolumeState {
-        public static final int Init       = -1;
-        public static final int NoMedia    = 0;
-        public static final int Idle       = 1;
-        public static final int Pending    = 2;
-        public static final int Checking   = 3;
-        public static final int Mounted    = 4;
-        public static final int Unmounting = 5;
-        public static final int Formatting = 6;
-        public static final int Shared     = 7;
-        public static final int SharedMnt  = 8;
-    }
+    private static final String PROP_PRIMARY_PHYSICAL = "ro.vold.primary_physical";
 
     /*
      * Internal vold response code constants
@@ -209,12 +193,19 @@
         /*
          * 600 series - Unsolicited broadcasts.
          */
-        public static final int VolumeStateChange              = 605;
-        public static final int VolumeUuidChange               = 613;
-        public static final int VolumeUserLabelChange          = 614;
-        public static final int VolumeDiskInserted             = 630;
-        public static final int VolumeDiskRemoved              = 631;
-        public static final int VolumeBadRemoval               = 632;
+        public static final int DISK_CREATED = 640;
+        public static final int DISK_SIZE_CHANGED = 641;
+        public static final int DISK_LABEL_CHANGED = 642;
+        public static final int DISK_VOLUME_CREATED = 643;
+        public static final int DISK_DESTROYED = 649;
+
+        public static final int VOLUME_CREATED = 650;
+        public static final int VOLUME_STATE_CHANGED = 651;
+        public static final int VOLUME_FS_TYPE_CHANGED = 652;
+        public static final int VOLUME_FS_UUID_CHANGED = 653;
+        public static final int VOLUME_FS_LABEL_CHANGED = 654;
+        public static final int VOLUME_PATH_CHANGED = 655;
+        public static final int VOLUME_DESTROYED = 659;
 
         /*
          * 700 series - fstrim
@@ -222,6 +213,243 @@
         public static final int FstrimCompleted                = 700;
     }
 
+    private static SparseArray<String> sStateToEnvironment = new SparseArray<>();
+    private static ArrayMap<String, String> sEnvironmentToBroadcast = new ArrayMap<>();
+
+    static {
+        sStateToEnvironment.put(Volume.STATE_UNMOUNTED, Environment.MEDIA_UNMOUNTED);
+        sStateToEnvironment.put(Volume.STATE_MOUNTING, Environment.MEDIA_CHECKING);
+        sStateToEnvironment.put(Volume.STATE_MOUNTED, Environment.MEDIA_MOUNTED);
+        sStateToEnvironment.put(Volume.STATE_FORMATTING, Environment.MEDIA_UNMOUNTED);
+        sStateToEnvironment.put(Volume.STATE_UNMOUNTING, Environment.MEDIA_EJECTING);
+
+        sEnvironmentToBroadcast.put(Environment.MEDIA_UNMOUNTED, Intent.ACTION_MEDIA_UNMOUNTED);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_CHECKING, Intent.ACTION_MEDIA_CHECKING);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_MOUNTED, Intent.ACTION_MEDIA_MOUNTED);
+        sEnvironmentToBroadcast.put(Environment.MEDIA_EJECTING, Intent.ACTION_MEDIA_EJECT);
+    }
+
+    /**
+     * <em>Never</em> hold the lock while performing downcalls into vold, since
+     * unsolicited events can suddenly appear to update data structures.
+     */
+    private final Object mLock = new Object();
+
+    @GuardedBy("mLock")
+    private int[] mStartedUsers = EmptyArray.INT;
+    @GuardedBy("mLock")
+    private ArrayMap<String, Disk> mDisks = new ArrayMap<>();
+    @GuardedBy("mLock")
+    private ArrayMap<String, Volume> mVolumes = new ArrayMap<>();
+
+    @Deprecated
+    private Volume findVolumeByLegacyPath(String legacyPath) {
+        synchronized (mLock) {
+            for (Volume vol : mVolumes.values()) {
+                if (vol.path != null && legacyPath.startsWith(vol.path)) {
+                    return vol;
+                }
+            }
+        }
+        Slog.w(TAG, "Failed to find volume for path " + legacyPath);
+        return null;
+    }
+
+    /**
+     * Framework-side twin of android::vold::Disk
+     */
+    private class Disk {
+        public static final int FLAG_ADOPTABLE = 1 << 0;
+        public static final int FLAG_DEFAULT_PRIMARY = 1 << 1;
+        public static final int FLAG_SD = 1 << 2;
+        public static final int FLAG_USB = 1 << 3;
+
+        public final String id;
+        public final int flags;
+        public long size;
+        public String label;
+
+        public ArrayList<Volume> volumes = new ArrayList<>();
+
+        public Disk(String id, int flags) {
+            this.id = id;
+            this.flags = flags;
+        }
+
+        public void partitionPublic() throws NativeDaemonConnectorException {
+            mConnector.execute("volume", "partition", id, "public");
+        }
+
+        public void partitionPrivate() throws NativeDaemonConnectorException {
+            mConnector.execute("volume", "partition", id, "private");
+        }
+
+        public void partitionMixed(int frac) throws NativeDaemonConnectorException {
+            mConnector.execute("volume", "partition", id, "mixed", frac);
+        }
+
+        public void dump(IndentingPrintWriter pw) {
+            pw.println("Disk:");
+            pw.increaseIndent();
+            pw.printPair("id", id);
+            pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
+            pw.printPair("size", size);
+            pw.printPair("label", label);
+            pw.decreaseIndent();
+            pw.println();
+        }
+    }
+
+    private static int sNextMtpIndex = 1;
+
+    /**
+     * Framework-side twin of android::vold::VolumeBase
+     */
+    private class Volume {
+        public static final String ID_EMULATED_INTERNAL = "emulated";
+
+        public static final int TYPE_PUBLIC = 0;
+        public static final int TYPE_PRIVATE = 1;
+        public static final int TYPE_EMULATED = 2;
+        public static final int TYPE_ASEC = 3;
+        public static final int TYPE_OBB = 4;
+
+        public static final int STATE_UNMOUNTED = 0;
+        public static final int STATE_MOUNTING = 1;
+        public static final int STATE_MOUNTED = 2;
+        public static final int STATE_FORMATTING = 3;
+        public static final int STATE_UNMOUNTING = 4;
+
+        public static final int FLAG_PRIMARY = 1 << 0;
+        public static final int FLAG_VISIBLE = 1 << 1;
+
+        /** vold state */
+        public final String id;
+        public final int type;
+        public int flags = 0;
+        public int userId = -1;
+        public int state = STATE_UNMOUNTED;
+        public String fsType;
+        public String fsUuid;
+        public String fsLabel;
+        public String path = "/dev/null";
+
+        /** Framework state */
+        public final int mtpIndex;
+
+        public Disk disk;
+
+        public Volume(String id, int type) {
+            this.id = id;
+            this.type = type;
+
+            if (ID_EMULATED_INTERNAL.equals(id)) {
+                mtpIndex = 0;
+            } else {
+                mtpIndex = sNextMtpIndex++;
+            }
+        }
+
+        public boolean isPrimary() {
+            return (flags & FLAG_PRIMARY) != 0;
+        }
+
+        public boolean isVisible() {
+            return (flags & FLAG_VISIBLE) != 0;
+        }
+
+        public boolean isVisibleToUser(int userId) {
+            if (type == TYPE_PUBLIC && this.userId == userId) {
+                return isVisible();
+            } else if (type == TYPE_EMULATED) {
+                return isVisible();
+            } else {
+                return false;
+            }
+        }
+
+        public void mount() throws NativeDaemonConnectorException {
+            mConnector.execute("volume", "mount", id, flags, userId);
+        }
+
+        public void unmount() throws NativeDaemonConnectorException {
+            mConnector.execute("volume", "unmount", id);
+        }
+
+        public void format() throws NativeDaemonConnectorException {
+            mConnector.execute("volume", "format", id);
+        }
+
+        public StorageVolume buildVolumeForUser(int userId) {
+            final File userPath;
+            final boolean removable;
+            final boolean emulated;
+            final boolean allowMassStorage = false;
+            final int mtpStorageId = MtpStorage.getStorageIdForIndex(mtpIndex);
+            final String envState = sStateToEnvironment.get(state);
+
+            int descriptionId = com.android.internal.R.string.unknownName;
+            long mtpReserveSize = 0;
+            long maxFileSize = 0;
+
+            if (type == TYPE_EMULATED) {
+                userPath = new File(path, Integer.toString(userId));
+                emulated = true;
+                mtpReserveSize = StorageManager.from(mContext).getStorageLowBytes(userPath);
+                descriptionId = com.android.internal.R.string.storage_internal;
+
+                if (ID_EMULATED_INTERNAL.equals(id)) {
+                    removable = false;
+                } else {
+                    removable = true;
+                }
+
+            } else if (type == TYPE_PUBLIC) {
+                userPath = new File(path);
+                emulated = false;
+                removable = true;
+
+                if (disk != null) {
+                    if ((disk.flags & Disk.FLAG_SD) != 0) {
+                        descriptionId = com.android.internal.R.string.storage_sd_card;
+                    } else if ((disk.flags & Disk.FLAG_USB) != 0) {
+                        descriptionId = com.android.internal.R.string.storage_usb;
+                    }
+                }
+
+                if ("vfat".equals(fsType)) {
+                    maxFileSize = 4294967295L;
+                }
+
+            } else {
+                throw new IllegalStateException("Unexpected volume type " + type);
+            }
+
+            return new StorageVolume(id, mtpStorageId, userPath, descriptionId, isPrimary(),
+                    removable, emulated, mtpReserveSize, allowMassStorage, maxFileSize,
+                    new UserHandle(userId), fsUuid, fsLabel, envState);
+        }
+
+        public void dump(IndentingPrintWriter pw) {
+            pw.println("Volume:");
+            pw.increaseIndent();
+            pw.printPair("id", id);
+            pw.printPair("type", DebugUtils.valueToString(getClass(), "TYPE_", type));
+            pw.printPair("flags", DebugUtils.flagsToString(getClass(), "FLAG_", flags));
+            pw.printPair("userId", userId);
+            pw.printPair("state", DebugUtils.valueToString(getClass(), "STATE_", state));
+            pw.println();
+            pw.printPair("fsType", fsType);
+            pw.printPair("fsUuid", fsUuid);
+            pw.printPair("fsLabel", fsLabel);
+            pw.println();
+            pw.printPair("path", path);
+            pw.printPair("mtpIndex", mtpIndex);
+            pw.decreaseIndent();
+            pw.println();
+        }
+    }
+
     /** List of crypto types.
       * These must match CRYPT_TYPE_XXX in cryptfs.h AND their
       * corresponding commands in CommandListener.cpp */
@@ -231,33 +459,20 @@
     private final Context mContext;
     private final NativeDaemonConnector mConnector;
 
-    private final Object mVolumesLock = new Object();
-
-    /** When defined, base template for user-specific {@link StorageVolume}. */
-    private StorageVolume mEmulatedTemplate;
-
-    // TODO: separate storage volumes on per-user basis
-
-    @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 volatile boolean mDaemonConnected = 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 final CountDownLatch mConnectedSignal = new CountDownLatch(1);
     private final CountDownLatch mAsecsScanned = new CountDownLatch(1);
-    private boolean                               mSendUmsConnectedOnBoot = false;
+
+    private final Object mUnmountLock = new Object();
+    @GuardedBy("mUnmountLock")
+    private CountDownLatch mUnmountSignal;
 
     /**
      * Private hash of currently mounted secure containers.
@@ -366,6 +581,7 @@
     final private DefaultContainerConnection mDefContainerConn = new DefaultContainerConnection();
 
     class DefaultContainerConnection implements ServiceConnection {
+        @Override
         public void onServiceConnected(ComponentName name, IBinder service) {
             if (DEBUG_OBB)
                 Slog.i(TAG, "onServiceConnected");
@@ -373,6 +589,7 @@
             mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(OBB_MCS_BOUND, imcs));
         }
 
+        @Override
         public void onServiceDisconnected(ComponentName name) {
             if (DEBUG_OBB)
                 Slog.i(TAG, "onServiceDisconnected");
@@ -388,177 +605,27 @@
     private long mLastMaintenance;
 
     // Handler messages
-    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 H_FSTRIM = 5;
-
-    private static final int RETRY_UNMOUNT_DELAY = 30; // in ms
-    private static final int MAX_UNMOUNT_RETRIES = 4;
-
-    class UnmountCallBack {
-        final String path;
-        final boolean force;
-        final boolean removeEncryption;
-        int retries;
-
-        UnmountCallBack(String path, boolean force, boolean removeEncryption) {
-            retries = 0;
-            this.path = path;
-            this.force = force;
-            this.removeEncryption = removeEncryption;
-        }
-
-        void handleFinished() {
-            if (DEBUG_UNMOUNT) Slog.i(TAG, "Unmounting " + path);
-            doUnmountVolume(path, true, removeEncryption);
-        }
-    }
-
-    class UmsEnableCallBack extends UnmountCallBack {
-        final String method;
-
-        UmsEnableCallBack(String path, String method, boolean force) {
-            super(path, force, false);
-            this.method = method;
-        }
-
-        @Override
-        void handleFinished() {
-            super.handleFinished();
-            doShareUnshareVolume(path, method, true);
-        }
-    }
-
-    class ShutdownCallBack extends UnmountCallBack {
-        MountShutdownLatch mMountShutdownLatch;
-        ShutdownCallBack(String path, final MountShutdownLatch mountShutdownLatch) {
-            super(path, true, false);
-            mMountShutdownLatch = mountShutdownLatch;
-        }
-
-        @Override
-        void handleFinished() {
-            int ret = doUnmountVolume(path, true, removeEncryption);
-            Slog.i(TAG, "Unmount completed: " + path + ", result code: " + ret);
-            mMountShutdownLatch.countDown();
-        }
-    }
-
-    static class MountShutdownLatch {
-        private IMountShutdownObserver mObserver;
-        private AtomicInteger mCount;
-
-        MountShutdownLatch(final IMountShutdownObserver observer, int count) {
-            mObserver = observer;
-            mCount = new AtomicInteger(count);
-        }
-
-        void countDown() {
-            boolean sendShutdown = false;
-            if (mCount.decrementAndGet() == 0) {
-                sendShutdown = true;
-            }
-            if (sendShutdown && mObserver != null) {
-                try {
-                    mObserver.onShutDownComplete(StorageResultCode.OperationSucceeded);
-                } catch (RemoteException e) {
-                    Slog.w(TAG, "RemoteException when shutting down");
-                }
-            }
-        }
-    }
+    private static final int H_SYSTEM_READY = 1;
+    private static final int H_DAEMON_CONNECTED = 2;
+    private static final int H_SHUTDOWN = 3;
+    private static final int H_FSTRIM = 4;
+    private static final int H_VOLUME_MOUNT = 5;
+    private static final int H_VOLUME_BROADCAST = 6;
 
     class MountServiceHandler extends Handler {
-        ArrayList<UnmountCallBack> mForceUnmounts = new ArrayList<UnmountCallBack>();
-        boolean mUpdatingStatus = false;
-
-        MountServiceHandler(Looper l) {
-            super(l);
+        public MountServiceHandler(Looper looper) {
+            super(looper);
         }
 
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
-                case H_UNMOUNT_PM_UPDATE: {
-                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_UPDATE");
-                    UnmountCallBack ucb = (UnmountCallBack) msg.obj;
-                    mForceUnmounts.add(ucb);
-                    if (DEBUG_UNMOUNT) Slog.i(TAG, " registered = " + mUpdatingStatus);
-                    // Register only if needed.
-                    if (!mUpdatingStatus) {
-                        if (DEBUG_UNMOUNT) Slog.i(TAG, "Updating external media status on PackageManager");
-                        mUpdatingStatus = true;
-                        mPms.updateExternalMediaStatus(false, true);
-                    }
-                    break;
-                }
-                case H_UNMOUNT_PM_DONE: {
-                    if (DEBUG_UNMOUNT) Slog.i(TAG, "H_UNMOUNT_PM_DONE");
-                    if (DEBUG_UNMOUNT) Slog.i(TAG, "Updated status. Processing requests");
-                    mUpdatingStatus = false;
-                    int size = mForceUnmounts.size();
-                    int sizeArr[] = new int[size];
-                    int sizeArrN = 0;
-                    // Kill processes holding references first
-                    ActivityManagerService ams = (ActivityManagerService)
-                    ServiceManager.getService("activity");
-                    for (int i = 0; i < size; i++) {
-                        UnmountCallBack ucb = mForceUnmounts.get(i);
-                        String path = ucb.path;
-                        boolean done = false;
-                        if (!ucb.force) {
-                            done = true;
-                        } else {
-                            int pids[] = getStorageUsers(path);
-                            if (pids == null || pids.length == 0) {
-                                done = true;
-                            } else {
-                                // Eliminate system process here?
-                                ams.killPids(pids, "unmount media", true);
-                                // Confirm if file references have been freed.
-                                pids = getStorageUsers(path);
-                                if (pids == null || pids.length == 0) {
-                                    done = true;
-                                }
-                            }
-                        }
-                        if (!done && (ucb.retries < MAX_UNMOUNT_RETRIES)) {
-                            // Retry again
-                            Slog.i(TAG, "Retrying to kill storage users again");
-                            mHandler.sendMessageDelayed(
-                                    mHandler.obtainMessage(H_UNMOUNT_PM_DONE,
-                                            ucb.retries++),
-                                    RETRY_UNMOUNT_DELAY);
-                        } else {
-                            if (ucb.retries >= MAX_UNMOUNT_RETRIES) {
-                                Slog.i(TAG, "Failed to unmount media inspite of " +
-                                        MAX_UNMOUNT_RETRIES + " retries. Forcibly killing processes now");
-                            }
-                            sizeArr[sizeArrN++] = i;
-                            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_MS,
-                                    ucb));
-                        }
-                    }
-                    // Remove already processed elements from list.
-                    for (int i = (sizeArrN-1); i >= 0; i--) {
-                        mForceUnmounts.remove(sizeArr[i]);
-                    }
-                    break;
-                }
-                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);
-                    }
+                    handleSystemReady();
+                    break;
+                }
+                case H_DAEMON_CONNECTED: {
+                    handleDaemonConnected();
                     break;
                 }
                 case H_FSTRIM: {
@@ -589,29 +656,68 @@
                     }
                     break;
                 }
+                case H_SHUTDOWN: {
+                    final IMountShutdownObserver obs = (IMountShutdownObserver) msg.obj;
+                    boolean success = false;
+                    try {
+                        success = mConnector.execute("volume", "shutdown").isClassOk();
+                    } catch (NativeDaemonConnectorException ignored) {
+                    }
+                    if (obs != null) {
+                        try {
+                            obs.onShutDownComplete(success ? 0 : -1);
+                        } catch (RemoteException ignored) {
+                        }
+                    }
+                    break;
+                }
+                case H_VOLUME_MOUNT: {
+                    final Volume vol = (Volume) msg.obj;
+                    try {
+                        vol.mount();
+                    } catch (NativeDaemonConnectorException ignored) {
+                    }
+                    break;
+                }
+                case H_VOLUME_BROADCAST: {
+                    final StorageVolume userVol = (StorageVolume) msg.obj;
+                    final String state = userVol.getState();
+                    Slog.d(TAG, "Volume " + userVol.getId() + " broadcasting " + state + " to "
+                            + userVol.getOwner());
+
+                    final String action = sEnvironmentToBroadcast.get(state);
+                    if (action != null) {
+                        final Intent intent = new Intent(action,
+                                Uri.fromFile(userVol.getPathFile()));
+                        intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, userVol);
+                        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
+                        mContext.sendBroadcastAsUser(intent, userVol.getOwner());
+                    }
+                    break;
+                }
             }
         }
-    };
+    }
 
     private final Handler mHandler;
 
     @Override
     public void waitForAsecScan() {
-        waitForLatch(mAsecsScanned);
+        waitForLatch(mAsecsScanned, "mAsecsScanned");
     }
 
     private void waitForReady() {
-        waitForLatch(mConnectedSignal);
+        waitForLatch(mConnectedSignal, "mConnectedSignal");
     }
 
-    private void waitForLatch(CountDownLatch latch) {
+    private void waitForLatch(CountDownLatch latch, String condition) {
         for (;;) {
             try {
                 if (latch.await(5000, TimeUnit.MILLISECONDS)) {
                     return;
                 } else {
                     Slog.w(TAG, "Thread " + Thread.currentThread().getName()
-                            + " still waiting for MountService ready...");
+                            + " still waiting for " + condition + "...");
                 }
             } catch (InterruptedException e) {
                 Slog.w(TAG, "Interrupt while waiting for MountService to be ready.");
@@ -628,103 +734,72 @@
     }
 
     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);
-        }
+        resetIfReadyAndConnected();
 
-        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;
-        }
-
-        /*
-         * Start scheduling nominally-daily fstrim operations
-         */
+        // Start scheduling nominally-daily fstrim operations
         MountServiceIdler.scheduleIdlePass(mContext);
     }
 
-    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);
+    private void resetIfReadyAndConnected() {
+        Slog.d(TAG, "Thinking about reset, mSystemReady=" + mSystemReady
+                + ", mDaemonConnected=" + mDaemonConnected);
+        if (mSystemReady && mDaemonConnected) {
+            mDisks.clear();
+            mVolumes.clear();
 
-            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);
-                    }
-                }
+            try {
+                mConnector.execute("volume", "reset");
+            } catch (NativeDaemonConnectorException e) {
+                Slog.w(TAG, "Failed to reset vold", e);
             }
         }
-    };
+    }
 
-    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 void onStartUser(int userId) {
+        Slog.d(TAG, "onStartUser " + userId);
+
+        // We purposefully block here to make sure that user-specific
+        // staging area is ready so it's ready for zygote-forked apps to
+        // bind mount against.
+        try {
+            mConnector.execute("volume", "start_user", userId);
+        } catch (NativeDaemonConnectorException ignored) {
         }
-    };
+
+        // Record user as started so newly mounted volumes kick off events
+        // correctly, then synthesize events for any already-mounted volumes.
+        synchronized (mVolumes) {
+            for (Volume vol : mVolumes.values()) {
+                if (vol.isVisibleToUser(userId) && vol.state == Volume.STATE_MOUNTED) {
+                    final StorageVolume userVol = vol.buildVolumeForUser(userId);
+                    mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
+                }
+            }
+            mStartedUsers = ArrayUtils.appendInt(mStartedUsers, userId);
+        }
+    }
+
+    private void onCleanupUser(int userId) {
+        Slog.d(TAG, "onCleanupUser " + userId);
+
+        try {
+            mConnector.execute("volume", "cleanup_user", userId);
+        } catch (NativeDaemonConnectorException ignored) {
+        }
+
+        synchronized (mVolumes) {
+            mStartedUsers = ArrayUtils.removeInt(mStartedUsers, userId);
+        }
+    }
 
     private final class MountServiceBinderListener implements IBinder.DeathRecipient {
         final IMountServiceListener mListener;
 
         MountServiceBinderListener(IMountServiceListener listener) {
             mListener = listener;
-
         }
 
+        @Override
         public void binderDied() {
             if (LOCAL_LOGD) Slog.d(TAG, "An IMountServiceListener has died!");
             synchronized (mListeners) {
@@ -741,7 +816,7 @@
     // Binder entry point for kicking off an immediate fstrim
     @Override
     public void runMaintenance() {
-        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         runIdleMaintenance(null);
     }
 
@@ -750,144 +825,35 @@
         return mLastMaintenance;
     }
 
-    private void doShareUnshareVolume(String path, String method, boolean enable) {
-        // TODO: Add support for multiple share methods
-        if (!method.equals("ums")) {
-            throw new IllegalArgumentException(String.format("Method %s not supported", method));
-        }
-
-        try {
-            mConnector.execute("volume", enable ? "share" : "unshare", path, method);
-        } catch (NativeDaemonConnectorException e) {
-            Slog.e(TAG, "Failed to share/unshare", e);
-        }
-    }
-
-    private void updatePublicVolumeState(StorageVolume volume, String state) {
-        final String path = volume.getPath();
-        final String oldState;
-        synchronized (mVolumesLock) {
-            oldState = mVolumeStates.put(path, state);
-            volume.setState(state);
-        }
-
-        if (state.equals(oldState)) {
-            Slog.w(TAG, String.format("Duplicate state transition (%s -> %s) for %s",
-                    state, state, path));
-            return;
-        }
-
-        Slog.d(TAG, "volume state changed for " + path + " (" + oldState + " -> " + state + ")");
-
-        // 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);
-            }
-        }
-
-        synchronized (mListeners) {
-            for (int i = mListeners.size() -1; i >= 0; i--) {
-                MountServiceBinderListener bl = mListeners.get(i);
-                try {
-                    bl.mListener.onStorageStateChanged(path, oldState, state);
-                } catch (RemoteException rex) {
-                    Slog.e(TAG, "Listener dead");
-                    mListeners.remove(i);
-                } catch (Exception ex) {
-                    Slog.e(TAG, "Listener failed", ex);
-                }
-            }
-        }
-    }
-
     /**
      * Callback from NativeDaemonConnector
      */
+    @Override
     public void onDaemonConnected() {
+        mDaemonConnected = true;
+        mHandler.obtainMessage(H_DAEMON_CONNECTED).sendToTarget();
+    }
+
+    private void handleDaemonConnected() {
+        resetIfReadyAndConnected();
+
         /*
-         * Since we'll be calling back into the NativeDaemonConnector,
-         * we need to do our work in a new thread.
+         * Now that we've done our initialization, release
+         * the hounds!
          */
-        new Thread("MountService#onDaemonConnected") {
-            @Override
-            public void run() {
-                /**
-                 * Determine media state and UMS detection status
-                 */
-                try {
-                    final String[] vols = NativeDaemonEvent.filterMessageList(
-                            mConnector.executeForList("volume", "list", "broadcast"),
-                            VoldResponseCode.VolumeListResult);
-                    for (String volstr : vols) {
-                        String[] tok = volstr.split(" ");
-                        // FMT: <label> <mountpoint> <state>
-                        String path = tok[1];
-                        String state = Environment.MEDIA_REMOVED;
+        mConnectedSignal.countDown();
 
-                        final StorageVolume volume;
-                        synchronized (mVolumesLock) {
-                            volume = mVolumesByPath.get(path);
-                        }
+        // On an encrypted device we can't see system properties yet, so pull
+        // the system locale out of the mount service.
+        if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
+            copyLocaleFromMountService();
+        }
 
-                        int st = Integer.parseInt(tok[2]);
-                        if (st == VolumeState.NoMedia) {
-                            state = Environment.MEDIA_REMOVED;
-                        } else if (st == VolumeState.Idle) {
-                            state = Environment.MEDIA_UNMOUNTED;
-                        } else if (st == VolumeState.Mounted) {
-                            state = Environment.MEDIA_MOUNTED;
-                            Slog.i(TAG, "Media already mounted on daemon connection");
-                        } else if (st == VolumeState.Shared) {
-                            state = Environment.MEDIA_SHARED;
-                            Slog.i(TAG, "Media shared on daemon connection");
-                        } else {
-                            throw new Exception(String.format("Unexpected state %d", st));
-                        }
+        // Let package manager load internal ASECs.
+        mPms.scanAvailableAsecs();
 
-                        if (state != null) {
-                            if (DEBUG_EVENTS) Slog.i(TAG, "Updating valid state " + state);
-                            updatePublicVolumeState(volume, state);
-                        }
-                    }
-                } catch (Exception e) {
-                    Slog.e(TAG, "Error processing initial volume state", e);
-                    final StorageVolume primary = getPrimaryPhysicalVolume();
-                    if (primary != null) {
-                        updatePublicVolumeState(primary, Environment.MEDIA_REMOVED);
-                    }
-                }
-
-                /*
-                 * Now that we've done our initialization, release
-                 * the hounds!
-                 */
-                mConnectedSignal.countDown();
-
-                // On an encrypted device we can't see system properties yet, so pull
-                // the system locale out of the mount service.
-                if ("".equals(SystemProperties.get("vold.encrypt_progress"))) {
-                    copyLocaleFromMountService();
-                }
-
-                // Let package manager load internal ASECs.
-                mPms.scanAvailableAsecs();
-
-                // Notify people waiting for ASECs to be scanned that it's done.
-                mAsecsScanned.countDown();
-            }
-        }.start();
+        // Notify people waiting for ASECs to be scanned that it's done.
+        mAsecsScanned.countDown();
     }
 
     private void copyLocaleFromMountService() {
@@ -919,6 +885,7 @@
     /**
      * Callback from NativeDaemonConnector
      */
+    @Override
     public boolean onCheckHoldWakeLock(int code) {
         return false;
     }
@@ -926,347 +893,182 @@
     /**
      * Callback from NativeDaemonConnector
      */
+    @Override
     public boolean onEvent(int code, String raw, String[] cooked) {
-        if (DEBUG_EVENTS) {
-            StringBuilder builder = new StringBuilder();
-            builder.append("onEvent::");
-            builder.append(" raw= " + raw);
-            if (cooked != null) {
-                builder.append(" cooked = " );
-                for (String str : cooked) {
-                    builder.append(" " + str);
-                }
-            }
-            Slog.i(TAG, builder.toString());
+        synchronized (mLock) {
+            return onEventLocked(code, raw, cooked);
         }
-        if (code == VoldResponseCode.VolumeStateChange) {
-            /*
-             * One of the volumes we're managing has changed state.
-             * Format: "NNN Volume <label> <path> state changed
-             * from <old_#> (<old_str>) to <new_#> (<new_str>)"
-             */
-            notifyVolumeStateChange(
-                    cooked[2], cooked[3], Integer.parseInt(cooked[7]),
-                            Integer.parseInt(cooked[10]));
-        } else if (code == VoldResponseCode.VolumeUuidChange) {
-            // Format: nnn <label> <path> <uuid>
-            final String path = cooked[2];
-            final String uuid = (cooked.length > 3) ? cooked[3] : null;
+    }
 
-            final StorageVolume vol = mVolumesByPath.get(path);
-            if (vol != null) {
-                vol.setUuid(uuid);
+    private boolean onEventLocked(int code, String raw, String[] cooked) {
+        switch (code) {
+            case VoldResponseCode.DISK_CREATED: {
+                if (cooked.length != 3) break;
+                final String id = cooked[1];
+                final int flags = Integer.parseInt(cooked[2]);
+                mDisks.put(id, new Disk(id, flags));
+                break;
             }
-
-        } else if (code == VoldResponseCode.VolumeUserLabelChange) {
-            // Format: nnn <label> <path> <label>
-            final String path = cooked[2];
-            final String userLabel = (cooked.length > 3) ? cooked[3] : null;
-
-            final StorageVolume vol = mVolumesByPath.get(path);
-            if (vol != null) {
-                vol.setUserLabel(userLabel);
-            }
-
-        } else if ((code == VoldResponseCode.VolumeDiskInserted) ||
-                   (code == VoldResponseCode.VolumeDiskRemoved) ||
-                   (code == VoldResponseCode.VolumeBadRemoval)) {
-            // FMT: NNN Volume <label> <mountpoint> disk inserted (<major>:<minor>)
-            // FMT: NNN Volume <label> <mountpoint> disk removed (<major>:<minor>)
-            // FMT: NNN Volume <label> <mountpoint> bad removal (<major>:<minor>)
-            String action = null;
-            final String label = cooked[2];
-            final String path = cooked[3];
-            int major = -1;
-            int minor = -1;
-
-            try {
-                String devComp = cooked[6].substring(1, cooked[6].length() -1);
-                String[] devTok = devComp.split(":");
-                major = Integer.parseInt(devTok[0]);
-                minor = Integer.parseInt(devTok[1]);
-            } catch (Exception ex) {
-                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("MountService#VolumeDiskInserted") {
-                    @Override
-                    public void run() {
-                        try {
-                            int rc;
-                            if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
-                                Slog.w(TAG, String.format("Insertion mount failed (%d)", rc));
-                            }
-                        } catch (Exception ex) {
-                            Slog.w(TAG, "Failed to mount media on insertion", ex);
-                        }
-                    }
-                }.start();
-            } else if (code == VoldResponseCode.VolumeDiskRemoved) {
-                /*
-                 * This event gets trumped if we're already in BAD_REMOVAL state
-                 */
-                if (getVolumeState(path).equals(Environment.MEDIA_BAD_REMOVAL)) {
-                    return true;
+            case VoldResponseCode.DISK_SIZE_CHANGED: {
+                if (cooked.length != 3) break;
+                final Disk disk = mDisks.get(cooked[1]);
+                if (disk != null) {
+                    disk.size = Long.parseLong(cooked[2]);
                 }
-                /* Send the media unmounted event first */
-                if (DEBUG_EVENTS) Slog.i(TAG, "Sending unmounted event first");
-                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
-                sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
+                break;
+            }
+            case VoldResponseCode.DISK_LABEL_CHANGED: {
+                if (cooked.length != 3) break;
+                final Disk disk = mDisks.get(cooked[1]);
+                if (disk != null) {
+                    disk.label = cooked[2];
+                }
+                break;
+            }
+            case VoldResponseCode.DISK_VOLUME_CREATED: {
+                if (cooked.length != 3) break;
+                final Disk disk = mDisks.get(cooked[1]);
+                final Volume vol = mVolumes.get(cooked[2]);
+                if (disk != null && vol != null) {
+                    disk.volumes.add(vol);
+                }
+                break;
+            }
+            case VoldResponseCode.DISK_DESTROYED: {
+                if (cooked.length != 2) break;
+                mDisks.remove(cooked[1]);
+                break;
+            }
 
-                if (DEBUG_EVENTS) Slog.i(TAG, "Sending 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(volume, Environment.MEDIA_UNMOUNTED);
-                sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
+            case VoldResponseCode.VOLUME_CREATED: {
+                if (cooked.length != 3) break;
+                final String id = cooked[1];
+                final int type = Integer.parseInt(cooked[2]);
+                final Volume vol = new Volume(id, type);
+                mVolumes.put(id, vol);
+                onVolumeCreatedLocked(vol);
+                break;
+            }
+            case VoldResponseCode.VOLUME_STATE_CHANGED: {
+                if (cooked.length != 3) break;
+                final Volume vol = mVolumes.get(cooked[1]);
+                if (vol != null) {
+                    final int oldState = vol.state;
+                    final int newState = Integer.parseInt(cooked[2]);
+                    vol.state = newState;
+                    onVolumeStateChangedLocked(vol, oldState, newState);
+                }
+                break;
+            }
+            case VoldResponseCode.VOLUME_FS_TYPE_CHANGED: {
+                if (cooked.length != 3) break;
+                final Volume vol = mVolumes.get(cooked[1]);
+                if (vol != null) {
+                    vol.fsType = cooked[2];
+                }
+                break;
+            }
+            case VoldResponseCode.VOLUME_FS_UUID_CHANGED: {
+                if (cooked.length != 3) break;
+                final Volume vol = mVolumes.get(cooked[1]);
+                if (vol != null) {
+                    vol.fsUuid = cooked[2];
+                }
+                break;
+            }
+            case VoldResponseCode.VOLUME_FS_LABEL_CHANGED: {
+                if (cooked.length != 3) break;
+                final Volume vol = mVolumes.get(cooked[1]);
+                if (vol != null) {
+                    vol.fsLabel = cooked[2];
+                }
+                break;
+            }
+            case VoldResponseCode.VOLUME_PATH_CHANGED: {
+                if (cooked.length != 3) break;
+                final Volume vol = mVolumes.get(cooked[1]);
+                if (vol != null) {
+                    vol.path = cooked[2];
+                }
+                break;
+            }
+            case VoldResponseCode.VOLUME_DESTROYED: {
+                if (cooked.length != 2) break;
+                mVolumes.remove(cooked[1]);
+                break;
+            }
 
-                if (DEBUG_EVENTS) Slog.i(TAG, "Sending media bad removal");
-                updatePublicVolumeState(volume, Environment.MEDIA_BAD_REMOVAL);
-                action = Intent.ACTION_MEDIA_BAD_REMOVAL;
-            } else if (code == VoldResponseCode.FstrimCompleted) {
+            case VoldResponseCode.FstrimCompleted: {
                 EventLogTags.writeFstrimFinish(SystemClock.elapsedRealtime());
-            } else {
-                Slog.e(TAG, String.format("Unknown code {%d}", code));
+                break;
             }
-
-            if (action != null) {
-                sendStorageIntent(action, volume, UserHandle.ALL);
+            default: {
+                Slog.d(TAG, "Unhandled vold event " + code);
             }
-        } else {
-            return false;
         }
 
         return true;
     }
 
-    private void notifyVolumeStateChange(String label, String path, int oldState, int newState) {
-        final StorageVolume volume;
-        final String state;
-        synchronized (mVolumesLock) {
-            volume = mVolumesByPath.get(path);
-            state = getVolumeState(path);
-        }
+    private void onVolumeCreatedLocked(Volume vol) {
+        final boolean primaryPhysical = SystemProperties.getBoolean(PROP_PRIMARY_PHYSICAL, false);
+        if (vol.type == Volume.TYPE_EMULATED && !primaryPhysical) {
+            vol.flags |= Volume.FLAG_PRIMARY;
+            vol.flags |= Volume.FLAG_VISIBLE;
+            mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
 
-        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, volume, UserHandle.ALL);
-        }
-
-        if (newState == VolumeState.Init) {
-        } else if (newState == VolumeState.NoMedia) {
-            // NoMedia is handled via Disk Remove events
-        } else if (newState == VolumeState.Idle) {
-            /*
-             * Don't notify if we're in BAD_REMOVAL, NOFS, UNMOUNTABLE, or
-             * if we're in the process of enabling UMS
-             */
-            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(volume, Environment.MEDIA_UNMOUNTED);
-                action = Intent.ACTION_MEDIA_UNMOUNTED;
+        } else if (vol.type == Volume.TYPE_PUBLIC) {
+            if (primaryPhysical) {
+                vol.flags |= Volume.FLAG_PRIMARY;
             }
-        } else if (newState == VolumeState.Pending) {
-        } else if (newState == VolumeState.Checking) {
-            if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state 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(volume, Environment.MEDIA_MOUNTED);
-            action = Intent.ACTION_MEDIA_MOUNTED;
-        } else if (newState == VolumeState.Unmounting) {
-            action = Intent.ACTION_MEDIA_EJECT;
-        } else if (newState == VolumeState.Formatting) {
-        } else if (newState == VolumeState.Shared) {
-            if (DEBUG_EVENTS) Slog.i(TAG, "Updating volume state media mounted");
-            /* Send the media unmounted event first */
-            updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTED);
-            sendStorageIntent(Intent.ACTION_MEDIA_UNMOUNTED, volume, UserHandle.ALL);
+            vol.flags |= Volume.FLAG_VISIBLE;
+            vol.userId = UserHandle.USER_OWNER;
+            mHandler.obtainMessage(H_VOLUME_MOUNT, vol).sendToTarget();
 
-            if (DEBUG_EVENTS) Slog.i(TAG, "Updating 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) {
-            Slog.e(TAG, "Live shared mounts not supported yet!");
-            return;
         } else {
-            Slog.e(TAG, "Unhandled VolumeState {" + newState + "}");
-        }
-
-        if (action != null) {
-            sendStorageIntent(action, volume, UserHandle.ALL);
+            Slog.d(TAG, "Skipping automatic mounting of " + vol);
         }
     }
 
-    private int doMountVolume(String path) {
-        int rc = StorageResultCode.OperationSucceeded;
-
-        final StorageVolume volume;
-        synchronized (mVolumesLock) {
-            volume = mVolumesByPath.get(path);
+    private void onVolumeStateChangedLocked(Volume vol, int oldState, int newState) {
+        // Kick state changed event towards all started users. Any users
+        // started after this point will trigger additional
+        // user-specific broadcasts.
+        for (int userId : mStartedUsers) {
+            if (vol.isVisibleToUser(userId)) {
+                final StorageVolume userVol = vol.buildVolumeForUser(userId);
+                mHandler.obtainMessage(H_VOLUME_BROADCAST, userVol).sendToTarget();
+            }
         }
 
-        if (!volume.isEmulated() && hasUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA)) {
-            Slog.w(TAG, "User has restriction DISALLOW_MOUNT_PHYSICAL_MEDIA; cannot mount volume.");
-            return StorageResultCode.OperationFailedInternalError;
-        }
+        // Tell PackageManager about changes to primary volume state, but only
+        // when not emulated.
+        if (vol.isPrimary() && vol.type == Volume.TYPE_PUBLIC) {
+            if (vol.state == Volume.STATE_MOUNTED) {
+                mPms.updateExternalMediaStatus(true, false);
 
-        if (DEBUG_EVENTS) Slog.i(TAG, "doMountVolume: Mouting " + path);
-        try {
-            mConnector.execute("volume", "mount", path);
-        } catch (NativeDaemonConnectorException e) {
-            /*
-             * Mount failed for some reason
-             */
-            String action = null;
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedNoMedia) {
+            } else if (vol.state == Volume.STATE_UNMOUNTING) {
+                mPms.updateExternalMediaStatus(false, false);
+
+                // TODO: this should eventually be handled by new ObbVolume state changes
                 /*
-                 * Attempt to mount but no media inserted
+                 * 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.
                  */
-                rc = StorageResultCode.OperationFailedNoMedia;
-            } else if (code == VoldResponseCode.OpFailedMediaBlank) {
-                if (DEBUG_EVENTS) Slog.i(TAG, " updating volume state :: media nofs");
-                /*
-                 * Media is blank or does not contain a supported filesystem
-                 */
-                updatePublicVolumeState(volume, Environment.MEDIA_NOFS);
-                action = Intent.ACTION_MEDIA_NOFS;
-                rc = StorageResultCode.OperationFailedMediaBlank;
-            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
-                if (DEBUG_EVENTS) Slog.i(TAG, "updating volume state media corrupt");
-                /*
-                 * Volume consistency check failed
-                 */
-                updatePublicVolumeState(volume, Environment.MEDIA_UNMOUNTABLE);
-                action = Intent.ACTION_MEDIA_UNMOUNTABLE;
-                rc = StorageResultCode.OperationFailedMediaCorrupt;
-            } else {
-                rc = StorageResultCode.OperationFailedInternalError;
-            }
-
-            /*
-             * Send broadcast intent (if required for the failure)
-             */
-            if (action != null) {
-                sendStorageIntent(action, volume, UserHandle.ALL);
+                mObbActionHandler.sendMessage(mObbActionHandler.obtainMessage(
+                        OBB_FLUSH_MOUNT_STATE, vol.path));
             }
         }
 
-        return rc;
-    }
+        final String oldEnvState = sStateToEnvironment.get(oldState);
+        final String newEnvState = sStateToEnvironment.get(newState);
 
-    /*
-     * If force is not set, we do not unmount if there are
-     * processes holding references to the volume about to be unmounted.
-     * If force is set, all the processes holding references need to be
-     * killed via the ActivityManager before actually unmounting the volume.
-     * This might even take a while and might be retried after timed delays
-     * to make sure we dont end up in an instable state and kill some core
-     * processes.
-     * If removeEncryption is set, force is implied, and the system will remove any encryption
-     * mapping set on the volume when unmounting.
-     */
-    private int doUnmountVolume(String path, boolean force, boolean removeEncryption) {
-        if (!getVolumeState(path).equals(Environment.MEDIA_MOUNTED)) {
-            return VoldResponseCode.OpFailedVolNotMounted;
-        }
-
-        /*
-         * Force a GC to make sure AssetManagers in other threads of the
-         * system_server are cleaned up. We have to do this since AssetManager
-         * instances are kept as a WeakReference and it's possible we have files
-         * open on the external storage.
-         */
-        Runtime.getRuntime().gc();
-
-        // Redundant probably. But no harm in updating state again.
-        mPms.updateExternalMediaStatus(false, false);
-        try {
-            final Command cmd = new Command("volume", "unmount", path);
-            if (removeEncryption) {
-                cmd.appendArg("force_and_revert");
-            } else if (force) {
-                cmd.appendArg("force");
-            }
-            mConnector.execute(cmd);
-            // We unmounted the volume. None of the asec containers are available now.
-            synchronized (mAsecMountSet) {
-                mAsecMountSet.clear();
-            }
-            return StorageResultCode.OperationSucceeded;
-        } catch (NativeDaemonConnectorException e) {
-            // Don't worry about mismatch in PackageManager since the
-            // call back will handle the status changes any way.
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedVolNotMounted) {
-                return StorageResultCode.OperationFailedStorageNotMounted;
-            } else if (code == VoldResponseCode.OpFailedStorageBusy) {
-                return StorageResultCode.OperationFailedStorageBusy;
-            } else {
-                return StorageResultCode.OperationFailedInternalError;
-            }
-        }
-    }
-
-    private int doFormatVolume(String path) {
-        try {
-            mConnector.execute("volume", "format", path);
-            return StorageResultCode.OperationSucceeded;
-        } catch (NativeDaemonConnectorException e) {
-            int code = e.getCode();
-            if (code == VoldResponseCode.OpFailedNoMedia) {
-                return StorageResultCode.OperationFailedNoMedia;
-            } else if (code == VoldResponseCode.OpFailedMediaCorrupt) {
-                return StorageResultCode.OperationFailedMediaCorrupt;
-            } else {
-                return StorageResultCode.OperationFailedInternalError;
-            }
-        }
-    }
-
-    private boolean doGetVolumeShared(String path, String method) {
-        final NativeDaemonEvent event;
-        try {
-            event = mConnector.execute("volume", "shared", path, method);
-        } catch (NativeDaemonConnectorException ex) {
-            Slog.e(TAG, "Failed to read response to volume shared " + path + " " + method);
-            return false;
-        }
-
-        if (event.getCode() == VoldResponseCode.ShareEnabledResult) {
-            return event.getMessage().endsWith("enabled");
-        } else {
-            return false;
-        }
-    }
-
-    private void notifyShareAvailabilityChange(final boolean avail) {
         synchronized (mListeners) {
-            mUmsAvailable = avail;
             for (int i = mListeners.size() -1; i >= 0; i--) {
                 MountServiceBinderListener bl = mListeners.get(i);
                 try {
-                    bl.mListener.onUsbMassStorageConnectionChanged(avail);
+                    bl.mListener.onStorageStateChanged(vol.path, oldEnvState, newEnvState);
                 } catch (RemoteException rex) {
                     Slog.e(TAG, "Listener dead");
                     mListeners.remove(i);
@@ -1275,221 +1077,19 @@
                 }
             }
         }
-
-        if (mSystemReady == true) {
-            sendUmsIntent(avail);
-        } else {
-            mSendUmsConnectedOnBoot = avail;
-        }
-
-        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
-             */
-            new Thread("MountService#AvailabilityChange") {
-                @Override
-                public void run() {
-                    try {
-                        int rc;
-                        Slog.w(TAG, "Disabling UMS after cable disconnect");
-                        doShareUnshareVolume(path, "ums", false);
-                        if ((rc = doMountVolume(path)) != StorageResultCode.OperationSucceeded) {
-                            Slog.e(TAG, String.format(
-                                    "Failed to remount {%s} on UMS enabled-disconnect (%d)",
-                                            path, rc));
-                        }
-                    } catch (Exception ex) {
-                        Slog.w(TAG, "Failed to mount media on UMS enabled-disconnect", ex);
-                    }
-                }
-            }.start();
-        }
     }
 
-    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);
-        intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT);
-        Slog.d(TAG, "sendStorageIntent " + intent + " to " + user);
-        mContext.sendBroadcastAsUser(intent, user);
+    private void enforcePermission(String perm) {
+        mContext.enforceCallingOrSelfPermission(perm, perm);
     }
 
-    private void sendUmsIntent(boolean c) {
-        mContext.sendBroadcastAsUser(
-                new Intent((c ? Intent.ACTION_UMS_CONNECTED : Intent.ACTION_UMS_DISCONNECTED)),
-                UserHandle.ALL);
-    }
-
-    private void validatePermission(String perm) {
-        if (mContext.checkCallingOrSelfPermission(perm) != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException(String.format("Requires %s permission", perm));
-        }
-    }
-
-    private boolean hasUserRestriction(String restriction) {
+    private void enforceUserRestriction(String restriction) {
         UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE);
-        return um.hasUserRestriction(restriction, Binder.getCallingUserHandle());
-    }
-
-    private void validateUserRestriction(String restriction) {
-        if (hasUserRestriction(restriction)) {
+        if (um.hasUserRestriction(restriction, Binder.getCallingUserHandle())) {
             throw new SecurityException("User has restriction " + restriction);
         }
     }
 
-    // Storage list XML tags
-    private static final String TAG_STORAGE_LIST = "StorageList";
-    private static final String TAG_STORAGE = "storage";
-
-    private void readStorageListLocked() {
-        mVolumes.clear();
-        mVolumeStates.clear();
-
-        Resources resources = mContext.getResources();
-
-        int id = com.android.internal.R.xml.storage_list;
-        XmlResourceParser parser = resources.getXml(id);
-        AttributeSet attrs = Xml.asAttributeSet(parser);
-
-        try {
-            XmlUtils.beginDocument(parser, TAG_STORAGE_LIST);
-            while (true) {
-                XmlUtils.nextElement(parser);
-
-                String element = parser.getName();
-                if (element == null) break;
-
-                if (TAG_STORAGE.equals(element)) {
-                    TypedArray a = resources.obtainAttributes(attrs,
-                            com.android.internal.R.styleable.Storage);
-
-                    String path = a.getString(
-                            com.android.internal.R.styleable.Storage_mountPoint);
-                    int descriptionId = a.getResourceId(
-                            com.android.internal.R.styleable.Storage_storageDescription, -1);
-                    CharSequence description = a.getText(
-                            com.android.internal.R.styleable.Storage_storageDescription);
-                    boolean primary = a.getBoolean(
-                            com.android.internal.R.styleable.Storage_primary, false);
-                    boolean removable = a.getBoolean(
-                            com.android.internal.R.styleable.Storage_removable, false);
-                    boolean emulated = a.getBoolean(
-                            com.android.internal.R.styleable.Storage_emulated, false);
-                    int mtpReserve = a.getInt(
-                            com.android.internal.R.styleable.Storage_mtpReserve, 0);
-                    boolean allowMassStorage = a.getBoolean(
-                            com.android.internal.R.styleable.Storage_allowMassStorage, false);
-                    // resource parser does not support longs, so XML value is in megabytes
-                    long maxFileSize = a.getInt(
-                            com.android.internal.R.styleable.Storage_maxFileSize, 0) * 1024L * 1024L;
-
-                    Slog.d(TAG, "got storage path: " + path + " description: " + description +
-                            " primary: " + primary + " removable: " + removable +
-                            " emulated: " + emulated +  " mtpReserve: " + mtpReserve +
-                            " allowMassStorage: " + allowMassStorage +
-                            " maxFileSize: " + maxFileSize);
-
-                    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(false)) {
-                            createEmulatedVolumeForUserLocked(user.getUserHandle());
-                        }
-
-                    } else {
-                        if (path == null || description == null) {
-                            Slog.e(TAG, "Missing storage path or description in readStorageList");
-                        } else {
-                            final StorageVolume volume = new StorageVolume(new File(path),
-                                    descriptionId, primary, removable, emulated, mtpReserve,
-                                    allowMassStorage, maxFileSize, null);
-                            addVolumeLocked(volume);
-
-                            // Until we hear otherwise, treat as unmounted
-                            mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED);
-                            volume.setState(Environment.MEDIA_UNMOUNTED);
-                        }
-                    }
-
-                    a.recycle();
-                }
-            }
-        } catch (XmlPullParserException e) {
-            throw new RuntimeException(e);
-        } catch (IOException e) {
-            throw new RuntimeException(e);
-        } finally {
-            // 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);
-            volume.setState(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
      *
@@ -1500,10 +1100,6 @@
 
         mContext = context;
 
-        synchronized (mVolumesLock) {
-            readStorageListLocked();
-        }
-
         // XXX: This will go away soon in favor of IMountServiceObserver
         mPms = (PackageManagerService) ServiceManager.getService("package");
 
@@ -1511,19 +1107,6 @@
         hthread.start();
         mHandler = new MountServiceHandler(hthread.getLooper());
 
-        // 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(IoThread.get().getLooper());
 
@@ -1550,6 +1133,7 @@
          */
         mConnector = new NativeDaemonConnector(this, "vold", MAX_CONTAINERS * 2, VOLD_TAG, 25,
                 null);
+        mConnector.setDebug(true);
 
         Thread thread = new Thread(mConnector, VOLD_TAG);
         thread.start();
@@ -1593,201 +1177,118 @@
         }
     }
 
+    @Override
     public void shutdown(final IMountShutdownObserver observer) {
-        validatePermission(android.Manifest.permission.SHUTDOWN);
+        enforcePermission(android.Manifest.permission.SHUTDOWN);
 
         Slog.i(TAG, "Shutting down");
-        synchronized (mVolumesLock) {
-            // Get all volumes to be unmounted.
-            MountShutdownLatch mountShutdownLatch = new MountShutdownLatch(observer,
-                                                            mVolumeStates.size());
-
-            for (String path : mVolumeStates.keySet()) {
-                String state = mVolumeStates.get(path);
-
-                if (state.equals(Environment.MEDIA_SHARED)) {
-                    /*
-                     * If the media is currently shared, unshare it.
-                     * XXX: This is still dangerous!. We should not
-                     * be rebooting at *all* if UMS is enabled, since
-                     * the UMS host could have dirty FAT cache entries
-                     * yet to flush.
-                     */
-                    setUsbMassStorageEnabled(false);
-                } else if (state.equals(Environment.MEDIA_CHECKING)) {
-                    /*
-                     * If the media is being checked, then we need to wait for
-                     * it to complete before being able to proceed.
-                     */
-                    // XXX: @hackbod - Should we disable the ANR timer here?
-                    int retries = 30;
-                    while (state.equals(Environment.MEDIA_CHECKING) && (retries-- >=0)) {
-                        try {
-                            Thread.sleep(1000);
-                        } catch (InterruptedException iex) {
-                            Slog.e(TAG, "Interrupted while waiting for media", iex);
-                            break;
-                        }
-                        state = Environment.getExternalStorageState();
-                    }
-                    if (retries == 0) {
-                        Slog.e(TAG, "Timed out waiting for media to check");
-                    }
-                }
-
-                if (state.equals(Environment.MEDIA_MOUNTED)) {
-                    // Post a unmount message.
-                    ShutdownCallBack ucb = new ShutdownCallBack(path, mountShutdownLatch);
-                    mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
-                } else if (observer != null) {
-                    /*
-                     * Count down, since nothing will be done. The observer will be
-                     * notified when we are done so shutdown sequence can continue.
-                     */
-                    mountShutdownLatch.countDown();
-                    Slog.i(TAG, "Unmount completed: " + path +
-                        ", result code: " + StorageResultCode.OperationSucceeded);
-                }
-            }
-        }
+        mHandler.obtainMessage(H_SHUTDOWN, observer).sendToTarget();
     }
 
-    private boolean getUmsEnabling() {
-        synchronized (mListeners) {
-            return mUmsEnabling;
-        }
-    }
-
-    private void setUmsEnabling(boolean enable) {
-        synchronized (mListeners) {
-            mUmsEnabling = enable;
-        }
-    }
-
+    @Override
+    @Deprecated
     public boolean isUsbMassStorageConnected() {
-        waitForReady();
-
-        if (getUmsEnabling()) {
-            return true;
-        }
-        synchronized (mListeners) {
-            return mUmsAvailable;
-        }
+        return false;
     }
 
+    @Override
+    @Deprecated
     public void setUsbMassStorageEnabled(boolean enable) {
-        waitForReady();
-        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
-        validateUserRestriction(UserManager.DISALLOW_USB_FILE_TRANSFER);
-
-        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 = primary.getPath();
-        String vs = getVolumeState(path);
-        String method = "ums";
-        if (enable && vs.equals(Environment.MEDIA_MOUNTED)) {
-            // Override for isUsbMassStorageEnabled()
-            setUmsEnabling(enable);
-            UmsEnableCallBack umscb = new UmsEnableCallBack(path, method, true);
-            mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, umscb));
-            // Clear override
-            setUmsEnabling(false);
-        }
-        /*
-         * If we disabled UMS then mount the volume
-         */
-        if (!enable) {
-            doShareUnshareVolume(path, method, enable);
-            if (doMountVolume(path) != StorageResultCode.OperationSucceeded) {
-                Slog.e(TAG, "Failed to remount " + path +
-                        " after disabling share method " + method);
-                /*
-                 * Even though the mount failed, the unshare didn't so don't indicate an error.
-                 * The mountVolume() call will have set the storage state and sent the necessary
-                 * broadcasts.
-                 */
-            }
-        }
+        throw new UnsupportedOperationException();
     }
 
+    @Override
+    @Deprecated
     public boolean isUsbMassStorageEnabled() {
-        waitForReady();
-
-        final StorageVolume primary = getPrimaryPhysicalVolume();
-        if (primary != null) {
-            return doGetVolumeShared(primary.getPath(), "ums");
-        } else {
-            return false;
-        }
+        return false;
     }
 
     /**
      * @return state of the volume at the specified mount point
      */
+    @Override
+    @Deprecated
     public String getVolumeState(String mountPoint) {
-        synchronized (mVolumesLock) {
-            String state = mVolumeStates.get(mountPoint);
-            if (state == null) {
-                Slog.w(TAG, "getVolumeState(" + mountPoint + "): Unknown volume");
-                if (SystemProperties.get("vold.encrypt_progress").length() != 0) {
-                    state = Environment.MEDIA_REMOVED;
-                } else {
-                    throw new IllegalArgumentException();
-                }
-            }
+        // TODO: pretend that we're unmounted when encrypting?
+        // SystemProperties.get("vold.encrypt_progress")
 
-            return state;
-        }
+        final Volume vol = findVolumeByLegacyPath(mountPoint);
+        return sStateToEnvironment.get(vol.state);
     }
 
     @Override
     public boolean isExternalStorageEmulated() {
-        return mEmulatedTemplate != null;
+        return Environment.isExternalStorageEmulated();
     }
 
+    @Override
     public int mountVolume(String path) {
-        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
-        return doMountVolume(path);
+
+        final Volume vol = findVolumeByLegacyPath(path);
+        if (vol != null) {
+            if (vol.type == Volume.TYPE_PUBLIC || vol.type == Volume.TYPE_PRIVATE) {
+                enforceUserRestriction(UserManager.DISALLOW_MOUNT_PHYSICAL_MEDIA);
+            }
+            try {
+                vol.mount();
+                return 0;
+            } catch (NativeDaemonConnectorException ignored) {
+            }
+        } else {
+            Slog.w(TAG, "Unknown volume for path " + path);
+        }
+        return -1;
     }
 
+    @Override
     public void unmountVolume(String path, boolean force, boolean removeEncryption) {
-        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
 
-        String volState = getVolumeState(path);
-        if (DEBUG_UNMOUNT) {
-            Slog.i(TAG, "Unmounting " + path
-                    + " force = " + force
-                    + " removeEncryption = " + removeEncryption);
+        final Volume vol = findVolumeByLegacyPath(path);
+        if (vol != null) {
+            // TODO: expand PMS to know about multiple volumes
+            if (vol.isPrimary()) {
+                synchronized (mUnmountLock) {
+                    mUnmountSignal = new CountDownLatch(1);
+                    mPms.updateExternalMediaStatus(false, true);
+                    waitForLatch(mUnmountSignal, "mUnmountSignal");
+                    mUnmountSignal = null;
+                }
+            }
+
+            try {
+                vol.unmount();
+            } catch (NativeDaemonConnectorException ignored) {
+            }
+        } else {
+            Slog.w(TAG, "Unknown volume for path " + path);
         }
-        if (Environment.MEDIA_UNMOUNTED.equals(volState) ||
-                Environment.MEDIA_REMOVED.equals(volState) ||
-                Environment.MEDIA_SHARED.equals(volState) ||
-                Environment.MEDIA_UNMOUNTABLE.equals(volState)) {
-            // Media already unmounted or cannot be unmounted.
-            // TODO return valid return code when adding observer call back.
-            return;
-        }
-        UnmountCallBack ucb = new UnmountCallBack(path, force, removeEncryption);
-        mHandler.sendMessage(mHandler.obtainMessage(H_UNMOUNT_PM_UPDATE, ucb));
     }
 
+    @Override
     public int formatVolume(String path) {
-        validatePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
+        enforcePermission(android.Manifest.permission.MOUNT_FORMAT_FILESYSTEMS);
         waitForReady();
 
-        return doFormatVolume(path);
+        final Volume vol = findVolumeByLegacyPath(path);
+        if (vol != null) {
+            try {
+                vol.format();
+                return 0;
+            } catch (NativeDaemonConnectorException ignored) {
+            }
+        } else {
+            Slog.w(TAG, "Unknown volume for path " + path);
+        }
+        return -1;
     }
 
+    @Override
     public int[] getStorageUsers(String path) {
-        validatePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
+        enforcePermission(android.Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS);
         waitForReady();
         try {
             final String[] r = NativeDaemonEvent.filterMessageList(
@@ -1813,22 +1314,20 @@
     }
 
     private void warnOnNotMounted() {
-        final StorageVolume primary = getPrimaryPhysicalVolume();
-        if (primary != null) {
-            boolean mounted = false;
-            try {
-                mounted = Environment.MEDIA_MOUNTED.equals(getVolumeState(primary.getPath()));
-            } catch (IllegalArgumentException e) {
-            }
-
-            if (!mounted) {
-                Slog.w(TAG, "getSecureContainerList() called when storage not mounted");
+        synchronized (mLock) {
+            for (Volume vol : mVolumes.values()) {
+                if (vol.isPrimary() && vol.state == Volume.STATE_MOUNTED) {
+                    // Cool beans, we have a mounted primary volume
+                    return;
+                }
             }
         }
+
+        Slog.w(TAG, "No primary storage mounted!");
     }
 
     public String[] getSecureContainerList() {
-        validatePermission(android.Manifest.permission.ASEC_ACCESS);
+        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
         waitForReady();
         warnOnNotMounted();
 
@@ -1842,7 +1341,7 @@
 
     public int createSecureContainer(String id, int sizeMb, String fstype, String key,
             int ownerUid, boolean external) {
-        validatePermission(android.Manifest.permission.ASEC_CREATE);
+        enforcePermission(android.Manifest.permission.ASEC_CREATE);
         waitForReady();
         warnOnNotMounted();
 
@@ -1864,7 +1363,7 @@
 
     @Override
     public int resizeSecureContainer(String id, int sizeMb, String key) {
-        validatePermission(android.Manifest.permission.ASEC_CREATE);
+        enforcePermission(android.Manifest.permission.ASEC_CREATE);
         waitForReady();
         warnOnNotMounted();
 
@@ -1878,7 +1377,7 @@
     }
 
     public int finalizeSecureContainer(String id) {
-        validatePermission(android.Manifest.permission.ASEC_CREATE);
+        enforcePermission(android.Manifest.permission.ASEC_CREATE);
         warnOnNotMounted();
 
         int rc = StorageResultCode.OperationSucceeded;
@@ -1895,7 +1394,7 @@
     }
 
     public int fixPermissionsSecureContainer(String id, int gid, String filename) {
-        validatePermission(android.Manifest.permission.ASEC_CREATE);
+        enforcePermission(android.Manifest.permission.ASEC_CREATE);
         warnOnNotMounted();
 
         int rc = StorageResultCode.OperationSucceeded;
@@ -1912,7 +1411,7 @@
     }
 
     public int destroySecureContainer(String id, boolean force) {
-        validatePermission(android.Manifest.permission.ASEC_DESTROY);
+        enforcePermission(android.Manifest.permission.ASEC_DESTROY);
         waitForReady();
         warnOnNotMounted();
 
@@ -1952,7 +1451,7 @@
     }
 
     public int mountSecureContainer(String id, String key, int ownerUid, boolean readOnly) {
-        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+        enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
         waitForReady();
         warnOnNotMounted();
 
@@ -1982,7 +1481,7 @@
     }
 
     public int unmountSecureContainer(String id, boolean force) {
-        validatePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
+        enforcePermission(android.Manifest.permission.ASEC_MOUNT_UNMOUNT);
         waitForReady();
         warnOnNotMounted();
 
@@ -2025,7 +1524,7 @@
     }
 
     public boolean isSecureContainerMounted(String id) {
-        validatePermission(android.Manifest.permission.ASEC_ACCESS);
+        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
         waitForReady();
         warnOnNotMounted();
 
@@ -2035,7 +1534,7 @@
     }
 
     public int renameSecureContainer(String oldId, String newId) {
-        validatePermission(android.Manifest.permission.ASEC_RENAME);
+        enforcePermission(android.Manifest.permission.ASEC_RENAME);
         waitForReady();
         warnOnNotMounted();
 
@@ -2060,7 +1559,7 @@
     }
 
     public String getSecureContainerPath(String id) {
-        validatePermission(android.Manifest.permission.ASEC_ACCESS);
+        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
         waitForReady();
         warnOnNotMounted();
 
@@ -2081,7 +1580,7 @@
     }
 
     public String getSecureContainerFilesystemPath(String id) {
-        validatePermission(android.Manifest.permission.ASEC_ACCESS);
+        enforcePermission(android.Manifest.permission.ASEC_ACCESS);
         waitForReady();
         warnOnNotMounted();
 
@@ -2101,8 +1600,13 @@
         }
     }
 
+    @Override
     public void finishMediaUpdate() {
-        mHandler.sendEmptyMessage(H_UNMOUNT_PM_DONE);
+        if (mUnmountSignal != null) {
+            mUnmountSignal.countDown();
+        } else {
+            Slog.w(TAG, "Odd, nobody asked to unmount?");
+        }
     }
 
     private boolean isUidOwnerOfPackageOrSystem(String packageName, int callerUid) {
@@ -2477,109 +1981,84 @@
                 Context.APP_OPS_SERVICE);
         appOps.checkPackage(Binder.getCallingUid(), callingPkg);
 
+        File appFile = null;
         try {
-            appPath = new File(appPath).getCanonicalPath();
+            appFile = new File(appPath).getCanonicalFile();
         } catch (IOException e) {
             Slog.e(TAG, "Failed to resolve " + appPath + ": " + e);
             return -1;
         }
 
-        if (!appPath.endsWith("/")) {
-            appPath = appPath + "/";
-        }
-
         // Try translating the app path into a vold path, but require that it
         // belong to the calling package.
-        String voldPath = maybeTranslatePathForVold(appPath,
-                userEnv.buildExternalStorageAppDataDirs(callingPkg),
-                userEnv.buildExternalStorageAppDataDirsForVold(callingPkg));
-        if (voldPath != null) {
+        if (FileUtils.contains(userEnv.buildExternalStorageAppDataDirs(callingPkg), appFile) ||
+                FileUtils.contains(userEnv.buildExternalStorageAppObbDirs(callingPkg), appFile) ||
+                FileUtils.contains(userEnv.buildExternalStorageAppMediaDirs(callingPkg), appFile)) {
+            appPath = appFile.getAbsolutePath();
+            if (!appPath.endsWith("/")) {
+                appPath = appPath + "/";
+            }
+
             try {
-                mConnector.execute("volume", "mkdirs", voldPath);
+                mConnector.execute("volume", "mkdirs", appPath);
                 return 0;
             } catch (NativeDaemonConnectorException e) {
                 return e.getCode();
             }
         }
 
-        voldPath = maybeTranslatePathForVold(appPath,
-                userEnv.buildExternalStorageAppObbDirs(callingPkg),
-                userEnv.buildExternalStorageAppObbDirsForVold(callingPkg));
-        if (voldPath != null) {
-            try {
-                mConnector.execute("volume", "mkdirs", voldPath);
-                return 0;
-            } catch (NativeDaemonConnectorException e) {
-                return e.getCode();
-            }
-        }
-
-        voldPath = maybeTranslatePathForVold(appPath,
-                userEnv.buildExternalStorageAppMediaDirs(callingPkg),
-                userEnv.buildExternalStorageAppMediaDirsForVold(callingPkg));
-        if (voldPath != null) {
-            try {
-                mConnector.execute("volume", "mkdirs", voldPath);
-                return 0;
-            } catch (NativeDaemonConnectorException e) {
-                return e.getCode();
-            }
-        }
-
-        throw new SecurityException("Invalid mkdirs path: " + appPath);
-    }
-
-    /**
-     * Translate the given path from an app-visible path to a vold-visible path,
-     * but only if it's under the given whitelisted paths.
-     *
-     * @param path a canonicalized app-visible path.
-     * @param appPaths list of app-visible paths that are allowed.
-     * @param voldPaths list of vold-visible paths directly corresponding to the
-     *            allowed app-visible paths argument.
-     * @return a vold-visible path representing the original path, or
-     *         {@code null} if the given path didn't have an app-to-vold
-     *         mapping.
-     */
-    @VisibleForTesting
-    public static String maybeTranslatePathForVold(
-            String path, File[] appPaths, File[] voldPaths) {
-        if (appPaths.length != voldPaths.length) {
-            throw new IllegalStateException("Paths must be 1:1 mapping");
-        }
-
-        for (int i = 0; i < appPaths.length; i++) {
-            final String appPath = appPaths[i].getAbsolutePath() + "/";
-            if (path.startsWith(appPath)) {
-                path = new File(voldPaths[i], path.substring(appPath.length()))
-                        .getAbsolutePath();
-                if (!path.endsWith("/")) {
-                    path = path + "/";
-                }
-                return path;
-            }
-        }
-        return null;
+        throw new SecurityException("Invalid mkdirs path: " + appFile);
     }
 
     @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);
+    public StorageVolume[] getVolumeList(int userId) {
+        if (UserHandle.getCallingUserId() != userId) {
+            mContext.enforceCallingOrSelfPermission(
+                    android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE, "getVolumeList");
+        }
 
-        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);
+        final ArrayList<StorageVolume> res = Lists.newArrayList();
+        boolean foundPrimary = false;
+        synchronized (mLock) {
+            for (Volume vol : mVolumes.values()) {
+                if (vol.isVisibleToUser(userId)) {
+                    final StorageVolume userVol = vol.buildVolumeForUser(userId);
+                    if (vol.isPrimary()) {
+                        res.add(0, userVol);
+                        foundPrimary = true;
+                    } else {
+                        res.add(userVol);
+                    }
                 }
             }
-            return filtered.toArray(new StorageVolume[filtered.size()]);
         }
+
+        if (!foundPrimary) {
+            Slog.w(TAG, "No primary storage defined yet; hacking together a stub");
+
+            final boolean primaryPhysical = SystemProperties.getBoolean(
+                    PROP_PRIMARY_PHYSICAL, false);
+
+            final String id = "stub_primary";
+            final File path = Environment.getLegacyExternalStorageDirectory();
+            final int descriptionId = android.R.string.unknownName;
+            final boolean primary = true;
+            final boolean removable = primaryPhysical;
+            final boolean emulated = !primaryPhysical;
+            final long mtpReserveSize = 0L;
+            final boolean allowMassStorage = false;
+            final long maxFileSize = 0L;
+            final UserHandle owner = new UserHandle(userId);
+            final String uuid = null;
+            final String userLabel = null;
+            final String state = Environment.MEDIA_REMOVED;
+
+            res.add(0, new StorageVolume(id, MtpStorage.getStorageIdForIndex(0), path,
+                    descriptionId, primary, removable, emulated, mtpReserveSize,
+                    allowMassStorage, maxFileSize, owner, uuid, userLabel, state));
+        }
+
+        return res.toArray(new StorageVolume[res.size()]);
     }
 
     private void addObbStateLocked(ObbState obbState) throws RemoteException {
@@ -3073,21 +2552,13 @@
         if (path.startsWith(obbPath)) {
             path = path.substring(obbPath.length() + 1);
 
-            if (forVold) {
-                return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath();
-            } else {
-                final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
-                return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
-                        .getAbsolutePath();
-            }
+            final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER);
+            return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path)
+                    .getAbsolutePath();
         }
 
         // Handle normal external storage paths
-        if (forVold) {
-            return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath();
-        } else {
-            return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath();
-        }
+        return new File(userEnv.getExternalStorageDirectory(), path).getAbsolutePath();
     }
 
     @Override
@@ -3126,15 +2597,20 @@
             pw.decreaseIndent();
         }
 
-        synchronized (mVolumesLock) {
+        synchronized (mLock) {
             pw.println();
-            pw.println("mVolumes:");
+            pw.println("Disks:");
             pw.increaseIndent();
-            for (StorageVolume volume : mVolumes) {
-                pw.println(volume);
-                pw.increaseIndent();
-                pw.println("Current state: " + mVolumeStates.get(volume.getPath()));
-                pw.decreaseIndent();
+            for (Disk disk : mDisks.values()) {
+                disk.dump(pw);
+            }
+            pw.decreaseIndent();
+
+            pw.println();
+            pw.println("Volumes:");
+            pw.increaseIndent();
+            for (Volume vol : mVolumes.values()) {
+                vol.dump(pw);
             }
             pw.decreaseIndent();
         }
@@ -3153,6 +2629,7 @@
     }
 
     /** {@inheritDoc} */
+    @Override
     public void monitor() {
         if (mConnector != null) {
             mConnector.monitor();
diff --git a/services/core/java/com/android/server/NativeDaemonConnector.java b/services/core/java/com/android/server/NativeDaemonConnector.java
index d2dfc7b..78c7f38 100644
--- a/services/core/java/com/android/server/NativeDaemonConnector.java
+++ b/services/core/java/com/android/server/NativeDaemonConnector.java
@@ -48,8 +48,6 @@
  * {@code libsysutils} FrameworkListener protocol.
  */
 final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdog.Monitor {
-    private static final boolean LOGD = false;
-
     private final static boolean VDBG = false;
 
     private final String TAG;
@@ -58,6 +56,8 @@
     private OutputStream mOutputStream;
     private LocalLog mLocalLog;
 
+    private volatile boolean mDebug = false;
+
     private final ResponseQueue mResponseQueue;
 
     private final PowerManager.WakeLock mWakeLock;
@@ -99,6 +99,14 @@
         mLocalLog = new LocalLog(maxLogSize);
     }
 
+    /**
+     * Enable Set debugging mode, which causes messages to also be written to both
+     * {@link Slog} in addition to internal log.
+     */
+    public void setDebug(boolean debug) {
+        mDebug = debug;
+    }
+
     @Override
     public void run() {
         mCallbackHandler = new Handler(mLooper, this);
@@ -513,7 +521,7 @@
     }
 
     private void log(String logstring) {
-        if (LOGD) Slog.d(TAG, logstring);
+        if (mDebug) Slog.d(TAG, logstring);
         mLocalLog.log(logstring);
     }
 
diff --git a/services/core/java/com/android/server/accounts/AccountManagerService.java b/services/core/java/com/android/server/accounts/AccountManagerService.java
index a64e6c3..1b32f57 100644
--- a/services/core/java/com/android/server/accounts/AccountManagerService.java
+++ b/services/core/java/com/android/server/accounts/AccountManagerService.java
@@ -109,7 +109,7 @@
 
     private static final int TIMEOUT_DELAY_MS = 1000 * 60;
     private static final String DATABASE_NAME = "accounts.db";
-    private static final int DATABASE_VERSION = 6;
+    private static final int DATABASE_VERSION = 7;
 
     private final Context mContext;
 
@@ -131,6 +131,8 @@
     private static final String ACCOUNTS_TYPE_COUNT = "count(type)";
     private static final String ACCOUNTS_PASSWORD = "password";
     private static final String ACCOUNTS_PREVIOUS_NAME = "previous_name";
+    private static final String ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS =
+            "last_password_entry_time_millis_epoch";
 
     private static final String TABLE_AUTHTOKENS = "authtokens";
     private static final String AUTHTOKENS_ID = "_id";
@@ -697,7 +699,8 @@
         long identityToken = clearCallingIdentity();
         try {
             new Session(fromAccounts, response, account.type, false,
-                    false /* stripAuthTokenFromResult */) {
+                    false /* stripAuthTokenFromResult */, account.name,
+                    false /* authDetailsRequired */) {
                 @Override
                 protected String toDebugString(long now) {
                     return super.toDebugString(now) + ", getAccountCredentialsForClone"
@@ -725,12 +728,43 @@
         }
     }
 
+    @Override
+    public boolean accountAuthenticated(final Account account) {
+        if (account == null) {
+            throw new IllegalArgumentException("account is null");
+        }
+        checkAuthenticateAccountsPermission(account);
+
+        final UserAccounts accounts = getUserAccountsForCaller();
+        int userId = Binder.getCallingUserHandle().getIdentifier();
+        if (!canUserModifyAccounts(userId) || !canUserModifyAccountsForType(userId, account.type)) {
+            return false;
+        }
+        synchronized (accounts.cacheLock) {
+            final ContentValues values = new ContentValues();
+            values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
+            final SQLiteDatabase db = accounts.openHelper.getWritableDatabase();
+            int i = db.update(
+                    TABLE_ACCOUNTS,
+                    values,
+                    ACCOUNTS_NAME + "=? AND " + ACCOUNTS_TYPE + "=?",
+                    new String[] {
+                            account.name, account.type
+                    });
+            if (i > 0) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     private void completeCloningAccount(IAccountManagerResponse response,
             final Bundle accountCredentials, final Account account, final UserAccounts targetUser) {
         long id = clearCallingIdentity();
         try {
             new Session(targetUser, response, account.type, false,
-                    false /* stripAuthTokenFromResult */) {
+                    false /* stripAuthTokenFromResult */, account.name,
+                    false /* authDetailsRequired */) {
                 @Override
                 protected String toDebugString(long now) {
                     return super.toDebugString(now) + ", getAccountCredentialsForClone"
@@ -795,6 +829,7 @@
                 values.put(ACCOUNTS_NAME, account.name);
                 values.put(ACCOUNTS_TYPE, account.type);
                 values.put(ACCOUNTS_PASSWORD, password);
+                values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, System.currentTimeMillis());
                 long accountId = db.insert(TABLE_ACCOUNTS, ACCOUNTS_NAME, values);
                 if (accountId < 0) {
                     Log.w(TAG, "insertAccountIntoDatabase: " + account
@@ -885,7 +920,8 @@
         public TestFeaturesSession(UserAccounts accounts, IAccountManagerResponse response,
                 Account account, String[] features) {
             super(accounts, response, account.type, false /* expectActivityLaunch */,
-                    true /* stripAuthTokenFromResult */);
+                    true /* stripAuthTokenFromResult */, account.name,
+                    false /* authDetailsRequired */);
             mFeatures = features;
             mAccount = account;
         }
@@ -1184,7 +1220,8 @@
         public RemoveAccountSession(UserAccounts accounts, IAccountManagerResponse response,
                 Account account, boolean expectActivityLaunch) {
             super(accounts, response, account.type, expectActivityLaunch,
-                    true /* stripAuthTokenFromResult */);
+                    true /* stripAuthTokenFromResult */, account.name,
+                    false /* authDetailsRequired */);
             mAccount = account;
         }
 
@@ -1419,6 +1456,13 @@
             try {
                 final ContentValues values = new ContentValues();
                 values.put(ACCOUNTS_PASSWORD, password);
+                long time = 0;
+                // Only set current time, if it is a valid password. For clear password case, it
+                // should not be set.
+                if (password != null) {
+                    time = System.currentTimeMillis();
+                }
+                values.put(ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS, time);
                 final long accountId = getAccountIdLocked(db, account);
                 if (accountId >= 0) {
                     final String[] argsAccountId = {String.valueOf(accountId)};
@@ -1547,8 +1591,9 @@
         UserAccounts accounts = getUserAccounts(UserHandle.getUserId(callingUid));
         long identityToken = clearCallingIdentity();
         try {
-            new Session(accounts, response, accountType, false,
-                    false /* stripAuthTokenFromResult */) {
+            new Session(accounts, response, accountType, false /* expectActivityLaunch */,
+                    false /* stripAuthTokenFromResult */,  null /* accountName */,
+                    false /* authDetailsRequired */) {
                 @Override
                 protected String toDebugString(long now) {
                     return super.toDebugString(now) + ", getAuthTokenLabel"
@@ -1648,7 +1693,8 @@
             }
 
             new Session(accounts, response, account.type, expectActivityLaunch,
-                    false /* stripAuthTokenFromResult */) {
+                    false /* stripAuthTokenFromResult */, account.name,
+                    false /* authDetailsRequired */) {
                 @Override
                 protected String toDebugString(long now) {
                     if (loginOptions != null) loginOptions.keySet();
@@ -1842,7 +1888,8 @@
         long identityToken = clearCallingIdentity();
         try {
             new Session(accounts, response, accountType, expectActivityLaunch,
-                    true /* stripAuthTokenFromResult */) {
+                    true /* stripAuthTokenFromResult */, null /* accountName */,
+                    false /* authDetailsRequired */) {
                 @Override
                 public void run() throws RemoteException {
                     mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
@@ -1917,7 +1964,8 @@
         long identityToken = clearCallingIdentity();
         try {
             new Session(accounts, response, accountType, expectActivityLaunch,
-                    true /* stripAuthTokenFromResult */) {
+                    true /* stripAuthTokenFromResult */, null /* accountName */,
+                    false /* authDetailsRequired */) {
                 @Override
                 public void run() throws RemoteException {
                     mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures,
@@ -1973,7 +2021,8 @@
         long identityToken = clearCallingIdentity();
         try {
             new Session(accounts, response, account.type, expectActivityLaunch,
-                    true /* stripAuthTokenFromResult */) {
+                    true /* stripAuthTokenFromResult */, account.name,
+                    true /* authDetailsRequired */) {
                 @Override
                 public void run() throws RemoteException {
                     mAuthenticator.confirmCredentials(this, account, options);
@@ -2009,7 +2058,8 @@
         long identityToken = clearCallingIdentity();
         try {
             new Session(accounts, response, account.type, expectActivityLaunch,
-                    true /* stripAuthTokenFromResult */) {
+                    true /* stripAuthTokenFromResult */, account.name,
+                    false /* authDetailsRequired */) {
                 @Override
                 public void run() throws RemoteException {
                     mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions);
@@ -2045,7 +2095,8 @@
         long identityToken = clearCallingIdentity();
         try {
             new Session(accounts, response, accountType, expectActivityLaunch,
-                    true /* stripAuthTokenFromResult */) {
+                    true /* stripAuthTokenFromResult */, null /* accountName */,
+                    false /* authDetailsRequired */) {
                 @Override
                 public void run() throws RemoteException {
                     mAuthenticator.editProperties(this, mAccountType);
@@ -2071,7 +2122,8 @@
         public GetAccountsByTypeAndFeatureSession(UserAccounts accounts,
                 IAccountManagerResponse response, String type, String[] features, int callingUid) {
             super(accounts, response, type, false /* expectActivityLaunch */,
-                    true /* stripAuthTokenFromResult */);
+                    true /* stripAuthTokenFromResult */, null /* accountName */,
+                    false /* authDetailsRequired */);
             mCallingUid = callingUid;
             mFeatures = features;
         }
@@ -2437,6 +2489,9 @@
         final String mAccountType;
         final boolean mExpectActivityLaunch;
         final long mCreationTime;
+        final String mAccountName;
+        // Indicates if we need to add auth details(like last credential time)
+        final boolean mAuthDetailsRequired;
 
         public int mNumResults = 0;
         private int mNumRequestContinued = 0;
@@ -2448,7 +2503,8 @@
         protected final UserAccounts mAccounts;
 
         public Session(UserAccounts accounts, IAccountManagerResponse response, String accountType,
-                boolean expectActivityLaunch, boolean stripAuthTokenFromResult) {
+                boolean expectActivityLaunch, boolean stripAuthTokenFromResult, String accountName,
+                boolean authDetailsRequired) {
             super();
             //if (response == null) throw new IllegalArgumentException("response is null");
             if (accountType == null) throw new IllegalArgumentException("accountType is null");
@@ -2458,6 +2514,9 @@
             mAccountType = accountType;
             mExpectActivityLaunch = expectActivityLaunch;
             mCreationTime = SystemClock.elapsedRealtime();
+            mAccountName = accountName;
+            mAuthDetailsRequired = authDetailsRequired;
+
             synchronized (mSessions) {
                 mSessions.put(toString(), this);
             }
@@ -2592,6 +2651,16 @@
         public void onResult(Bundle result) {
             mNumResults++;
             Intent intent = null;
+            if (result != null && mAuthDetailsRequired) {
+                long lastAuthenticatedTime = DatabaseUtils.longForQuery(
+                        mAccounts.openHelper.getReadableDatabase(),
+                        "select " + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " from " +
+                                TABLE_ACCOUNTS + " WHERE " + ACCOUNTS_NAME + "=? AND "
+                                + ACCOUNTS_TYPE + "=?",
+                        new String[]{mAccountName, mAccountType});
+                result.putLong(AccountManager.KEY_LAST_AUTHENTICATE_TIME_MILLIS_EPOCH,
+                        lastAuthenticatedTime);
+            }
             if (result != null
                     && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) {
                 /*
@@ -2798,6 +2867,7 @@
                     + ACCOUNTS_TYPE + " TEXT NOT NULL, "
                     + ACCOUNTS_PASSWORD + " TEXT, "
                     + ACCOUNTS_PREVIOUS_NAME + " TEXT, "
+                    + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " INTEGER DEFAULT 0, "
                     + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
 
             db.execSQL("CREATE TABLE " + TABLE_AUTHTOKENS + " (  "
@@ -2833,6 +2903,11 @@
                     + "UNIQUE(" + ACCOUNTS_NAME + "," + ACCOUNTS_TYPE + "))");
         }
 
+        private void addLastSuccessfullAuthenticatedTimeColumn(SQLiteDatabase db) {
+            db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN "
+                    + ACCOUNTS_LAST_AUTHENTICATE_TIME_EPOCH_MILLIS + " DEFAULT 0");
+        }
+
         private void addOldAccountNameColumn(SQLiteDatabase db) {
             db.execSQL("ALTER TABLE " + TABLE_ACCOUNTS + " ADD COLUMN " + ACCOUNTS_PREVIOUS_NAME);
         }
@@ -2892,6 +2967,11 @@
                 oldVersion++;
             }
 
+            if (oldVersion == 6) {
+                addLastSuccessfullAuthenticatedTimeColumn(db);
+                oldVersion++;
+            }
+
             if (oldVersion != newVersion) {
                 Log.e(TAG, "failed to upgrade version " + oldVersion + " to version " + newVersion);
             }
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index b5e1de9..c318370 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -3041,24 +3041,13 @@
             int uid = app.uid;
 
             int[] gids = null;
-            int mountExternal = Zygote.MOUNT_EXTERNAL_NONE;
+            int mountExternal = Zygote.MOUNT_EXTERNAL_DEFAULT;
             if (!app.isolated) {
                 int[] permGids = null;
                 try {
                     checkTime(startTime, "startProcess: getting gids from package manager");
                     permGids = AppGlobals.getPackageManager().getPackageGids(app.info.packageName,
                             app.userId);
-
-                    if (Environment.isExternalStorageEmulated()) {
-                        checkTime(startTime, "startProcess: checking external storage perm");
-                        if (mContext.getPackageManager().checkPermission(
-                                android.Manifest.permission.ACCESS_ALL_EXTERNAL_STORAGE,
-                                app.info.packageName) == PERMISSION_GRANTED) {
-                            mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER_ALL;
-                        } else {
-                            mountExternal = Zygote.MOUNT_EXTERNAL_MULTIUSER;
-                        }
-                    }
                 } catch (RemoteException e) {
                     Slog.w(TAG, "Unable to retrieve gids", e);
                 }
@@ -17505,8 +17494,12 @@
         mFullPssPending = true;
         mPendingPssProcesses.ensureCapacity(mLruProcesses.size());
         mPendingPssProcesses.clear();
-        for (int i=mLruProcesses.size()-1; i>=0; i--) {
+        for (int i = mLruProcesses.size() - 1; i >= 0; i--) {
             ProcessRecord app = mLruProcesses.get(i);
+            if (app.thread == null
+                    || app.curProcState == ActivityManager.PROCESS_STATE_NONEXISTENT) {
+                continue;
+            }
             if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) {
                 app.pssProcState = app.setProcState;
                 app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true,
@@ -17822,8 +17815,8 @@
                 }
             }
         }
-        if (app.setProcState < 0 || ProcessList.procStatesDifferForMem(app.curProcState,
-                app.setProcState)) {
+        if (app.setProcState == ActivityManager.PROCESS_STATE_NONEXISTENT
+                || ProcessList.procStatesDifferForMem(app.curProcState, app.setProcState)) {
             if (false && mTestPssMode && app.setProcState >= 0 && app.lastStateTime <= (now-200)) {
                 // Experimental code to more aggressively collect pss while
                 // running test...  the problem is that this tends to collect
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 0eb914b..8ba34e2 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1250,6 +1250,11 @@
                 TAG, "ensureActivitiesVisible behind " + top
                 + " configChanges=0x" + Integer.toHexString(configChanges));
 
+        if (DEBUG_STATES && starting != null && starting.task.stack == this) {
+            Slog.d(TAG, "ensureActivitiesVisibleLocked: starting=" + starting + " state="
+                    + starting.state + " fullscreen=" + starting.fullscreen + " top=" + top
+                    + " state=" + top.state + " fullscreen=" + top.fullscreen);
+        }
         if (mTranslucentActivityWaiting != top) {
             mUndrawnActivitiesBelowTopTranslucent.clear();
             if (mTranslucentActivityWaiting != null) {
diff --git a/services/core/java/com/android/server/am/ProcessRecord.java b/services/core/java/com/android/server/am/ProcessRecord.java
index 7cf3b51..7c921ac 100644
--- a/services/core/java/com/android/server/am/ProcessRecord.java
+++ b/services/core/java/com/android/server/am/ProcessRecord.java
@@ -16,6 +16,7 @@
 
 package com.android.server.am;
 
+import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
@@ -89,10 +90,10 @@
     int curSchedGroup;          // Currently desired scheduling class
     int setSchedGroup;          // Last set to background scheduling class
     int trimMemoryLevel;        // Last selected memory trimming level
-    int curProcState = -1;      // Currently computed process state: ActivityManager.PROCESS_STATE_*
-    int repProcState = -1;      // Last reported process state
-    int setProcState = -1;      // Last set process state in process tracker
-    int pssProcState = -1;      // The proc state we are currently requesting pss for
+    int curProcState = PROCESS_STATE_NONEXISTENT; // Currently computed process state
+    int repProcState = PROCESS_STATE_NONEXISTENT; // Last reported process state
+    int setProcState = PROCESS_STATE_NONEXISTENT; // Last set process state in process tracker
+    int pssProcState = PROCESS_STATE_NONEXISTENT; // Currently requesting pss for
     boolean serviceb;           // Process currently is on the service B list
     boolean serviceHighRam;     // We are forcing to service B list due to its RAM use
     boolean setIsForeground;    // Running foreground UI when last set?
diff --git a/services/core/java/com/android/server/am/TaskRecord.java b/services/core/java/com/android/server/am/TaskRecord.java
index d8d361b..3b34541 100644
--- a/services/core/java/com/android/server/am/TaskRecord.java
+++ b/services/core/java/com/android/server/am/TaskRecord.java
@@ -16,6 +16,8 @@
 
 package com.android.server.am;
 
+import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
+import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.am.ActivityRecord.HOME_ACTIVITY_TYPE;
@@ -346,8 +348,8 @@
         if ((info.flags & ActivityInfo.FLAG_AUTO_REMOVE_FROM_RECENTS) != 0) {
             // If the activity itself has requested auto-remove, then just always do it.
             autoRemoveRecents = true;
-        } else if ((intentFlags & (Intent.FLAG_ACTIVITY_NEW_DOCUMENT
-                | Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS)) == Intent.FLAG_ACTIVITY_NEW_DOCUMENT) {
+        } else if ((intentFlags & (FLAG_ACTIVITY_NEW_DOCUMENT | FLAG_ACTIVITY_RETAIN_IN_RECENTS))
+                == FLAG_ACTIVITY_NEW_DOCUMENT) {
             // If the caller has not asked for the document to be retained, then we may
             // want to turn on auto-remove, depending on whether the target has set its
             // own document launch mode.
@@ -879,7 +881,8 @@
         for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) {
             final ActivityRecord r = activities.get(activityNdx);
             if (r.info.persistableMode == ActivityInfo.PERSIST_ROOT_ONLY || !r.isPersistable() ||
-                    ((r.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) &&
+                    ((r.intent.getFlags() & FLAG_ACTIVITY_NEW_DOCUMENT
+                            | FLAG_ACTIVITY_RETAIN_IN_RECENTS) == FLAG_ACTIVITY_NEW_DOCUMENT) &&
                             activityNdx > 0) {
                 // Stop at first non-persistable or first break in task (CLEAR_WHEN_TASK_RESET).
                 break;
diff --git a/services/core/java/com/android/server/camera/CameraService.java b/services/core/java/com/android/server/camera/CameraService.java
new file mode 100644
index 0000000..f9b17ed
--- /dev/null
+++ b/services/core/java/com/android/server/camera/CameraService.java
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.camera;
+
+import android.content.Context;
+import android.hardware.ICameraService;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+import com.android.server.SystemService;
+
+/**
+ * CameraService is the system_server analog to the camera service running in mediaserver.
+ *
+ * @hide
+ */
+public class CameraService extends SystemService {
+
+    /**
+     * This must match the ICameraService.aidl definition
+     */
+    private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
+
+    // Event arguments to use with the camera service notifySystemEvent call:
+    public static final int NO_EVENT = 0; // NOOP
+    public static final int USER_SWITCHED = 1; // User changed, argument is the new user handle
+
+    public CameraService(Context context) {
+        super(context);
+    }
+
+    @Override
+    public void onStart() {}
+
+    @Override
+    public void onSwitchUser(int userHandle) {
+        super.onSwitchUser(userHandle);
+
+        /**
+         * Forward the user switch event to the native camera service running in mediaserver.
+         */
+        IBinder cameraServiceBinder = getBinderService(CAMERA_SERVICE_BINDER_NAME);
+        if (cameraServiceBinder == null) {
+            return; // Camera service not active, there is no need to evict user clients.
+        }
+        ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);
+        try {
+            cameraServiceRaw.notifySystemEvent(USER_SWITCHED, userHandle);
+        } catch (RemoteException e) {
+            // Do nothing, if camera service is dead, there is no need to evict user clients.
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/policy/WindowOrientationListener.java b/services/core/java/com/android/server/policy/WindowOrientationListener.java
index a33ee4c..c8fd82e 100644
--- a/services/core/java/com/android/server/policy/WindowOrientationListener.java
+++ b/services/core/java/com/android/server/policy/WindowOrientationListener.java
@@ -136,7 +136,9 @@
 
     public void onTouchStart() {
         synchronized (mLock) {
-            mSensorEventListener.onTouchStartLocked();
+            if (mSensorEventListener != null) {
+                mSensorEventListener.onTouchStartLocked();
+            }
         }
     }
 
@@ -144,7 +146,9 @@
         long whenElapsedNanos = SystemClock.elapsedRealtimeNanos();
 
         synchronized (mLock) {
-            mSensorEventListener.onTouchEndLocked(whenElapsedNanos);
+            if (mSensorEventListener != null) {
+                mSensorEventListener.onTouchEndLocked(whenElapsedNanos);
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java b/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
deleted file mode 100644
index 2fe68f8..0000000
--- a/services/core/java/com/android/server/updates/TZInfoInstallReceiver.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.updates;
-
-import android.util.Base64;
-
-import java.io.IOException;
-
-public class TZInfoInstallReceiver extends ConfigUpdateInstallReceiver {
-
-    public TZInfoInstallReceiver() {
-        super("/data/misc/zoneinfo/", "tzdata", "metadata/", "version");
-    }
-
-    @Override
-    protected void install(byte[] encodedContent, int version) throws IOException {
-        super.install(Base64.decode(encodedContent, Base64.DEFAULT), version);
-    }
-}
diff --git a/services/core/java/com/android/server/updates/TzDataInstallReceiver.java b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
new file mode 100644
index 0000000..b260e4e
--- /dev/null
+++ b/services/core/java/com/android/server/updates/TzDataInstallReceiver.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.updates;
+
+import android.util.Slog;
+
+import java.io.File;
+import java.io.IOException;
+import libcore.tzdata.update.TzDataBundleInstaller;
+
+/**
+ * An install receiver responsible for installing timezone data updates.
+ */
+public class TzDataInstallReceiver extends ConfigUpdateInstallReceiver {
+
+    private static final String TAG = "TZDataInstallReceiver";
+
+    private static final File TZ_DATA_DIR = new File("/data/misc/zoneinfo");
+    private static final String UPDATE_DIR_NAME = TZ_DATA_DIR.getPath() + "/updates/";
+    private static final String UPDATE_METADATA_DIR_NAME = "metadata/";
+    private static final String UPDATE_VERSION_FILE_NAME = "version";
+    private static final String UPDATE_CONTENT_FILE_NAME = "tzdata_bundle.zip";
+
+    private final TzDataBundleInstaller installer;
+
+    public TzDataInstallReceiver() {
+        super(UPDATE_DIR_NAME, UPDATE_CONTENT_FILE_NAME, UPDATE_METADATA_DIR_NAME,
+                UPDATE_VERSION_FILE_NAME);
+        installer = new TzDataBundleInstaller(TAG, TZ_DATA_DIR);
+    }
+
+    @Override
+    protected void install(byte[] content, int version) throws IOException {
+        boolean valid = installer.install(content);
+        Slog.i(TAG, "Timezone data install valid for this device: " + valid);
+        // Even if !valid, we call super.install(). Only in the event of an exception should we
+        // not. If we didn't do this we could attempt to install repeatedly.
+        super.install(content, version);
+    }
+}
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 32bb78e..dcd233f 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -9059,39 +9059,41 @@
                     goodToGo = false;
                 }
             }
-            if (goodToGo && isWallpaperVisible(mWallpaperTarget)) {
-                boolean wallpaperGoodToGo = true;
-                for (int curTokenIndex = mWallpaperTokens.size() - 1;
-                        curTokenIndex >= 0 && wallpaperGoodToGo; curTokenIndex--) {
-                    WindowToken token = mWallpaperTokens.get(curTokenIndex);
-                    for (int curWallpaperIndex = token.windows.size() - 1; curWallpaperIndex >= 0;
-                            curWallpaperIndex--) {
-                        WindowState wallpaper = token.windows.get(curWallpaperIndex);
-                        if (wallpaper.mWallpaperVisible && !wallpaper.isDrawnLw()) {
-                            // We've told this wallpaper to be visible, but it is not drawn yet
-                            wallpaperGoodToGo = false;
-                            if (mWallpaperDrawState != WALLPAPER_DRAW_TIMEOUT) {
-                                // wait for this wallpaper until it is drawn or timeout
-                                goodToGo = false;
-                            }
-                            if (mWallpaperDrawState == WALLPAPER_DRAW_NORMAL) {
-                                mWallpaperDrawState = WALLPAPER_DRAW_PENDING;
-                                mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
-                                mH.sendEmptyMessageDelayed(H.WALLPAPER_DRAW_PENDING_TIMEOUT,
-                                        WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
-                            }
-                            if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
-                                    "Wallpaper should be visible but has not been drawn yet. " +
-                                    "mWallpaperDrawState=" + mWallpaperDrawState);
-                            break;
-                        }
-                    }
-                }
-                if (wallpaperGoodToGo) {
-                    mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
-                    mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
-                }
-            }
+// Stuck in a state with mWallpaperDrawState == WALLPAPER_DRAW_PENDING without a timeout. Leave
+// commented out until that is understood.
+//            if (goodToGo && isWallpaperVisible(mWallpaperTarget)) {
+//                boolean wallpaperGoodToGo = true;
+//                for (int curTokenIndex = mWallpaperTokens.size() - 1;
+//                        curTokenIndex >= 0 && wallpaperGoodToGo; curTokenIndex--) {
+//                    WindowToken token = mWallpaperTokens.get(curTokenIndex);
+//                    for (int curWallpaperIndex = token.windows.size() - 1; curWallpaperIndex >= 0;
+//                            curWallpaperIndex--) {
+//                        WindowState wallpaper = token.windows.get(curWallpaperIndex);
+//                        if (wallpaper.mWallpaperVisible && !wallpaper.isDrawnLw()) {
+//                            // We've told this wallpaper to be visible, but it is not drawn yet
+//                            wallpaperGoodToGo = false;
+//                            if (mWallpaperDrawState != WALLPAPER_DRAW_TIMEOUT) {
+//                                // wait for this wallpaper until it is drawn or timeout
+//                                goodToGo = false;
+//                            }
+//                            if (mWallpaperDrawState == WALLPAPER_DRAW_NORMAL) {
+//                                mWallpaperDrawState = WALLPAPER_DRAW_PENDING;
+//                                mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
+//                                mH.sendEmptyMessageDelayed(H.WALLPAPER_DRAW_PENDING_TIMEOUT,
+//                                        WALLPAPER_DRAW_PENDING_TIMEOUT_DURATION);
+//                            }
+//                            if (DEBUG_APP_TRANSITIONS || DEBUG_WALLPAPER) Slog.v(TAG,
+//                                    "Wallpaper should be visible but has not been drawn yet. " +
+//                                    "mWallpaperDrawState=" + mWallpaperDrawState);
+//                            break;
+//                        }
+//                    }
+//                }
+//                if (wallpaperGoodToGo) {
+//                    mWallpaperDrawState = WALLPAPER_DRAW_NORMAL;
+//                    mH.removeMessages(H.WALLPAPER_DRAW_PENDING_TIMEOUT);
+//                }
+//            }
         }
         if (goodToGo) {
             if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "**** GOOD TO GO");
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 090c0f8..53da75b 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -53,6 +53,7 @@
 import com.android.server.accounts.AccountManagerService;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.audio.AudioService;
+import com.android.server.camera.CameraService;
 import com.android.server.clipboard.ClipboardService;
 import com.android.server.content.ContentService;
 import com.android.server.devicepolicy.DevicePolicyManagerService;
@@ -408,6 +409,7 @@
         AudioService audioService = null;
         MmsServiceBroker mmsService = null;
         EntropyMixer entropyMixer = null;
+        CameraService cameraService = null;
 
         boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false);
         boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false);
@@ -436,6 +438,9 @@
 
             mContentResolver = context.getContentResolver();
 
+            Slog.i(TAG, "Camera Service");
+            mSystemServiceManager.startService(CameraService.class);
+
             // The AccountManager must come before the ContentService
             try {
                 // TODO: seems like this should be disable-able, but req'd by ContentService
diff --git a/services/usb/java/com/android/server/usb/UsbMidiDevice.java b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
index 7c101a40f..6ece888 100644
--- a/services/usb/java/com/android/server/usb/UsbMidiDevice.java
+++ b/services/usb/java/com/android/server/usb/UsbMidiDevice.java
@@ -29,6 +29,9 @@
 import android.system.StructPollfd;
 import android.util.Log;
 
+import com.android.internal.midi.MidiEventScheduler;
+import com.android.internal.midi.MidiEventScheduler.MidiEvent;
+
 import libcore.io.IoUtils;
 
 import java.io.Closeable;
@@ -42,7 +45,7 @@
 
     private MidiDeviceServer mServer;
 
-    private final MidiReceiver[] mInputPortReceivers;
+    private final MidiEventScheduler mEventScheduler;
 
     private static final int BUFFER_SIZE = 512;
 
@@ -99,19 +102,7 @@
         for (int i = 0; i < outputCount; i++) {
             mOutputStreams[i] = new FileOutputStream(fileDescriptors[i]);
         }
-
-        mInputPortReceivers = new MidiReceiver[inputCount];
-        for (int port = 0; port < inputCount; port++) {
-            final int portF = port;
-            mInputPortReceivers[port] = new MidiReceiver() {
-                @Override
-                public void onReceive(byte[] data, int offset, int count, long timestamp)
-                        throws IOException {
-                    // FIXME - timestamps are ignored, future posting not supported yet.
-                    mOutputStreams[portF].write(data, offset, count);
-                }
-            };
-        }
+        mEventScheduler = new MidiEventScheduler(inputCount);
     }
 
     private boolean register(Context context, Bundle properties) {
@@ -121,16 +112,22 @@
             return false;
         }
 
+        int inputCount = mInputStreams.length;
         int outputCount = mOutputStreams.length;
-        mServer = midiManager.createDeviceServer(mInputPortReceivers, outputCount,
+        MidiReceiver[] inputPortReceivers = new MidiReceiver[inputCount];
+        for (int port = 0; port < inputCount; port++) {
+            inputPortReceivers[port] = mEventScheduler.getReceiver(port);
+        }
+
+        mServer = midiManager.createDeviceServer(inputPortReceivers, outputCount,
                 null, null, properties, MidiDeviceInfo.TYPE_USB, null);
         if (mServer == null) {
             return false;
         }
         final MidiReceiver[] outputReceivers = mServer.getOutputPortReceivers();
 
-        // FIXME can we only run this when we have a dispatcher that has listeners?
-        new Thread() {
+        // Create input thread
+        new Thread("UsbMidiDevice input thread") {
             @Override
             public void run() {
                 byte[] buffer = new byte[BUFFER_SIZE];
@@ -160,6 +157,33 @@
                 } catch (ErrnoException e) {
                     Log.d(TAG, "reader thread exiting");
                 }
+                Log.d(TAG, "input thread exit");
+            }
+        }.start();
+
+        // Create output thread
+        new Thread("UsbMidiDevice output thread") {
+            @Override
+            public void run() {
+                while (true) {
+                    MidiEvent event;
+                    try {
+                        event = (MidiEvent)mEventScheduler.waitNextEvent();
+                    } catch (InterruptedException e) {
+                        // try again
+                        continue;
+                    }
+                    if (event == null) {
+                        break;
+                    }
+                    try {
+                        mOutputStreams[event.portNumber].write(event.data, 0, event.count);
+                    } catch (IOException e) {
+                        Log.e(TAG, "write failed for port " + event.portNumber);
+                    }
+                    mEventScheduler.addEventToPool(event);
+                }
+                Log.d(TAG, "output thread exit");
             }
         }.start();
 
@@ -168,6 +192,8 @@
 
     @Override
     public void close() throws IOException {
+        mEventScheduler.close();
+
         if (mServer != null) {
             mServer.close();
         }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index fbc70db..6bc6de63 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -3316,11 +3316,11 @@
      * @return the preferred network type, defined in RILConstants.java.
      * @hide
      */
-    public int getPreferredNetworkType() {
+    public int getPreferredNetworkType(int subId) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
-                return telephony.getPreferredNetworkType();
+                return telephony.getPreferredNetworkType(subId);
         } catch (RemoteException ex) {
             Rlog.e(TAG, "getPreferredNetworkType RemoteException", ex);
         } catch (NullPointerException ex) {
@@ -3330,6 +3330,27 @@
     }
 
     /**
+     * Sets the network selection mode to automatic.
+     * <p>
+     * Requires Permission:
+     *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
+     * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
+     *
+     * @hide
+     */
+    public void setNetworkSelectionModeAutomatic(int subId) {
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null)
+                telephony.setNetworkSelectionModeAutomatic(subId);
+        } catch (RemoteException ex) {
+            Rlog.e(TAG, "setNetworkSelectionModeAutomatic RemoteException", ex);
+        } catch (NullPointerException ex) {
+            Rlog.e(TAG, "setNetworkSelectionModeAutomatic NPE", ex);
+        }
+    }
+
+    /**
      * Set the preferred network type.
      * Used for device configuration by some CDMA operators.
      * <p>
@@ -3337,15 +3358,16 @@
      *   {@link android.Manifest.permission#MODIFY_PHONE_STATE MODIFY_PHONE_STATE}
      * Or the calling app has carrier privileges. @see #hasCarrierPrivileges
      *
+     * @param subId the id of the subscription to set the preferred network type for.
      * @param networkType the preferred network type, defined in RILConstants.java.
      * @return true on success; false on any failure.
      * @hide
      */
-    public boolean setPreferredNetworkType(int networkType) {
+    public boolean setPreferredNetworkType(int subId, int networkType) {
         try {
             ITelephony telephony = getITelephony();
             if (telephony != null)
-                return telephony.setPreferredNetworkType(networkType);
+                return telephony.setPreferredNetworkType(subId, networkType);
         } catch (RemoteException ex) {
             Rlog.e(TAG, "setPreferredNetworkType RemoteException", ex);
         } catch (NullPointerException ex) {
@@ -3364,7 +3386,8 @@
      * @return true on success; false on any failure.
      */
     public boolean setPreferredNetworkTypeToGlobal() {
-        return setPreferredNetworkType(RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
+        return setPreferredNetworkType(getDefaultSubscription(),
+                RILConstants.NETWORK_MODE_LTE_CDMA_EVDO_GSM_WCDMA);
     }
 
     /**
@@ -4448,4 +4471,22 @@
 
         return retval;
     }
+
+    /**
+     * Resets telephony manager settings back to factory defaults.
+     *
+     * @hide
+     */
+    public void factoryReset(int subId) {
+        if (SubscriptionManager.isUsableSubIdValue(subId)) {
+            // Enable data
+            setDataEnabled(subId, true);
+            // Set network selection mode to automatic
+            setNetworkSelectionModeAutomatic(subId);
+            // Set preferred mobile network type to the best available
+            setPreferredNetworkType(subId, RILConstants.PREFERRED_NETWORK_MODE);
+            // Turn off roaming
+            SubscriptionManager.from(mContext).setDataRoaming(0, subId);
+        }
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index c18e3b6..a24859b 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -653,9 +653,10 @@
      * Get the preferred network type.
      * Used for device configuration by some CDMA operators.
      *
+     * @param subId the id of the subscription to query.
      * @return the preferred network type, defined in RILConstants.java.
      */
-    int getPreferredNetworkType();
+    int getPreferredNetworkType(int subId);
 
     /**
      * Check TETHER_DUN_REQUIRED and TETHER_DUN_APN settings, net.tethering.noprovisioning
@@ -667,13 +668,21 @@
     int getTetherApnRequired();
 
     /**
+     * Set the network selection mode to automatic.
+     *
+     * @param subId the id of the subscription to update.
+     */
+    void setNetworkSelectionModeAutomatic(int subId);
+
+    /**
      * Set the preferred network type.
      * Used for device configuration by some CDMA operators.
      *
+     * @param subId the id of the subscription to update.
      * @param networkType the preferred network type, defined in RILConstants.java.
      * @return true on success; false on any failure.
      */
-    boolean setPreferredNetworkType(int networkType);
+    boolean setPreferredNetworkType(int subId, int networkType);
 
     /**
      * User enable/disable Mobile Data.
diff --git a/wifi/java/android/net/wifi/WifiManager.java b/wifi/java/android/net/wifi/WifiManager.java
index e1460ef..6371891 100644
--- a/wifi/java/android/net/wifi/WifiManager.java
+++ b/wifi/java/android/net/wifi/WifiManager.java
@@ -2621,4 +2621,25 @@
         }
         return false;
     }
+
+    /**
+     * Resets all wifi manager settings back to factory defaults.
+     *
+     * @hide
+     */
+    public void factoryReset() {
+        // Enable wifi
+        setWifiEnabled(true);
+        // Delete all Wifi SSIDs
+        List<WifiConfiguration> networks = getConfiguredNetworks();
+        if (networks != null) {
+            for (WifiConfiguration config : networks) {
+                removeNetwork(config.networkId);
+            }
+            saveConfiguration();
+        }
+
+        // Turn mobile hotspot off
+        setWifiApEnabled(null, false);
+    }
 }