Merge "Use device model and name for internal storage." into nyc-dev
diff --git a/Android.mk b/Android.mk
index 2ee7600..3ac5889 100644
--- a/Android.mk
+++ b/Android.mk
@@ -150,14 +150,14 @@
 	core/java/android/content/pm/IPackageStatsObserver.aidl \
 	core/java/android/content/pm/IOnPermissionsChangeListener.aidl \
 	core/java/android/database/IContentObserver.aidl \
-	core/java/android/hardware/ICameraService.aidl \
-	core/java/android/hardware/ICameraServiceListener.aidl \
-	core/java/android/hardware/ICameraServiceProxy.aidl \
-	core/java/android/hardware/ICamera.aidl \
-	core/java/android/hardware/ICameraClient.aidl \
+	../av/camera/aidl/android/hardware/ICameraService.aidl \
+	../av/camera/aidl/android/hardware/ICameraServiceListener.aidl \
+	../av/camera/aidl/android/hardware/ICameraServiceProxy.aidl \
+	../av/camera/aidl/android/hardware/ICamera.aidl \
+	../av/camera/aidl/android/hardware/ICameraClient.aidl \
+	../av/camera/aidl/android/hardware/camera2/ICameraDeviceUser.aidl \
+	../av/camera/aidl/android/hardware/camera2/ICameraDeviceCallbacks.aidl \
 	core/java/android/hardware/IConsumerIrService.aidl \
-	core/java/android/hardware/camera2/ICameraDeviceUser.aidl \
-	core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl \
 	core/java/android/hardware/ISerialManager.aidl \
 	core/java/android/hardware/display/IDisplayManager.aidl \
 	core/java/android/hardware/display/IDisplayManagerCallback.aidl \
@@ -455,6 +455,10 @@
       $(FRAMEWORKS_BASE_JAVA_SRC_DIRS) \
       frameworks/native/aidl/binder
 
+LOCAL_AIDL_INCLUDES += \
+	frameworks/av/camera/aidl \
+	frameworks/native/aidl/gui
+
 LOCAL_INTERMEDIATE_SOURCES := \
 			$(framework_res_source_path)/android/R.java \
 			$(framework_res_source_path)/android/Manifest.java \
@@ -577,7 +581,7 @@
 	frameworks/base/core/java/android/view/Display.aidl \
 	frameworks/base/core/java/android/view/InputDevice.aidl \
 	frameworks/base/core/java/android/view/InputEvent.aidl \
-	frameworks/base/core/java/android/view/Surface.aidl \
+	frameworks/native/aidl/gui/android/view/Surface.aidl \
 	frameworks/base/core/java/android/view/WindowContentFrameStats.aidl \
 	frameworks/base/core/java/android/view/inputmethod/InputMethodSubtype.aidl \
 	frameworks/base/core/java/android/view/inputmethod/CursorAnchorInfo.aidl \
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index 4aab163..4676cc4 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -647,6 +647,16 @@
             return stackId != PINNED_STACK_ID && stackId != FREEFORM_WORKSPACE_STACK_ID
                     && stackId != DOCKED_STACK_ID;
         }
+
+        /**
+         * Returns true if the input stack id should only be present on a device that supports
+         * multi-window mode.
+         * @see android.app.ActivityManager#supportsMultiWindow
+         */
+        public static boolean isMultiWindowStack(int stackId) {
+            return isStaticStack(stackId) || stackId == PINNED_STACK_ID
+                    || stackId == FREEFORM_WORKSPACE_STACK_ID || stackId == DOCKED_STACK_ID;
+        }
     }
 
     /**
@@ -868,6 +878,17 @@
     }
 
     /**
+     * Returns true if the system supports at least one form of multi-window.
+     * E.g. freeform, split-screen, picture-in-picture.
+     * @hide
+     */
+    static public boolean supportsMultiWindow() {
+        return !isLowRamDeviceStatic()
+                && Resources.getSystem().getBoolean(
+                    com.android.internal.R.bool.config_supportsMultiWindow);
+    }
+
+    /**
      * Information you can set and retrieve about the current activity within the recent task list.
      */
     public static class TaskDescription implements Parcelable {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 5285d52..a1ad590 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2750,7 +2750,7 @@
      * @param admin Which {@link DeviceAdminReceiver} this request is associated with, or
      *            {@code null} if calling from a delegated certificate installer.
      * @param alias The private key alias under which the certificate is installed.
-     * @return {@code true} if the keys were both removed, {@code false} otherwise.
+     * @return {@code true} if the certificate alias no longer exists, {@code false} otherwise.
      */
     public boolean removeKeyPair(@Nullable ComponentName admin, @NonNull String alias) {
         try {
diff --git a/core/java/android/hardware/CameraInfo.aidl b/core/java/android/hardware/CameraInfo.aidl
deleted file mode 100644
index e21e694..0000000
--- a/core/java/android/hardware/CameraInfo.aidl
+++ /dev/null
@@ -1,20 +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 android.hardware;
-
-/** @hide */
-parcelable CameraInfo;
diff --git a/core/java/android/hardware/ICamera.aidl b/core/java/android/hardware/ICamera.aidl
deleted file mode 100644
index d4f64f8..0000000
--- a/core/java/android/hardware/ICamera.aidl
+++ /dev/null
@@ -1,26 +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 android.hardware;
-
-/** @hide */
-interface ICamera
-{
-    /**
-     * Keep up-to-date with frameworks/av/include/camera/ICamera.h
-     */
-    void disconnect();
-}
diff --git a/core/java/android/hardware/ICameraClient.aidl b/core/java/android/hardware/ICameraClient.aidl
deleted file mode 100644
index d7877b4..0000000
--- a/core/java/android/hardware/ICameraClient.aidl
+++ /dev/null
@@ -1,26 +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 android.hardware;
-
-/** @hide */
-interface ICameraClient
-{
-    /**
-     * Keep up-to-date with frameworks/av/include/camera/ICameraClient.h
-     */
-    // TODO: consider implementing this.
-}
diff --git a/core/java/android/hardware/ICameraService.aidl b/core/java/android/hardware/ICameraService.aidl
deleted file mode 100644
index 0b165cd..0000000
--- a/core/java/android/hardware/ICameraService.aidl
+++ /dev/null
@@ -1,89 +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 android.hardware;
-
-import android.hardware.ICamera;
-import android.hardware.ICameraClient;
-import android.hardware.camera2.ICameraDeviceUser;
-import android.hardware.camera2.ICameraDeviceCallbacks;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.utils.BinderHolder;
-import android.hardware.ICameraServiceListener;
-import android.hardware.CameraInfo;
-
-/**
- * Binder interface for the native camera service running in mediaserver.
- *
- * @hide
- */
-interface ICameraService
-{
-    /**
-     * Keep up-to-date with frameworks/av/include/camera/ICameraService.h
-     */
-    int getNumberOfCameras(int type);
-
-    // rest of 'int' return values in this file are actually status_t
-
-    int getCameraInfo(int cameraId, out CameraInfo info);
-
-    int connect(ICameraClient client, int cameraId,
-                    String opPackageName,
-                    int clientUid,
-                    // Container for an ICamera object
-                    out BinderHolder device);
-
-    int connectDevice(ICameraDeviceCallbacks callbacks, int cameraId,
-                              String opPackageName,
-                              int clientUid,
-                              // Container for an ICameraDeviceUser object
-                              out BinderHolder device);
-
-    int addListener(ICameraServiceListener listener);
-    int removeListener(ICameraServiceListener listener);
-
-    int getCameraCharacteristics(int cameraId, out CameraMetadataNative info);
-
-    /**
-     * The java stubs for this method are not intended to be used.  Please use
-     * the native stub in frameworks/av/include/camera/ICameraService.h instead.
-     * The BinderHolder output is being used as a placeholder, and will not be
-     * well-formatted in the generated java method.
-     */
-    int getCameraVendorTagDescriptor(out BinderHolder desc);
-
-    // Writes the camera1 parameters into a single-element array.
-    int getLegacyParameters(int cameraId, out String[] parameters);
-    // Determines if a particular API version is supported; see ICameraService.h for version defines
-    int supportsCameraApi(int cameraId, int apiVersion);
-
-    int connectLegacy(ICameraClient client, int cameraId,
-                    int halVersion,
-                    String opPackageName,
-                    int clientUid,
-                    // Container for an ICamera object
-                    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, in int[] args);
-}
diff --git a/core/java/android/hardware/ICameraServiceListener.aidl b/core/java/android/hardware/ICameraServiceListener.aidl
deleted file mode 100644
index 49278b6..0000000
--- a/core/java/android/hardware/ICameraServiceListener.aidl
+++ /dev/null
@@ -1,28 +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 android.hardware;
-
-/** @hide */
-interface ICameraServiceListener
-{
-    /**
-     * Keep up-to-date with frameworks/av/include/camera/ICameraServiceListener.h
-     */
-    void onStatusChanged(int status, int cameraId);
-
-    void onTorchStatusChanged(int status, String cameraId);
-}
diff --git a/core/java/android/hardware/ICameraServiceProxy.aidl b/core/java/android/hardware/ICameraServiceProxy.aidl
deleted file mode 100644
index 0e654d5..0000000
--- a/core/java/android/hardware/ICameraServiceProxy.aidl
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 android.hardware;
-
-/**
- * Binder interface for the camera service proxy running in system_server.
- *
- * Keep in sync with frameworks/av/include/camera/ICameraServiceProxy.h
- *
- * @hide
- */
-interface ICameraServiceProxy
-{
-    /**
-     * Ping the service proxy to update the valid users for the camera service.
-     */
-    oneway void pingForUserUpdate();
-
-    /**
-     * Update the status of a camera device
-     */
-     oneway void notifyCameraState(String cameraId, int newCameraState);
-}
diff --git a/core/java/android/hardware/camera2/CameraAccessException.java b/core/java/android/hardware/camera2/CameraAccessException.java
index 933ce0d..f9b659c 100644
--- a/core/java/android/hardware/camera2/CameraAccessException.java
+++ b/core/java/android/hardware/camera2/CameraAccessException.java
@@ -114,12 +114,12 @@
     }
 
     public CameraAccessException(@AccessError int problem, String message) {
-        super(message);
+        super(getCombinedMessage(problem, message));
         mReason = problem;
     }
 
     public CameraAccessException(@AccessError int problem, String message, Throwable cause) {
-        super(message, cause);
+        super(getCombinedMessage(problem, message), cause);
         mReason = problem;
     }
 
@@ -151,4 +151,37 @@
         }
         return null;
     }
+
+    private static String getCombinedMessage(@AccessError int problem, String message) {
+        String problemString = getProblemString(problem);
+        return String.format("%s (%d): %s", problemString, problem, message);
+    }
+
+    private static String getProblemString(int problem) {
+        String problemString;
+        switch (problem) {
+            case CAMERA_IN_USE:
+                problemString = "CAMERA_IN_USE";
+                break;
+            case MAX_CAMERAS_IN_USE:
+                problemString = "MAX_CAMERAS_IN_USE";
+                break;
+            case CAMERA_DISCONNECTED:
+                problemString = "CAMERA_DISCONNECTED";
+                break;
+            case CAMERA_DISABLED:
+                problemString = "CAMERA_DISABLED";
+                break;
+            case CAMERA_ERROR:
+                problemString = "CAMERA_ERROR";
+                break;
+            case CAMERA_DEPRECATED_HAL:
+                problemString = "CAMERA_DEPRECATED_HAL";
+                break;
+            default:
+                problemString = "<UNKNOWN ERROR>";
+        }
+        return problemString;
+    }
+
 }
diff --git a/core/java/android/hardware/camera2/CameraManager.java b/core/java/android/hardware/camera2/CameraManager.java
index 51796eb..b3c8e3b 100644
--- a/core/java/android/hardware/camera2/CameraManager.java
+++ b/core/java/android/hardware/camera2/CameraManager.java
@@ -26,15 +26,14 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.legacy.CameraDeviceUserShim;
 import android.hardware.camera2.legacy.LegacyMetadataMapper;
-import android.hardware.camera2.utils.CameraServiceBinderDecorator;
-import android.hardware.camera2.utils.CameraRuntimeException;
-import android.hardware.camera2.utils.BinderHolder;
 import android.os.IBinder;
 import android.os.Binder;
+import android.os.DeadObjectException;
 import android.os.Handler;
 import android.os.Looper;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 import android.util.ArrayMap;
 
@@ -240,25 +239,19 @@
                 if (!supportsCamera2ApiLocked(cameraId)) {
                     // Legacy backwards compatibility path; build static info from the camera
                     // parameters
-                    String[] outParameters = new String[1];
+                    String parameters = cameraService.getLegacyParameters(id);
 
-                    cameraService.getLegacyParameters(id, /*out*/outParameters);
-                    String parameters = outParameters[0];
-
-                    CameraInfo info = new CameraInfo();
-                    cameraService.getCameraInfo(id, /*out*/info);
+                    CameraInfo info = cameraService.getCameraInfo(id);
 
                     characteristics = LegacyMetadataMapper.createCharacteristics(parameters, info);
                 } else {
                     // Normal path: Get the camera characteristics directly from the camera service
-                    CameraMetadataNative info = new CameraMetadataNative();
-
-                    cameraService.getCameraCharacteristics(id, info);
+                    CameraMetadataNative info = cameraService.getCameraCharacteristics(id);
 
                     characteristics = new CameraCharacteristics(info);
                 }
-            } catch (CameraRuntimeException e) {
-                throw e.asChecked();
+            } catch (ServiceSpecificException e) {
+                throwAsPublicException(e);
             } catch (RemoteException e) {
                 // Camera service died - act as if the camera was disconnected
                 throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
@@ -292,85 +285,83 @@
             throws CameraAccessException {
         CameraCharacteristics characteristics = getCameraCharacteristics(cameraId);
         CameraDevice device = null;
-        try {
 
-            synchronized (mLock) {
+        synchronized (mLock) {
 
-                ICameraDeviceUser cameraUser = null;
+            ICameraDeviceUser cameraUser = null;
 
-                android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
-                        new android.hardware.camera2.impl.CameraDeviceImpl(
-                                cameraId,
-                                callback,
-                                handler,
-                                characteristics);
+            android.hardware.camera2.impl.CameraDeviceImpl deviceImpl =
+                    new android.hardware.camera2.impl.CameraDeviceImpl(
+                        cameraId,
+                        callback,
+                        handler,
+                        characteristics);
 
-                BinderHolder holder = new BinderHolder();
+            ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
 
-                ICameraDeviceCallbacks callbacks = deviceImpl.getCallbacks();
-                int id = Integer.parseInt(cameraId);
-                try {
-                    if (supportsCamera2ApiLocked(cameraId)) {
-                        // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
-                        ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
-                        if (cameraService == null) {
-                            throw new CameraRuntimeException(
-                                CameraAccessException.CAMERA_DISCONNECTED,
-                                "Camera service is currently unavailable");
-                        }
-                        cameraService.connectDevice(callbacks, id,
-                                mContext.getOpPackageName(), USE_CALLING_UID, holder);
-                        cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
-                    } else {
-                        // Use legacy camera implementation for HAL1 devices
-                        Log.i(TAG, "Using legacy camera HAL.");
-                        cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
-                    }
-                } catch (CameraRuntimeException e) {
-                    if (e.getReason() == CameraAccessException.CAMERA_DEPRECATED_HAL) {
-                        throw new AssertionError("Should've gone down the shim path");
-                    } else if (e.getReason() == CameraAccessException.CAMERA_IN_USE ||
-                            e.getReason() == CameraAccessException.MAX_CAMERAS_IN_USE ||
-                            e.getReason() == CameraAccessException.CAMERA_DISABLED ||
-                            e.getReason() == CameraAccessException.CAMERA_DISCONNECTED ||
-                            e.getReason() == CameraAccessException.CAMERA_ERROR) {
-                        // Received one of the known connection errors
-                        // The remote camera device cannot be connected to, so
-                        // set the local camera to the startup error state
-                        deviceImpl.setRemoteFailure(e);
-
-                        if (e.getReason() == CameraAccessException.CAMERA_DISABLED ||
-                                e.getReason() == CameraAccessException.CAMERA_DISCONNECTED ||
-                                e.getReason() == CameraAccessException.CAMERA_IN_USE) {
-                            // Per API docs, these failures call onError and throw
-                            throw e.asChecked();
-                        }
-                    } else {
-                        // Unexpected failure - rethrow
-                        throw e;
-                    }
-                } catch (RemoteException e) {
-                    // Camera service died - act as if it's a CAMERA_DISCONNECTED case
-                    CameraRuntimeException ce = new CameraRuntimeException(
-                        CameraAccessException.CAMERA_DISCONNECTED,
-                        "Camera service is currently unavailable", e);
-                    deviceImpl.setRemoteFailure(ce);
-                    throw ce.asChecked();
-                }
-
-                // TODO: factor out callback to be non-nested, then move setter to constructor
-                // For now, calling setRemoteDevice will fire initial
-                // onOpened/onUnconfigured callbacks.
-                deviceImpl.setRemoteDevice(cameraUser);
-                device = deviceImpl;
+            int id;
+            try {
+                id = Integer.parseInt(cameraId);
+            } catch (NumberFormatException e) {
+                throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
+                        + cameraId);
             }
 
-        } catch (NumberFormatException e) {
-            throw new IllegalArgumentException("Expected cameraId to be numeric, but it was: "
-                    + cameraId);
-        } catch (CameraRuntimeException e) {
-            throw e.asChecked();
+            try {
+                if (supportsCamera2ApiLocked(cameraId)) {
+                    // Use cameraservice's cameradeviceclient implementation for HAL3.2+ devices
+                    ICameraService cameraService = CameraManagerGlobal.get().getCameraService();
+                    if (cameraService == null) {
+                        throw new ServiceSpecificException(
+                            ICameraService.ERROR_DISCONNECTED,
+                            "Camera service is currently unavailable");
+                    }
+                    cameraUser = cameraService.connectDevice(callbacks, id,
+                            mContext.getOpPackageName(), USE_CALLING_UID);
+                } else {
+                    // Use legacy camera implementation for HAL1 devices
+                    Log.i(TAG, "Using legacy camera HAL.");
+                    cameraUser = CameraDeviceUserShim.connectBinderShim(callbacks, id);
+                }
+            } catch (ServiceSpecificException e) {
+                if (e.errorCode == ICameraService.ERROR_DEPRECATED_HAL) {
+                    throw new AssertionError("Should've gone down the shim path");
+                } else if (e.errorCode == ICameraService.ERROR_CAMERA_IN_USE ||
+                        e.errorCode == ICameraService.ERROR_MAX_CAMERAS_IN_USE ||
+                        e.errorCode == ICameraService.ERROR_DISABLED ||
+                        e.errorCode == ICameraService.ERROR_DISCONNECTED ||
+                        e.errorCode == ICameraService.ERROR_INVALID_OPERATION) {
+                    // Received one of the known connection errors
+                    // The remote camera device cannot be connected to, so
+                    // set the local camera to the startup error state
+                    deviceImpl.setRemoteFailure(e);
+
+                    if (e.errorCode == ICameraService.ERROR_DISABLED ||
+                            e.errorCode == ICameraService.ERROR_DISCONNECTED ||
+                            e.errorCode == ICameraService.ERROR_CAMERA_IN_USE) {
+                        // Per API docs, these failures call onError and throw
+                        throwAsPublicException(e);
+                    }
+                } else {
+                    // Unexpected failure - rethrow
+                    throwAsPublicException(e);
+                }
+            } catch (RemoteException e) {
+                // Camera service died - act as if it's a CAMERA_DISCONNECTED case
+                ServiceSpecificException sse = new ServiceSpecificException(
+                    ICameraService.ERROR_DISCONNECTED,
+                    "Camera service is currently unavailable");
+                deviceImpl.setRemoteFailure(sse);
+                throwAsPublicException(sse);
+            }
+
+            // TODO: factor out callback to be non-nested, then move setter to constructor
+            // For now, calling setRemoteDevice will fire initial
+            // onOpened/onUnconfigured callbacks.
+            deviceImpl.setRemoteDevice(cameraUser);
+            device = deviceImpl;
         }
+
         return device;
     }
 
@@ -602,6 +593,56 @@
     }
 
     /**
+     * Convert ServiceSpecificExceptions and Binder RemoteExceptions from camera binder interfaces
+     * into the correct public exceptions.
+     *
+     * @hide
+     */
+    public static void throwAsPublicException(Throwable t) throws CameraAccessException {
+        if (t instanceof ServiceSpecificException) {
+            ServiceSpecificException e = (ServiceSpecificException) t;
+            int reason = CameraAccessException.CAMERA_ERROR;
+            switch(e.errorCode) {
+                case ICameraService.ERROR_DISCONNECTED:
+                    reason = CameraAccessException.CAMERA_DISCONNECTED;
+                    break;
+                case ICameraService.ERROR_DISABLED:
+                    reason = CameraAccessException.CAMERA_DISABLED;
+                    break;
+                case ICameraService.ERROR_CAMERA_IN_USE:
+                    reason = CameraAccessException.CAMERA_IN_USE;
+                    break;
+                case ICameraService.ERROR_MAX_CAMERAS_IN_USE:
+                    reason = CameraAccessException.MAX_CAMERAS_IN_USE;
+                    break;
+                case ICameraService.ERROR_DEPRECATED_HAL:
+                    reason = CameraAccessException.CAMERA_DEPRECATED_HAL;
+                    break;
+                case ICameraService.ERROR_ILLEGAL_ARGUMENT:
+                case ICameraService.ERROR_ALREADY_EXISTS:
+                    throw new IllegalArgumentException(e.getMessage(), e);
+                case ICameraService.ERROR_PERMISSION_DENIED:
+                    throw new SecurityException(e.getMessage(), e);
+                case ICameraService.ERROR_TIMED_OUT:
+                case ICameraService.ERROR_INVALID_OPERATION:
+                default:
+                    reason = CameraAccessException.CAMERA_ERROR;
+            }
+            throw new CameraAccessException(reason, e.getMessage(), e);
+        } else if (t instanceof DeadObjectException) {
+            throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
+                    "Camera service has died unexpectedly",
+                    t);
+        } else if (t instanceof RemoteException) {
+            throw new UnsupportedOperationException("An unknown RemoteException was thrown" +
+                    " which should never happen.", t);
+        } else if (t instanceof RuntimeException) {
+            RuntimeException e = (RuntimeException) t;
+            throw e;
+        }
+    }
+
+    /**
      * Return or create the list of currently connected camera devices.
      *
      * <p>In case of errors connecting to the camera service, will return an empty list.</p>
@@ -619,34 +660,32 @@
 
             try {
                 numCameras = cameraService.getNumberOfCameras(CAMERA_TYPE_ALL);
-            } catch(CameraRuntimeException e) {
-                throw e.asChecked();
+            } catch(ServiceSpecificException e) {
+                throwAsPublicException(e);
             } catch (RemoteException e) {
                 // camera service just died - if no camera service, then no devices
                 return deviceIdList;
             }
 
-            CameraMetadataNative info = new CameraMetadataNative();
             for (int i = 0; i < numCameras; ++i) {
                 // Non-removable cameras use integers starting at 0 for their
                 // identifiers
                 boolean isDeviceSupported = false;
                 try {
-                    cameraService.getCameraCharacteristics(i, info);
+                    CameraMetadataNative info = cameraService.getCameraCharacteristics(i);
                     if (!info.isEmpty()) {
                         isDeviceSupported = true;
                     } else {
                         throw new AssertionError("Expected to get non-empty characteristics");
                     }
-                } catch(IllegalArgumentException  e) {
-                    // Got a BAD_VALUE from service, meaning that this
-                    // device is not supported.
-                } catch(CameraRuntimeException e) {
+                } catch(ServiceSpecificException e) {
                     // DISCONNECTED means that the HAL reported an low-level error getting the
-                    // device info; skip listing the device.  Other errors,
+                    // device info; ILLEGAL_ARGUMENT means that this devices is not supported.
+                    // Skip listing the device.  Other errors,
                     // propagate exception onward
-                    if (e.getReason() != CameraAccessException.CAMERA_DISCONNECTED) {
-                        throw e.asChecked();
+                    if (e.errorCode != ICameraService.ERROR_DISCONNECTED ||
+                            e.errorCode != ICameraService.ERROR_ILLEGAL_ARGUMENT) {
+                        throwAsPublicException(e);
                     }
                 } catch(RemoteException e) {
                     // Camera service died - no devices to list
@@ -699,17 +738,7 @@
             // If no camera service, no support
             if (cameraService == null) return false;
 
-            int res = cameraService.supportsCameraApi(id, apiVersion);
-
-            if (res != CameraServiceBinderDecorator.NO_ERROR) {
-                throw new AssertionError("Unexpected value " + res);
-            }
-            return true;
-        } catch (CameraRuntimeException e) {
-            if (e.getReason() != CameraAccessException.CAMERA_DEPRECATED_HAL) {
-                throw e;
-            }
-            // API level is not supported
+            return cameraService.supportsCameraApi(id, apiVersion);
         } catch (RemoteException e) {
             // Camera service is now down, no support for any API level
         }
@@ -737,21 +766,6 @@
          */
         private static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
 
-        // Keep up-to-date with ICameraServiceListener.h
-
-        // Device physically unplugged
-        public static final int STATUS_NOT_PRESENT = 0;
-        // Device physically has been plugged in
-        // and the camera can be used exclusively
-        public static final int STATUS_PRESENT = 1;
-        // Device physically has been plugged in
-        // but it will not be connect-able until enumeration is complete
-        public static final int STATUS_ENUMERATING = 2;
-        // Camera is in use by another app and cannot be used exclusively
-        public static final int STATUS_NOT_AVAILABLE = 0x80000000;
-
-        // End enums shared with ICameraServiceListener.h
-
         // Camera ID -> Status map
         private final ArrayMap<String, Integer> mDeviceStatus = new ArrayMap<String, Integer>();
 
@@ -759,17 +773,6 @@
         private final ArrayMap<AvailabilityCallback, Handler> mCallbackMap =
             new ArrayMap<AvailabilityCallback, Handler>();
 
-        // Keep up-to-date with ICameraServiceListener.h
-
-        // torch mode has become not available to set via setTorchMode().
-        public static final int TORCH_STATUS_NOT_AVAILABLE = 0;
-        // torch mode is off and available to be turned on via setTorchMode().
-        public static final int TORCH_STATUS_AVAILABLE_OFF = 1;
-        // torch mode is on and available to be turned off via setTorchMode().
-        public static final int TORCH_STATUS_AVAILABLE_ON = 2;
-
-        // End enums shared with ICameraServiceListener.h
-
         // torch client binder to set the torch mode with.
         private Binder mTorchClientBinder = new Binder();
 
@@ -839,29 +842,20 @@
                 return;
             }
 
-            ICameraService cameraServiceRaw = ICameraService.Stub.asInterface(cameraServiceBinder);
-
-            /**
-             * Wrap the camera service in a decorator which automatically translates return codes
-             * into exceptions.
-             */
-            ICameraService cameraService =
-                CameraServiceBinderDecorator.newInstance(cameraServiceRaw);
+            ICameraService cameraService = ICameraService.Stub.asInterface(cameraServiceBinder);
 
             try {
-                CameraServiceBinderDecorator.throwOnError(
-                        CameraMetadataNative.nativeSetupGlobalVendorTagDescriptor());
-            } catch (CameraRuntimeException e) {
-                handleRecoverableSetupErrors(e, "Failed to set up vendor tags");
+                CameraMetadataNative.setupGlobalVendorTagDescriptor();
+            } catch (ServiceSpecificException e) {
+                handleRecoverableSetupErrors(e);
             }
 
             try {
                 cameraService.addListener(this);
                 mCameraService = cameraService;
-            } catch(CameraRuntimeException e) {
+            } catch(ServiceSpecificException e) {
                 // Unexpected failure
-                throw new IllegalStateException("Failed to register a camera service listener",
-                        e.asChecked());
+                throw new IllegalStateException("Failed to register a camera service listener", e);
             } catch (RemoteException e) {
                 // Camera service is now down, leave mCameraService as null
             }
@@ -881,16 +875,9 @@
                 }
 
                 try {
-                    int status = cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder);
-                } catch(CameraRuntimeException e) {
-                    int problem = e.getReason();
-                    switch (problem) {
-                        case CameraAccessException.CAMERA_ERROR:
-                            throw new IllegalArgumentException(
-                                    "the camera device doesn't have a flash unit.");
-                        default:
-                            throw e.asChecked();
-                    }
+                    cameraService.setTorchMode(cameraId, enabled, mTorchClientBinder);
+                } catch(ServiceSpecificException e) {
+                    throwAsPublicException(e);
                 } catch (RemoteException e) {
                     throw new CameraAccessException(CameraAccessException.CAMERA_DISCONNECTED,
                             "Camera service is currently unavailable");
@@ -898,21 +885,19 @@
             }
         }
 
-        private void handleRecoverableSetupErrors(CameraRuntimeException e, String msg) {
-            int problem = e.getReason();
-            switch (problem) {
-                case CameraAccessException.CAMERA_DISCONNECTED:
-                    String errorMsg = CameraAccessException.getDefaultMessage(problem);
-                    Log.w(TAG, msg + ": " + errorMsg);
+        private void handleRecoverableSetupErrors(ServiceSpecificException e) {
+            switch (e.errorCode) {
+                case ICameraService.ERROR_DISCONNECTED:
+                    Log.w(TAG, e.getMessage());
                     break;
                 default:
-                    throw new IllegalStateException(msg, e.asChecked());
+                    throw new IllegalStateException(e);
             }
         }
 
         private boolean isAvailable(int status) {
             switch (status) {
-                case STATUS_PRESENT:
+                case ICameraServiceListener.STATUS_PRESENT:
                     return true;
                 default:
                     return false;
@@ -921,10 +906,10 @@
 
         private boolean validStatus(int status) {
             switch (status) {
-                case STATUS_NOT_PRESENT:
-                case STATUS_PRESENT:
-                case STATUS_ENUMERATING:
-                case STATUS_NOT_AVAILABLE:
+                case ICameraServiceListener.STATUS_NOT_PRESENT:
+                case ICameraServiceListener.STATUS_PRESENT:
+                case ICameraServiceListener.STATUS_ENUMERATING:
+                case ICameraServiceListener.STATUS_NOT_AVAILABLE:
                     return true;
                 default:
                     return false;
@@ -933,9 +918,9 @@
 
         private boolean validTorchStatus(int status) {
             switch (status) {
-                case TORCH_STATUS_NOT_AVAILABLE:
-                case TORCH_STATUS_AVAILABLE_ON:
-                case TORCH_STATUS_AVAILABLE_OFF:
+                case ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE:
+                case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON:
+                case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF:
                     return true;
                 default:
                     return false;
@@ -966,14 +951,14 @@
         private void postSingleTorchUpdate(final TorchCallback callback, final Handler handler,
                 final String id, final int status) {
             switch(status) {
-                case TORCH_STATUS_AVAILABLE_ON:
-                case TORCH_STATUS_AVAILABLE_OFF:
+                case ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON:
+                case ICameraServiceListener.TORCH_STATUS_AVAILABLE_OFF:
                     handler.post(
                             new Runnable() {
                                 @Override
                                 public void run() {
                                     callback.onTorchModeChanged(id, status ==
-                                            TORCH_STATUS_AVAILABLE_ON);
+                                            ICameraServiceListener.TORCH_STATUS_AVAILABLE_ON);
                                 }
                             });
                     break;
@@ -1220,11 +1205,12 @@
                 // and torch statuses will be updated.
                 for (int i = 0; i < mDeviceStatus.size(); i++) {
                     String cameraId = mDeviceStatus.keyAt(i);
-                    onStatusChangedLocked(STATUS_NOT_PRESENT, cameraId);
+                    onStatusChangedLocked(ICameraServiceListener.STATUS_NOT_PRESENT, cameraId);
                 }
                 for (int i = 0; i < mTorchStatus.size(); i++) {
                     String cameraId = mTorchStatus.keyAt(i);
-                    onTorchStatusChangedLocked(TORCH_STATUS_NOT_AVAILABLE, cameraId);
+                    onTorchStatusChangedLocked(ICameraServiceListener.TORCH_STATUS_NOT_AVAILABLE,
+                            cameraId);
                 }
 
                 scheduleCameraServiceReconnectionLocked();
diff --git a/core/java/android/hardware/camera2/CaptureRequest.aidl b/core/java/android/hardware/camera2/CaptureRequest.aidl
deleted file mode 100644
index 0b7d5ba..0000000
--- a/core/java/android/hardware/camera2/CaptureRequest.aidl
+++ /dev/null
@@ -1,20 +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 android.hardware.camera2;
-
-/** @hide */
-parcelable CaptureRequest;
diff --git a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl b/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
deleted file mode 100644
index 151c918..0000000
--- a/core/java/android/hardware/camera2/ICameraDeviceCallbacks.aidl
+++ /dev/null
@@ -1,35 +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 android.hardware.camera2;
-
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.impl.CaptureResultExtras;
-
-/** @hide */
-interface ICameraDeviceCallbacks
-{
-    /**
-     * Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceCallbacks.h
-     */
-
-    oneway void onDeviceError(int errorCode, in CaptureResultExtras resultExtras);
-    oneway void onDeviceIdle();
-    oneway void onCaptureStarted(in CaptureResultExtras resultExtras, long timestamp);
-    oneway void onResultReceived(in CameraMetadataNative result,
-                                 in CaptureResultExtras resultExtras);
-    oneway void onPrepared(int streamId);
-}
diff --git a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl b/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
deleted file mode 100644
index c9c9abc..0000000
--- a/core/java/android/hardware/camera2/ICameraDeviceUser.aidl
+++ /dev/null
@@ -1,107 +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 android.hardware.camera2;
-
-import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.params.OutputConfiguration;
-import android.hardware.camera2.utils.LongParcelable;
-import android.view.Surface;
-
-/** @hide */
-interface ICameraDeviceUser
-{
-    /**
-     * Keep up-to-date with frameworks/av/include/camera/camera2/ICameraDeviceUser.h and
-     * frameworks/base/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
-     */
-    void disconnect();
-
-    // ints here are status_t
-
-    // non-negative value is the requestId. negative value is status_t
-    int submitRequest(in CaptureRequest request, boolean streaming,
-                      out LongParcelable lastFrameNumber);
-
-    int submitRequestList(in List<CaptureRequest> requestList, boolean streaming,
-                          out LongParcelable lastFrameNumber);
-
-    int cancelRequest(int requestId, out LongParcelable lastFrameNumber);
-
-    /**
-     * Begin the device configuration.
-     *
-     * <p>
-     * beginConfigure must be called before any call to deleteStream, createStream,
-     * or endConfigure.  It is not valid to call this when the device is not idle.
-     * <p>
-     */
-    int beginConfigure();
-
-    /**
-     * End the device configuration.
-     *
-     * <p>
-     * endConfigure must be called after stream configuration is complete (i.e. after
-     * a call to beginConfigure and subsequent createStream/deleteStream calls).  This
-     * must be called before any requests can be submitted.
-     * <p>
-     */
-    int endConfigure(boolean isConstrainedHighSpeed);
-
-    int deleteStream(int streamId);
-
-    // non-negative value is the stream ID. negative value is status_t
-    int createStream(in OutputConfiguration outputConfiguration);
-
-    /**
-     * Create an input stream
-     *
-     * <p>Create an input stream of width, height, and format</p>
-     *
-     * @param width Width of the input buffers
-     * @param height Height of the input buffers
-     * @param format Format of the input buffers. One of HAL_PIXEL_FORMAT_*.
-     *
-     * @return stream ID if it's a non-negative value. status_t if it's a negative value.
-     */
-    int createInputStream(int width, int height, int format);
-
-    /**
-     * Get the surface of the input stream.
-     *
-     * <p>It's valid to call this method only after a stream configuration is completed
-     * successfully and the stream configuration includes a input stream.</p>
-     *
-     * @param surface An output argument for the surface of the input stream buffer queue.
-     */
-    int getInputSurface(out Surface surface);
-
-    int createDefaultRequest(int templateId, out CameraMetadataNative request);
-
-    int getCameraInfo(out CameraMetadataNative info);
-
-    int waitUntilIdle();
-
-    int flush(out LongParcelable lastFrameNumber);
-
-    int prepare(int streamId);
-
-    int tearDown(int streamId);
-
-    int prepare2(int maxCount, int streamId);
-}
diff --git a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
index 3aba0d1..00dd780 100644
--- a/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
+++ b/core/java/android/hardware/camera2/impl/CameraDeviceImpl.java
@@ -33,14 +33,14 @@
 import android.hardware.camera2.params.OutputConfiguration;
 import android.hardware.camera2.params.ReprocessFormatsMap;
 import android.hardware.camera2.params.StreamConfigurationMap;
-import android.hardware.camera2.utils.CameraBinderDecorator;
-import android.hardware.camera2.utils.CameraRuntimeException;
-import android.hardware.camera2.utils.LongParcelable;
+import android.hardware.camera2.utils.SubmitInfo;
 import android.hardware.camera2.utils.SurfaceUtils;
+import android.hardware.ICameraService;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 import android.util.Range;
 import android.util.Size;
@@ -70,7 +70,7 @@
     private static final int REQUEST_ID_NONE = -1;
 
     // TODO: guard every function with if (!mRemoteDevice) check (if it was closed)
-    private ICameraDeviceUser mRemoteDevice;
+    private ICameraDeviceUserWrapper mRemoteDevice;
 
     // Lock to synchronize cross-thread access to device public interface
     final Object mInterfaceLock = new Object(); // access from this class and Session only!
@@ -267,7 +267,7 @@
             // If setRemoteFailure already called, do nothing
             if (mInError) return;
 
-            mRemoteDevice = CameraBinderDecorator.newInstance(remoteDevice);
+            mRemoteDevice = new ICameraDeviceUserWrapper(remoteDevice);
 
             mDeviceHandler.post(mCallOnOpened);
             mDeviceHandler.post(mCallOnUnconfigured);
@@ -280,28 +280,29 @@
      * <p>This places the camera device in the error state and informs the callback.
      * Use in place of setRemoteDevice() when startup fails.</p>
      */
-    public void setRemoteFailure(final CameraRuntimeException failure) {
+    public void setRemoteFailure(final ServiceSpecificException failure) {
         int failureCode = StateCallback.ERROR_CAMERA_DEVICE;
         boolean failureIsError = true;
 
-        switch (failure.getReason()) {
-            case CameraAccessException.CAMERA_IN_USE:
+        switch (failure.errorCode) {
+            case ICameraService.ERROR_CAMERA_IN_USE:
                 failureCode = StateCallback.ERROR_CAMERA_IN_USE;
                 break;
-            case CameraAccessException.MAX_CAMERAS_IN_USE:
+            case ICameraService.ERROR_MAX_CAMERAS_IN_USE:
                 failureCode = StateCallback.ERROR_MAX_CAMERAS_IN_USE;
                 break;
-            case CameraAccessException.CAMERA_DISABLED:
+            case ICameraService.ERROR_DISABLED:
                 failureCode = StateCallback.ERROR_CAMERA_DISABLED;
                 break;
-            case CameraAccessException.CAMERA_DISCONNECTED:
+            case ICameraService.ERROR_DISCONNECTED:
                 failureIsError = false;
                 break;
-            case CameraAccessException.CAMERA_ERROR:
+            case ICameraService.ERROR_INVALID_OPERATION:
                 failureCode = StateCallback.ERROR_CAMERA_DEVICE;
                 break;
             default:
-                Log.wtf(TAG, "Unknown failure in opening camera device: " + failure.getReason());
+                Log.e(TAG, "Unexpected failure in opening camera device: " + failure.errorCode +
+                        failure.getMessage());
                 break;
         }
         final int code = failureCode;
@@ -430,27 +431,20 @@
                     }
                 }
 
-                try {
-                    mRemoteDevice.endConfigure(isConstrainedHighSpeed);
-                }
-                catch (IllegalArgumentException e) {
-                    // OK. camera service can reject stream config if it's not supported by HAL
-                    // This is only the result of a programmer misusing the camera2 api.
-                    Log.w(TAG, "Stream configuration failed");
-                    return false;
-                }
+                mRemoteDevice.endConfigure(isConstrainedHighSpeed);
 
                 success = true;
-            } catch (CameraRuntimeException e) {
-                if (e.getReason() == CAMERA_IN_USE) {
-                    throw new IllegalStateException("The camera is currently busy." +
-                            " You must wait until the previous operation completes.");
-                }
-
-                throw e.asChecked();
-            } catch (RemoteException e) {
-                // impossible
+            } catch (IllegalArgumentException e) {
+                // OK. camera service can reject stream config if it's not supported by HAL
+                // This is only the result of a programmer misusing the camera2 api.
+                Log.w(TAG, "Stream configuration failed due to: " + e.getMessage());
                 return false;
+            } catch (CameraAccessException e) {
+                if (e.getReason() == CameraAccessException.CAMERA_IN_USE) {
+                    throw new IllegalStateException("The camera is currently busy." +
+                            " You must wait until the previous operation completes.", e);
+                }
+                throw e;
             } finally {
                 if (success && outputs.size() > 0) {
                     mDeviceHandler.post(mCallOnIdle);
@@ -594,12 +588,7 @@
                 configureSuccess = configureStreamsChecked(inputConfig, outputConfigurations,
                         isConstrainedHighSpeed);
                 if (configureSuccess == true && inputConfig != null) {
-                    input = new Surface();
-                    try {
-                        mRemoteDevice.getInputSurface(/*out*/input);
-                    } catch (CameraRuntimeException e) {
-                        e.asChecked();
-                    }
+                    input = mRemoteDevice.getInputSurface();
                 }
             } catch (CameraAccessException e) {
                 configureSuccess = false;
@@ -608,9 +597,6 @@
                 if (DEBUG) {
                     Log.v(TAG, "createCaptureSession - failed with exception ", e);
                 }
-            } catch (RemoteException e) {
-                // impossible
-                return;
             }
 
             List<Surface> outSurfaces = new ArrayList<>(outputConfigurations.size());
@@ -655,16 +641,9 @@
         synchronized(mInterfaceLock) {
             checkIfCameraClosedOrInError();
 
-            CameraMetadataNative templatedRequest = new CameraMetadataNative();
+            CameraMetadataNative templatedRequest = null;
 
-            try {
-                mRemoteDevice.createDefaultRequest(templateType, /*out*/templatedRequest);
-            } catch (CameraRuntimeException e) {
-                throw e.asChecked();
-            } catch (RemoteException e) {
-                // impossible
-                return null;
-            }
+            templatedRequest = mRemoteDevice.createDefaultRequest(templateType);
 
             CaptureRequest.Builder builder = new CaptureRequest.Builder(
                     templatedRequest, /*reprocess*/false, CameraCaptureSession.SESSION_ID_NONE);
@@ -701,14 +680,8 @@
             if (streamId == -1) {
                 throw new IllegalArgumentException("Surface is not part of this session");
             }
-            try {
-                mRemoteDevice.prepare(streamId);
-            } catch (CameraRuntimeException e) {
-                throw e.asChecked();
-            } catch (RemoteException e) {
-                // impossible
-                return;
-            }
+
+            mRemoteDevice.prepare(streamId);
         }
     }
 
@@ -728,14 +701,8 @@
             if (streamId == -1) {
                 throw new IllegalArgumentException("Surface is not part of this session");
             }
-            try {
-                mRemoteDevice.prepare2(maxCount, streamId);
-            } catch (CameraRuntimeException e) {
-                throw e.asChecked();
-            } catch (RemoteException e) {
-                // impossible
-                return;
-            }
+
+            mRemoteDevice.prepare2(maxCount, streamId);
         }
     }
 
@@ -753,14 +720,8 @@
             if (streamId == -1) {
                 throw new IllegalArgumentException("Surface is not part of this session");
             }
-            try {
-                mRemoteDevice.tearDown(streamId);
-            } catch (CameraRuntimeException e) {
-                throw e.asChecked();
-            } catch (RemoteException e) {
-                // impossible
-                return;
-            }
+
+            mRemoteDevice.tearDown(streamId);
         }
     }
 
@@ -875,45 +836,37 @@
 
         synchronized(mInterfaceLock) {
             checkIfCameraClosedOrInError();
-            int requestId;
-
             if (repeating) {
                 stopRepeating();
             }
 
-            LongParcelable lastFrameNumberRef = new LongParcelable();
-            try {
-                requestId = mRemoteDevice.submitRequestList(requestList, repeating,
-                        /*out*/lastFrameNumberRef);
-                if (DEBUG) {
-                    Log.v(TAG, "last frame number " + lastFrameNumberRef.getNumber());
-                }
-            } catch (CameraRuntimeException e) {
-                throw e.asChecked();
-            } catch (RemoteException e) {
-                // impossible
-                return -1;
+            SubmitInfo requestInfo;
+
+            CaptureRequest[] requestArray = requestList.toArray(new CaptureRequest[requestList.size()]);
+            requestInfo = mRemoteDevice.submitRequestList(requestArray, repeating);
+            if (DEBUG) {
+                Log.v(TAG, "last frame number " + requestInfo.getLastFrameNumber());
             }
 
             if (callback != null) {
-                mCaptureCallbackMap.put(requestId, new CaptureCallbackHolder(callback,
-                        requestList, handler, repeating, mNextSessionId - 1));
+                mCaptureCallbackMap.put(requestInfo.getRequestId(),
+                        new CaptureCallbackHolder(
+                            callback, requestList, handler, repeating, mNextSessionId - 1));
             } else {
                 if (DEBUG) {
-                    Log.d(TAG, "Listen for request " + requestId + " is null");
+                    Log.d(TAG, "Listen for request " + requestInfo.getRequestId() + " is null");
                 }
             }
 
-            long lastFrameNumber = lastFrameNumberRef.getNumber();
-
             if (repeating) {
                 if (mRepeatingRequestId != REQUEST_ID_NONE) {
-                    checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
+                    checkEarlyTriggerSequenceComplete(mRepeatingRequestId,
+                            requestInfo.getLastFrameNumber());
                 }
-                mRepeatingRequestId = requestId;
+                mRepeatingRequestId = requestInfo.getRequestId();
             } else {
-                mRequestLastFrameNumbersList.add(new RequestLastFrameNumbersHolder(requestList,
-                        requestId, lastFrameNumber));
+                mRequestLastFrameNumbersList.add(
+                    new RequestLastFrameNumbersHolder(requestList, requestInfo));
             }
 
             if (mIdle) {
@@ -921,7 +874,7 @@
             }
             mIdle = false;
 
-            return requestId;
+            return requestInfo.getRequestId();
         }
     }
 
@@ -949,19 +902,9 @@
                 int requestId = mRepeatingRequestId;
                 mRepeatingRequestId = REQUEST_ID_NONE;
 
-                try {
-                    LongParcelable lastFrameNumberRef = new LongParcelable();
-                    mRemoteDevice.cancelRequest(requestId, /*out*/lastFrameNumberRef);
-                    long lastFrameNumber = lastFrameNumberRef.getNumber();
+                long lastFrameNumber = mRemoteDevice.cancelRequest(requestId);
 
-                    checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
-
-                } catch (CameraRuntimeException e) {
-                    throw e.asChecked();
-                } catch (RemoteException e) {
-                    // impossible
-                    return;
-                }
+                checkEarlyTriggerSequenceComplete(requestId, lastFrameNumber);
             }
         }
     }
@@ -974,14 +917,8 @@
             if (mRepeatingRequestId != REQUEST_ID_NONE) {
                 throw new IllegalStateException("Active repeating request ongoing");
             }
-            try {
-                mRemoteDevice.waitUntilIdle();
-            } catch (CameraRuntimeException e) {
-                throw e.asChecked();
-            } catch (RemoteException e) {
-                // impossible
-                return;
-            }
+
+            mRemoteDevice.waitUntilIdle();
         }
     }
 
@@ -997,19 +934,11 @@
                 mDeviceHandler.post(mCallOnIdle);
                 return;
             }
-            try {
-                LongParcelable lastFrameNumberRef = new LongParcelable();
-                mRemoteDevice.flush(/*out*/lastFrameNumberRef);
-                if (mRepeatingRequestId != REQUEST_ID_NONE) {
-                    long lastFrameNumber = lastFrameNumberRef.getNumber();
-                    checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
-                    mRepeatingRequestId = REQUEST_ID_NONE;
-                }
-            } catch (CameraRuntimeException e) {
-                throw e.asChecked();
-            } catch (RemoteException e) {
-                // impossible
-                return;
+
+            long lastFrameNumber = mRemoteDevice.flush();
+            if (mRepeatingRequestId != REQUEST_ID_NONE) {
+                checkEarlyTriggerSequenceComplete(mRepeatingRequestId, lastFrameNumber);
+                mRepeatingRequestId = REQUEST_ID_NONE;
             }
         }
     }
@@ -1021,14 +950,8 @@
                 return;
             }
 
-            try {
-                if (mRemoteDevice != null) {
-                    mRemoteDevice.disconnect();
-                }
-            } catch (CameraRuntimeException e) {
-                Log.e(TAG, "Exception while closing: ", e.asChecked());
-            } catch (RemoteException e) {
-                // impossible
+            if (mRemoteDevice != null) {
+                mRemoteDevice.disconnect();
             }
 
             // Only want to fire the onClosed callback once;
@@ -1297,14 +1220,14 @@
          * Create a request-last-frame-numbers holder with a list of requests, request ID, and
          * the last frame number returned by camera service.
          */
-        public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, int requestId,
-                long lastFrameNumber) {
+        public RequestLastFrameNumbersHolder(List<CaptureRequest> requestList, SubmitInfo requestInfo) {
             long lastRegularFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
             long lastReprocessFrameNumber = CaptureCallback.NO_FRAMES_CAPTURED;
-            long frameNumber = lastFrameNumber;
+            long frameNumber = requestInfo.getLastFrameNumber();
 
-            if (lastFrameNumber < requestList.size() - 1) {
-                throw new IllegalArgumentException("lastFrameNumber: " + lastFrameNumber +
+            if (requestInfo.getLastFrameNumber() < requestList.size() - 1) {
+                throw new IllegalArgumentException(
+                        "lastFrameNumber: " + requestInfo.getLastFrameNumber() +
                         " should be at least " + (requestList.size() - 1) + " for the number of " +
                         " requests in the list: " + requestList.size());
             }
@@ -1330,7 +1253,7 @@
 
             mLastRegularFrameNumber = lastRegularFrameNumber;
             mLastReprocessFrameNumber = lastReprocessFrameNumber;
-            mRequestId = requestId;
+            mRequestId = requestInfo.getRequestId();
         }
 
         /**
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.aidl b/core/java/android/hardware/camera2/impl/CameraMetadataNative.aidl
deleted file mode 100644
index 4a89129..0000000
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.aidl
+++ /dev/null
@@ -1,20 +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 android.hardware.camera2.impl;
-
-/** @hide */
-parcelable CameraMetadataNative;
diff --git a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
index 12a2910..79eac26 100644
--- a/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
+++ b/core/java/android/hardware/camera2/impl/CameraMetadataNative.java
@@ -58,6 +58,7 @@
 import android.location.LocationManager;
 import android.os.Parcelable;
 import android.os.Parcel;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 import android.util.Size;
 
@@ -363,13 +364,24 @@
      * Set the global client-side vendor tag descriptor to allow use of vendor
      * tags in camera applications.
      *
-     * @return int A native status_t value corresponding to one of the
-     * {@link CameraBinderDecorator} integer constants.
-     * @see CameraBinderDecorator#throwOnError
-     *
+     * @throws ServiceSpecificException
      * @hide
      */
-    public static native int nativeSetupGlobalVendorTagDescriptor();
+    public static void setupGlobalVendorTagDescriptor() throws ServiceSpecificException {
+        int err = nativeSetupGlobalVendorTagDescriptor();
+        if (err != 0) {
+            throw new ServiceSpecificException(err, "Failure to set up global vendor tags");
+        }
+    }
+
+    /**
+     * Set the global client-side vendor tag descriptor to allow use of vendor
+     * tags in camera applications.
+     *
+     * @return int An error code corresponding to one of the
+     * {@link ICameraService} error constants, or 0 on success.
+     */
+    private static native int nativeSetupGlobalVendorTagDescriptor();
 
     /**
      * Set a camera metadata field to a value. The field definitions can be
diff --git a/core/java/android/hardware/camera2/impl/CaptureResultExtras.aidl b/core/java/android/hardware/camera2/impl/CaptureResultExtras.aidl
deleted file mode 100644
index ebc812a..0000000
--- a/core/java/android/hardware/camera2/impl/CaptureResultExtras.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.camera2.impl;
-
-/** @hide */
-parcelable CaptureResultExtras;
diff --git a/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
new file mode 100644
index 0000000..ddc3fd1
--- /dev/null
+++ b/core/java/android/hardware/camera2/impl/ICameraDeviceUserWrapper.java
@@ -0,0 +1,212 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.impl;
+
+import static android.hardware.camera2.CameraAccessException.CAMERA_DISABLED;
+import static android.hardware.camera2.CameraAccessException.CAMERA_DISCONNECTED;
+import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
+import static android.hardware.camera2.CameraAccessException.CAMERA_ERROR;
+import static android.hardware.camera2.CameraAccessException.MAX_CAMERAS_IN_USE;
+import static android.hardware.camera2.CameraAccessException.CAMERA_DEPRECATED_HAL;
+
+import android.hardware.ICameraService;
+import android.hardware.camera2.CameraManager;
+import android.hardware.camera2.CameraAccessException;
+import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.ICameraDeviceUser;
+import android.hardware.camera2.impl.CameraMetadataNative;
+import android.hardware.camera2.params.OutputConfiguration;
+import android.hardware.camera2.utils.SubmitInfo;
+import android.os.RemoteException;
+import android.view.Surface;
+
+/**
+ * A wrapper around ICameraDeviceUser.
+ *
+ * Mainly used to convert ServiceSpecificExceptions to the correct
+ * checked / unchecked exception.
+ *
+ * @hide
+ */
+public class ICameraDeviceUserWrapper {
+
+    private final ICameraDeviceUser mRemoteDevice;
+
+    public ICameraDeviceUserWrapper(ICameraDeviceUser remoteDevice) {
+        if (remoteDevice == null) {
+            throw new NullPointerException("Remote device may not be null");
+        }
+        mRemoteDevice = remoteDevice;
+    }
+
+    public void disconnect()  {
+        try {
+            mRemoteDevice.disconnect();
+        } catch (RemoteException t) {
+            // ignore binder errors for disconnect
+        }
+    }
+
+    public SubmitInfo submitRequest(CaptureRequest request, boolean streaming)
+            throws CameraAccessException  {
+        try {
+            return mRemoteDevice.submitRequest(request, streaming);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public SubmitInfo submitRequestList(CaptureRequest[] requestList, boolean streaming)
+            throws CameraAccessException {
+        try {
+            return mRemoteDevice.submitRequestList(requestList, streaming);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public long cancelRequest(int requestId) throws CameraAccessException {
+        try {
+            return mRemoteDevice.cancelRequest(requestId);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public void beginConfigure() throws CameraAccessException {
+        try {
+            mRemoteDevice.beginConfigure();
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public void endConfigure(boolean isConstrainedHighSpeed) throws CameraAccessException {
+        try {
+            mRemoteDevice.endConfigure(isConstrainedHighSpeed);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public void deleteStream(int streamId) throws CameraAccessException {
+        try {
+            mRemoteDevice.deleteStream(streamId);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public int createStream(OutputConfiguration outputConfiguration)
+            throws CameraAccessException {
+        try {
+            return mRemoteDevice.createStream(outputConfiguration);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public int createInputStream(int width, int height, int format) throws CameraAccessException {
+        try {
+            return mRemoteDevice.createInputStream(width, height, format);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public Surface getInputSurface() throws CameraAccessException {
+        try {
+            return mRemoteDevice.getInputSurface();
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public CameraMetadataNative createDefaultRequest(int templateId) throws CameraAccessException {
+        try {
+            return mRemoteDevice.createDefaultRequest(templateId);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public CameraMetadataNative getCameraInfo() throws CameraAccessException {
+        try {
+            return mRemoteDevice.getCameraInfo();
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public void waitUntilIdle() throws CameraAccessException {
+        try {
+            mRemoteDevice.waitUntilIdle();
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public long flush() throws CameraAccessException {
+        try {
+            return mRemoteDevice.flush();
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public void prepare(int streamId) throws CameraAccessException {
+        try {
+            mRemoteDevice.prepare(streamId);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public void tearDown(int streamId) throws CameraAccessException {
+        try {
+            mRemoteDevice.tearDown(streamId);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+    public void prepare2(int maxCount, int streamId) throws CameraAccessException {
+        try {
+            mRemoteDevice.prepare2(maxCount, streamId);
+        } catch (Throwable t) {
+            CameraManager.throwAsPublicException(t);
+            throw new UnsupportedOperationException("Unexpected exception", t);
+        }
+    }
+
+
+}
diff --git a/core/java/android/hardware/camera2/legacy/BurstHolder.java b/core/java/android/hardware/camera2/legacy/BurstHolder.java
index e7b3682..23efe15 100644
--- a/core/java/android/hardware/camera2/legacy/BurstHolder.java
+++ b/core/java/android/hardware/camera2/legacy/BurstHolder.java
@@ -35,10 +35,10 @@
      *
      * @param requestId id of the burst request.
      * @param repeating true if this burst is repeating.
-     * @param requests a {@link List} of {@link CaptureRequest}s in this burst.
+     * @param requests the array of {@link CaptureRequest}s for this burst.
      * @param jpegSurfaceIds a {@link Collection} of IDs for the surfaces that have jpeg outputs.
      */
-    public BurstHolder(int requestId, boolean repeating, List<CaptureRequest> requests,
+    public BurstHolder(int requestId, boolean repeating, CaptureRequest[] requests,
                        Collection<Long> jpegSurfaceIds) {
         mRequestBuilders = new ArrayList<>();
         int i = 0;
diff --git a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
index 798c941..d01c275 100644
--- a/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
+++ b/core/java/android/hardware/camera2/legacy/CameraDeviceUserShim.java
@@ -16,6 +16,7 @@
 
 package android.hardware.camera2.legacy;
 
+import android.hardware.ICameraService;
 import android.hardware.Camera;
 import android.hardware.Camera.CameraInfo;
 import android.hardware.camera2.CameraAccessException;
@@ -23,12 +24,10 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
-import android.hardware.camera2.utils.LongParcelable;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
 import android.hardware.camera2.params.OutputConfiguration;
-import android.hardware.camera2.utils.CameraBinderDecorator;
-import android.hardware.camera2.utils.CameraRuntimeException;
+import android.hardware.camera2.utils.SubmitInfo;
 import android.os.ConditionVariable;
 import android.os.IBinder;
 import android.os.Looper;
@@ -36,6 +35,7 @@
 import android.os.HandlerThread;
 import android.os.Message;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.Surface;
@@ -93,7 +93,7 @@
 
     private static int translateErrorsFromCamera1(int errorCode) {
         if (errorCode == -EACCES) {
-            return CameraBinderDecorator.PERMISSION_DENIED;
+            return ICameraService.ERROR_PERMISSION_DENIED;
         }
 
         return errorCode;
@@ -173,7 +173,7 @@
          *
          * @return int error code
          *
-         * @throws CameraRuntimeException if the camera open times out with ({@code CAMERA_ERROR})
+         * @throws ServiceSpecificException if the camera open times out with ({@code CAMERA_ERROR})
          */
         public int waitForOpen(int timeoutMs) {
             // Block until the camera is open asynchronously
@@ -186,7 +186,7 @@
                     Log.e(TAG, "connectBinderShim - Failed to release camera after timeout ", e);
                 }
 
-                throw new CameraRuntimeException(CameraAccessException.CAMERA_ERROR);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION);
             }
 
             return mInitErrors;
@@ -344,7 +344,7 @@
         Camera legacyCamera = init.getCamera();
 
         // Check errors old HAL initialization
-        CameraBinderDecorator.throwOnError(initErrors);
+        LegacyExceptionUtils.throwOnServiceError(initErrors);
 
         // Disable shutter sounds (this will work unconditionally) for api2 clients
         legacyCamera.disableShutterSound();
@@ -356,8 +356,8 @@
         try {
             legacyParameters = legacyCamera.getParameters();
         } catch (RuntimeException e) {
-            throw new CameraRuntimeException(CameraAccessException.CAMERA_ERROR,
-                    "Unable to get initial parameters", e);
+            throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION,
+                    "Unable to get initial parameters: " + e.getMessage());
         }
 
         CameraCharacteristics characteristics =
@@ -386,101 +386,106 @@
     }
 
     @Override
-    public int submitRequest(CaptureRequest request, boolean streaming,
-                             /*out*/LongParcelable lastFrameNumber) {
+    public SubmitInfo submitRequest(CaptureRequest request, boolean streaming) {
         if (DEBUG) {
             Log.d(TAG, "submitRequest called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot submit request, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot submit request, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         synchronized(mConfigureLock) {
             if (mConfiguring) {
-                Log.e(TAG, "Cannot submit request, configuration change in progress.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot submit request, configuration change in progress.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
             }
         }
-        return mLegacyDevice.submitRequest(request, streaming, lastFrameNumber);
+        return mLegacyDevice.submitRequest(request, streaming);
     }
 
     @Override
-    public int submitRequestList(List<CaptureRequest> request, boolean streaming,
-                                 /*out*/LongParcelable lastFrameNumber) {
+    public SubmitInfo submitRequestList(CaptureRequest[] request, boolean streaming) {
         if (DEBUG) {
             Log.d(TAG, "submitRequestList called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot submit request list, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot submit request list, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         synchronized(mConfigureLock) {
             if (mConfiguring) {
-                Log.e(TAG, "Cannot submit request, configuration change in progress.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot submit request, configuration change in progress.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
             }
         }
-        return mLegacyDevice.submitRequestList(request, streaming, lastFrameNumber);
+        return mLegacyDevice.submitRequestList(request, streaming);
     }
 
     @Override
-    public int cancelRequest(int requestId, /*out*/LongParcelable lastFrameNumber) {
+    public long cancelRequest(int requestId) {
         if (DEBUG) {
             Log.d(TAG, "cancelRequest called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot cancel request, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot cancel request, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         synchronized(mConfigureLock) {
             if (mConfiguring) {
-                Log.e(TAG, "Cannot cancel request, configuration change in progress.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot cancel request, configuration change in progress.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
             }
         }
-        long lastFrame = mLegacyDevice.cancelRequest(requestId);
-        lastFrameNumber.setNumber(lastFrame);
-        return CameraBinderDecorator.NO_ERROR;
+        return mLegacyDevice.cancelRequest(requestId);
     }
 
     @Override
-    public int beginConfigure() {
+    public void beginConfigure() {
         if (DEBUG) {
             Log.d(TAG, "beginConfigure called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot begin configure, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot begin configure, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         synchronized(mConfigureLock) {
             if (mConfiguring) {
-                Log.e(TAG, "Cannot begin configure, configuration change already in progress.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot begin configure, configuration change already in progress.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
             }
             mConfiguring = true;
         }
-        return CameraBinderDecorator.NO_ERROR;
     }
 
     @Override
-    public int endConfigure(boolean isConstrainedHighSpeed) {
+    public void endConfigure(boolean isConstrainedHighSpeed) {
         if (DEBUG) {
             Log.d(TAG, "endConfigure called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot end configure, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot end configure, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         ArrayList<Surface> surfaces = null;
         synchronized(mConfigureLock) {
             if (!mConfiguring) {
-                Log.e(TAG, "Cannot end configure, no configuration change in progress.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot end configure, no configuration change in progress.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
             }
             int numSurfaces = mSurfaces.size();
             if (numSurfaces > 0) {
@@ -491,32 +496,34 @@
             }
             mConfiguring = false;
         }
-        return mLegacyDevice.configureOutputs(surfaces);
+        mLegacyDevice.configureOutputs(surfaces);
     }
 
     @Override
-    public int deleteStream(int streamId) {
+    public void deleteStream(int streamId) {
         if (DEBUG) {
             Log.d(TAG, "deleteStream called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot delete stream, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot delete stream, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         synchronized(mConfigureLock) {
             if (!mConfiguring) {
-                Log.e(TAG, "Cannot delete stream, beginConfigure hasn't been called yet.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot delete stream, no configuration change in progress.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
             }
             int index = mSurfaces.indexOfKey(streamId);
             if (index < 0) {
-                Log.e(TAG, "Cannot delete stream, stream id " + streamId + " doesn't exist.");
-                return CameraBinderDecorator.BAD_VALUE;
+                String err = "Cannot delete stream, stream id " + streamId + " doesn't exist.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
             }
             mSurfaces.removeAt(index);
         }
-        return CameraBinderDecorator.NO_ERROR;
     }
 
     @Override
@@ -525,18 +532,21 @@
             Log.d(TAG, "createStream called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot create stream, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot create stream, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         synchronized(mConfigureLock) {
             if (!mConfiguring) {
-                Log.e(TAG, "Cannot create stream, beginConfigure hasn't been called yet.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot create stream, beginConfigure hasn't been called yet.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
             }
             if (outputConfiguration.getRotation() != OutputConfiguration.ROTATION_0) {
-                Log.e(TAG, "Cannot create stream, stream rotation is not supported.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot create stream, stream rotation is not supported.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
             }
             int id = ++mSurfaceIdCounter;
             mSurfaces.put(id, outputConfiguration.getSurface());
@@ -546,24 +556,27 @@
 
     @Override
     public int createInputStream(int width, int height, int format) {
-        Log.e(TAG, "creating input stream is not supported on legacy devices");
-        return CameraBinderDecorator.INVALID_OPERATION;
+        String err = "Creating input stream is not supported on legacy devices";
+        Log.e(TAG, err);
+        throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
     }
 
     @Override
-    public int getInputSurface(/*out*/ Surface surface) {
-        Log.e(TAG, "getting input surface is not supported on legacy devices");
-        return CameraBinderDecorator.INVALID_OPERATION;
+    public Surface getInputSurface() {
+        String err = "Getting input surface is not supported on legacy devices";
+        Log.e(TAG, err);
+        throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
     }
 
     @Override
-    public int createDefaultRequest(int templateId, /*out*/CameraMetadataNative request) {
+    public CameraMetadataNative createDefaultRequest(int templateId) {
         if (DEBUG) {
             Log.d(TAG, "createDefaultRequest called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot create default request, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot create default request, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         CameraMetadataNative template;
@@ -571,99 +584,96 @@
             template =
                     LegacyMetadataMapper.createRequestTemplate(mCameraCharacteristics, templateId);
         } catch (IllegalArgumentException e) {
-            Log.e(TAG, "createDefaultRequest - invalid templateId specified");
-            return CameraBinderDecorator.BAD_VALUE;
+            String err = "createDefaultRequest - invalid templateId specified";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_ILLEGAL_ARGUMENT, err);
         }
 
-        request.swap(template);
-        return CameraBinderDecorator.NO_ERROR;
+        return template;
     }
 
     @Override
-    public int getCameraInfo(/*out*/CameraMetadataNative info) {
+    public CameraMetadataNative getCameraInfo() {
         if (DEBUG) {
             Log.d(TAG, "getCameraInfo called.");
         }
         // TODO: implement getCameraInfo.
         Log.e(TAG, "getCameraInfo unimplemented.");
-        return CameraBinderDecorator.NO_ERROR;
+        return null;
     }
 
     @Override
-    public int waitUntilIdle() throws RemoteException {
+    public void waitUntilIdle() throws RemoteException {
         if (DEBUG) {
             Log.d(TAG, "waitUntilIdle called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot wait until idle, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot wait until idle, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         synchronized(mConfigureLock) {
             if (mConfiguring) {
-                Log.e(TAG, "Cannot wait until idle, configuration change in progress.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot wait until idle, configuration change in progress.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
             }
         }
         mLegacyDevice.waitUntilIdle();
-        return CameraBinderDecorator.NO_ERROR;
     }
 
     @Override
-    public int flush(/*out*/LongParcelable lastFrameNumber) {
+    public long flush() {
         if (DEBUG) {
             Log.d(TAG, "flush called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot flush, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot flush, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         synchronized(mConfigureLock) {
             if (mConfiguring) {
-                Log.e(TAG, "Cannot flush, configuration change in progress.");
-                return CameraBinderDecorator.INVALID_OPERATION;
+                String err = "Cannot flush, configuration change in progress.";
+                Log.e(TAG, err);
+                throw new ServiceSpecificException(ICameraService.ERROR_INVALID_OPERATION, err);
             }
         }
-        long lastFrame = mLegacyDevice.flush();
-        if (lastFrameNumber != null) {
-            lastFrameNumber.setNumber(lastFrame);
-        }
-        return CameraBinderDecorator.NO_ERROR;
+        return mLegacyDevice.flush();
     }
 
-    public int prepare(int streamId) {
+    public void prepare(int streamId) {
         if (DEBUG) {
             Log.d(TAG, "prepare called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot prepare stream, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot prepare stream, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         // LEGACY doesn't support actual prepare, just signal success right away
         mCameraCallbacks.onPrepared(streamId);
-
-        return CameraBinderDecorator.NO_ERROR;
     }
 
-    public int prepare2(int maxCount, int streamId) {
+    public void prepare2(int maxCount, int streamId) {
         // We don't support this in LEGACY mode.
-        return prepare(streamId);
+        prepare(streamId);
     }
 
-    public int tearDown(int streamId) {
+    public void tearDown(int streamId) {
         if (DEBUG) {
             Log.d(TAG, "tearDown called.");
         }
         if (mLegacyDevice.isClosed()) {
-            Log.e(TAG, "Cannot tear down stream, device has been closed.");
-            return -ENODEV;
+            String err = "Cannot tear down stream, device has been closed.";
+            Log.e(TAG, err);
+            throw new ServiceSpecificException(ICameraService.ERROR_DISCONNECTED, err);
         }
 
         // LEGACY doesn't support actual teardown, so just a no-op
-
-        return CameraBinderDecorator.NO_ERROR;
     }
 
     @Override
diff --git a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
index fddfbde..e62df3c 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyCameraDevice.java
@@ -26,14 +26,13 @@
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.hardware.camera2.utils.ArrayUtils;
-import android.hardware.camera2.utils.CameraBinderDecorator;
-import android.hardware.camera2.utils.LongParcelable;
+import android.hardware.camera2.utils.SubmitInfo;
 import android.hardware.camera2.impl.CameraMetadataNative;
-import android.hardware.camera2.utils.CameraRuntimeException;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Size;
@@ -45,7 +44,6 @@
 import java.util.List;
 
 import static android.hardware.camera2.legacy.LegacyExceptionUtils.*;
-import static android.hardware.camera2.utils.CameraBinderDecorator.*;
 import static com.android.internal.util.Preconditions.*;
 
 /**
@@ -357,9 +355,9 @@
         if (success) {
             mConfiguredSurfaces = outputs != null ? new ArrayList<>(outputs) : null;
         } else {
-            return CameraBinderDecorator.INVALID_OPERATION;
+            return LegacyExceptionUtils.INVALID_OPERATION;
         }
-        return CameraBinderDecorator.NO_ERROR;
+        return LegacyExceptionUtils.NO_ERROR;
     }
 
     /**
@@ -367,17 +365,16 @@
      *
      * @param requestList a list of capture requests to execute.
      * @param repeating {@code true} if this burst is repeating.
-     * @param frameNumber an output argument that contains either the frame number of the last frame
-     *                    that will be returned for this request, or the frame number of the last
-     *                    frame that will be returned for the current repeating request if this
-     *                    burst is set to be repeating.
-     * @return the request id.
+     * @return the submission info, including the new request id, and the last frame number, which
+     *   contains either the frame number of the last frame that will be returned for this request,
+     *   or the frame number of the last frame that will be returned for the current repeating
+     *   request if this burst is set to be repeating.
      */
-    public int submitRequestList(List<CaptureRequest> requestList, boolean repeating,
-            /*out*/LongParcelable frameNumber) {
-        if (requestList == null || requestList.isEmpty()) {
+    public SubmitInfo submitRequestList(CaptureRequest[] requestList, boolean repeating) {
+        if (requestList == null || requestList.length == 0) {
             Log.e(TAG, "submitRequestList - Empty/null requests are not allowed");
-            return BAD_VALUE;
+            throw new ServiceSpecificException(BAD_VALUE,
+                    "submitRequestList - Empty/null requests are not allowed");
         }
 
         List<Long> surfaceIds = (mConfiguredSurfaces == null) ? new ArrayList<Long>() :
@@ -388,28 +385,33 @@
             if (request.getTargets().isEmpty()) {
                 Log.e(TAG, "submitRequestList - "
                         + "Each request must have at least one Surface target");
-                return BAD_VALUE;
+                throw new ServiceSpecificException(BAD_VALUE,
+                        "submitRequestList - "
+                        + "Each request must have at least one Surface target");
             }
 
             for (Surface surface : request.getTargets()) {
                 if (surface == null) {
                     Log.e(TAG, "submitRequestList - Null Surface targets are not allowed");
-                    return BAD_VALUE;
+                    throw new ServiceSpecificException(BAD_VALUE,
+                            "submitRequestList - Null Surface targets are not allowed");
                 } else if (mConfiguredSurfaces == null) {
                     Log.e(TAG, "submitRequestList - must configure " +
                             " device with valid surfaces before submitting requests");
-                    return INVALID_OPERATION;
+                    throw new ServiceSpecificException(INVALID_OPERATION,
+                            "submitRequestList - must configure " +
+                            " device with valid surfaces before submitting requests");
                 } else if (!containsSurfaceId(surface, surfaceIds)) {
                     Log.e(TAG, "submitRequestList - cannot use a surface that wasn't configured");
-                    return BAD_VALUE;
+                    throw new ServiceSpecificException(BAD_VALUE,
+                            "submitRequestList - cannot use a surface that wasn't configured");
                 }
             }
         }
 
         // TODO: further validation of request here
         mIdle.close();
-        return mRequestThreadManager.submitCaptureRequests(requestList, repeating,
-                frameNumber);
+        return mRequestThreadManager.submitCaptureRequests(requestList, repeating);
     }
 
     /**
@@ -417,17 +419,14 @@
      *
      * @param request the capture request to execute.
      * @param repeating {@code true} if this request is repeating.
-     * @param frameNumber an output argument that contains either the frame number of the last frame
-     *                    that will be returned for this request, or the frame number of the last
-     *                    frame that will be returned for the current repeating request if this
-     *                    request is set to be repeating.
-     * @return the request id.
+     * @return the submission info, including the new request id, and the last frame number, which
+     *   contains either the frame number of the last frame that will be returned for this request,
+     *   or the frame number of the last frame that will be returned for the current repeating
+     *   request if this burst is set to be repeating.
      */
-    public int submitRequest(CaptureRequest request, boolean repeating,
-            /*out*/LongParcelable frameNumber) {
-        ArrayList<CaptureRequest> requestList = new ArrayList<CaptureRequest>();
-        requestList.add(request);
-        return submitRequestList(requestList, repeating, frameNumber);
+    public SubmitInfo submitRequest(CaptureRequest request, boolean repeating) {
+        CaptureRequest[] requestList = { request };
+        return submitRequestList(requestList, repeating);
     }
 
     /**
@@ -493,7 +492,7 @@
     protected void finalize() throws Throwable {
         try {
             close();
-        } catch (CameraRuntimeException e) {
+        } catch (ServiceSpecificException e) {
             Log.e(TAG, "Got error while trying to finalize, ignoring: " + e.getMessage());
         } finally {
             super.finalize();
diff --git a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
index 4501e81..93d6001 100644
--- a/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
+++ b/core/java/android/hardware/camera2/legacy/LegacyExceptionUtils.java
@@ -16,10 +16,11 @@
 
 package android.hardware.camera2.legacy;
 
-import android.hardware.camera2.utils.CameraBinderDecorator;
+import android.hardware.ICameraService;
+import android.os.ServiceSpecificException;
 import android.util.AndroidException;
 
-import static android.system.OsConstants.ENODEV;
+import static android.system.OsConstants.*;
 
 /**
  * Utility class containing exception handling used solely by the compatibility mode shim.
@@ -27,6 +28,14 @@
 public class LegacyExceptionUtils {
     private static final String TAG = "LegacyExceptionUtils";
 
+    public static final int NO_ERROR = 0;
+    public static final int PERMISSION_DENIED = -EPERM;
+    public static final int ALREADY_EXISTS = -EEXIST;
+    public static final int BAD_VALUE = -EINVAL;
+    public static final int DEAD_OBJECT = -ENOSYS;
+    public static final int INVALID_OPERATION = -EPIPE;
+    public static final int TIMED_OUT = -ETIMEDOUT;
+
     /**
      * Checked exception thrown when a BufferQueue has been abandoned by its consumer.
      */
@@ -58,8 +67,8 @@
      * @return {@code errorFlag} if the value was non-negative, throws otherwise.
      */
     public static int throwOnError(int errorFlag) throws BufferQueueAbandonedException {
-        if (errorFlag == CameraBinderDecorator.NO_ERROR) {
-            return CameraBinderDecorator.NO_ERROR;
+        if (errorFlag == NO_ERROR) {
+            return NO_ERROR;
         } else if (errorFlag == -ENODEV) {
             throw new BufferQueueAbandonedException();
         }
@@ -70,6 +79,59 @@
         return errorFlag;
     }
 
+    /**
+     * Throw error codes returned by the camera service as exceptions.
+     *
+     * @param errorFlag error to throw as an exception.
+     */
+    public static void throwOnServiceError(int errorFlag) {
+        int errorCode = ICameraService.ERROR_INVALID_OPERATION;
+        String errorMsg;
+
+        if (errorFlag >= NO_ERROR) {
+            return;
+        } else if (errorFlag == PERMISSION_DENIED) {
+            errorCode = ICameraService.ERROR_PERMISSION_DENIED;
+            errorMsg = "Lacking privileges to access camera service";
+        } else if (errorFlag == ALREADY_EXISTS) {
+            // This should be handled at the call site. Typically this isn't bad,
+            // just means we tried to do an operation that already completed.
+            return;
+        } else if (errorFlag == BAD_VALUE) {
+            errorCode = ICameraService.ERROR_ILLEGAL_ARGUMENT;
+            errorMsg = "Bad argument passed to camera service";
+        } else if (errorFlag == DEAD_OBJECT) {
+            errorCode = ICameraService.ERROR_DISCONNECTED;
+            errorMsg = "Camera service not available";
+        } else if (errorFlag == TIMED_OUT) {
+            errorCode = ICameraService.ERROR_INVALID_OPERATION;
+            errorMsg = "Operation timed out in camera service";
+        } else if (errorFlag == -EACCES) {
+            errorCode = ICameraService.ERROR_DISABLED;
+            errorMsg = "Camera disabled by policy";
+        } else if (errorFlag == -EBUSY) {
+            errorCode = ICameraService.ERROR_CAMERA_IN_USE;
+            errorMsg = "Camera already in use";
+        } else if (errorFlag == -EUSERS) {
+            errorCode = ICameraService.ERROR_MAX_CAMERAS_IN_USE;
+            errorMsg = "Maximum number of cameras in use";
+        } else if (errorFlag == -ENODEV) {
+            errorCode = ICameraService.ERROR_DISCONNECTED;
+            errorMsg = "Camera device not available";
+        } else if (errorFlag == -EOPNOTSUPP) {
+            errorCode = ICameraService.ERROR_DEPRECATED_HAL;
+            errorMsg = "Deprecated camera HAL does not support this";
+        } else if (errorFlag == INVALID_OPERATION) {
+            errorCode = ICameraService.ERROR_INVALID_OPERATION;
+            errorMsg = "Illegal state encountered in camera service.";
+        } else {
+            errorCode = ICameraService.ERROR_INVALID_OPERATION;
+            errorMsg = "Unknown camera device error " + errorFlag;
+        }
+
+        throw new ServiceSpecificException(errorCode, errorMsg);
+    }
+
     private LegacyExceptionUtils() {
         throw new AssertionError();
     }
diff --git a/core/java/android/hardware/camera2/legacy/RequestQueue.java b/core/java/android/hardware/camera2/legacy/RequestQueue.java
index c995029..8f252a1 100644
--- a/core/java/android/hardware/camera2/legacy/RequestQueue.java
+++ b/core/java/android/hardware/camera2/legacy/RequestQueue.java
@@ -16,7 +16,7 @@
 package android.hardware.camera2.legacy;
 
 import android.hardware.camera2.CaptureRequest;
-import android.hardware.camera2.utils.LongParcelable;
+import android.hardware.camera2.utils.SubmitInfo;
 import android.util.Log;
 import android.util.Pair;
 
@@ -111,31 +111,29 @@
      *
      * @param requests the burst of requests to add to the queue.
      * @param repeating true if the burst is repeating.
-     * @param frameNumber an output argument that contains either the frame number of the last frame
-     *                    that will be returned for this request, or the frame number of the last
-     *                    frame that will be returned for the current repeating request if this
-     *                    burst is set to be repeating.
-     * @return the request id.
+     * @return the submission info, including the new request id, and the last frame number, which
+     *   contains either the frame number of the last frame that will be returned for this request,
+     *   or the frame number of the last frame that will be returned for the current repeating
+     *   request if this burst is set to be repeating.
      */
-    public synchronized int submit(List<CaptureRequest> requests, boolean repeating,
-            /*out*/LongParcelable frameNumber) {
+    public synchronized SubmitInfo submit(CaptureRequest[] requests, boolean repeating) {
         int requestId = mCurrentRequestId++;
         BurstHolder burst = new BurstHolder(requestId, repeating, requests, mJpegSurfaceIds);
-        long ret = INVALID_FRAME;
+        long lastFrame = INVALID_FRAME;
         if (burst.isRepeating()) {
             Log.i(TAG, "Repeating capture request set.");
             if (mRepeatingRequest != null) {
-                ret = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
+                lastFrame = (mCurrentRepeatingFrameNumber == INVALID_FRAME) ? INVALID_FRAME :
                         mCurrentRepeatingFrameNumber - 1;
             }
             mCurrentRepeatingFrameNumber = INVALID_FRAME;
             mRepeatingRequest = burst;
         } else {
             mRequestQueue.offer(burst);
-            ret = calculateLastFrame(burst.getRequestId());
+            lastFrame = calculateLastFrame(burst.getRequestId());
         }
-        frameNumber.setNumber(ret);
-        return requestId;
+        SubmitInfo info = new SubmitInfo(requestId, lastFrame);
+        return info;
     }
 
     private long calculateLastFrame(int requestId) {
diff --git a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
index 4866598..1ca7ddf 100644
--- a/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
+++ b/core/java/android/hardware/camera2/legacy/RequestThreadManager.java
@@ -21,7 +21,7 @@
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.impl.CameraDeviceImpl;
-import android.hardware.camera2.utils.LongParcelable;
+import android.hardware.camera2.utils.SubmitInfo;
 import android.hardware.camera2.utils.SizeAreaComparator;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.os.ConditionVariable;
@@ -1008,21 +1008,19 @@
      *
      * @param requests the burst of requests to add to the queue.
      * @param repeating true if the burst is repeating.
-     * @param frameNumber an output argument that contains either the frame number of the last frame
-     *                    that will be returned for this request, or the frame number of the last
-     *                    frame that will be returned for the current repeating request if this
-     *                    burst is set to be repeating.
-     * @return the request id.
+     * @return the submission info, including the new request id, and the last frame number, which
+     *   contains either the frame number of the last frame that will be returned for this request,
+     *   or the frame number of the last frame that will be returned for the current repeating
+     *   request if this burst is set to be repeating.
      */
-    public int submitCaptureRequests(List<CaptureRequest> requests, boolean repeating,
-            /*out*/LongParcelable frameNumber) {
+    public SubmitInfo submitCaptureRequests(CaptureRequest[] requests, boolean repeating) {
         Handler handler = mRequestThread.waitAndGetHandler();
-        int ret;
+        SubmitInfo info;
         synchronized (mIdleLock) {
-            ret = mRequestQueue.submit(requests, repeating, frameNumber);
+            info = mRequestQueue.submit(requests, repeating);
             handler.sendEmptyMessage(MSG_SUBMIT_CAPTURE_REQUEST);
         }
-        return ret;
+        return info;
     }
 
     /**
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.aidl b/core/java/android/hardware/camera2/params/OutputConfiguration.aidl
deleted file mode 100644
index 0921cd8..0000000
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * 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 android.hardware.camera2.params;
-
-/** @hide */
-parcelable OutputConfiguration;
diff --git a/core/java/android/hardware/camera2/params/OutputConfiguration.java b/core/java/android/hardware/camera2/params/OutputConfiguration.java
index 4407e55..cd0c474 100644
--- a/core/java/android/hardware/camera2/params/OutputConfiguration.java
+++ b/core/java/android/hardware/camera2/params/OutputConfiguration.java
@@ -21,11 +21,11 @@
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.utils.HashCodeHelpers;
 import android.hardware.camera2.utils.SurfaceUtils;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.Log;
 import android.util.Size;
 import android.view.Surface;
-import android.os.Parcel;
-import android.os.Parcelable;
 
 import static com.android.internal.util.Preconditions.*;
 
diff --git a/core/java/android/hardware/camera2/params/VendorTagDescriptor.java b/core/java/android/hardware/camera2/params/VendorTagDescriptor.java
new file mode 100644
index 0000000..ea424e5
--- /dev/null
+++ b/core/java/android/hardware/camera2/params/VendorTagDescriptor.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.camera2.params;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.util.Log;
+
+/**
+ * A class for describing the vendor tags declared by a camera HAL module.
+ * Generally only used by the native side of
+ * android.hardware.camera2.impl.CameraMetadataNative
+ *
+ * @hide
+ */
+public final class VendorTagDescriptor implements Parcelable {
+
+    private VendorTagDescriptor(Parcel source) {
+    }
+
+    public static final Parcelable.Creator<VendorTagDescriptor> CREATOR =
+            new Parcelable.Creator<VendorTagDescriptor>() {
+        @Override
+        public VendorTagDescriptor createFromParcel(Parcel source) {
+            try {
+                VendorTagDescriptor vendorDescriptor = new VendorTagDescriptor(source);
+                return vendorDescriptor;
+            } catch (Exception e) {
+                Log.e(TAG, "Exception creating VendorTagDescriptor from parcel", e);
+                return null;
+            }
+        }
+
+        @Override
+        public VendorTagDescriptor[] newArray(int size) {
+            return new VendorTagDescriptor[size];
+        }
+    };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        if (dest == null) {
+            throw new IllegalArgumentException("dest must not be null");
+        }
+    }
+
+    private static final String TAG = "VendorTagDescriptor";
+}
diff --git a/core/java/android/hardware/camera2/utils/BinderHolder.aidl b/core/java/android/hardware/camera2/utils/BinderHolder.aidl
deleted file mode 100644
index f39d645..0000000
--- a/core/java/android/hardware/camera2/utils/BinderHolder.aidl
+++ /dev/null
@@ -1,20 +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 android.hardware.camera2.utils;
-
-/** @hide */
-parcelable BinderHolder;
diff --git a/core/java/android/hardware/camera2/utils/BinderHolder.java b/core/java/android/hardware/camera2/utils/BinderHolder.java
deleted file mode 100644
index 9eea390..0000000
--- a/core/java/android/hardware/camera2/utils/BinderHolder.java
+++ /dev/null
@@ -1,74 +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 android.hardware.camera2.utils;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.os.IBinder;
-
-/**
- * @hide
- */
-public class BinderHolder implements Parcelable {
-    private IBinder mBinder = null;
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(Parcel dest, int flags) {
-        dest.writeStrongBinder(mBinder);
-    }
-
-    public void readFromParcel(Parcel src) {
-        mBinder = src.readStrongBinder();
-    }
-
-    public static final Parcelable.Creator<BinderHolder> CREATOR =
-             new Parcelable.Creator<BinderHolder>() {
-         @Override
-         public BinderHolder createFromParcel(Parcel in) {
-             return new BinderHolder(in);
-         }
-
-         @Override
-         public BinderHolder[] newArray(int size) {
-             return new BinderHolder[size];
-         }
-    };
-
-    public IBinder getBinder() {
-        return mBinder;
-    }
-
-    public void setBinder(IBinder binder) {
-        mBinder = binder;
-    }
-
-    public BinderHolder() {}
-
-    public BinderHolder(IBinder binder) {
-        mBinder = binder;
-    }
-
-    private BinderHolder(Parcel in) {
-        mBinder = in.readStrongBinder();
-    }
-}
-
diff --git a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
deleted file mode 100644
index 162edc9..0000000
--- a/core/java/android/hardware/camera2/utils/CameraBinderDecorator.java
+++ /dev/null
@@ -1,162 +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 android.hardware.camera2.utils;
-
-import static android.hardware.camera2.CameraAccessException.CAMERA_DISABLED;
-import static android.hardware.camera2.CameraAccessException.CAMERA_DISCONNECTED;
-import static android.hardware.camera2.CameraAccessException.CAMERA_IN_USE;
-import static android.hardware.camera2.CameraAccessException.CAMERA_ERROR;
-import static android.hardware.camera2.CameraAccessException.MAX_CAMERAS_IN_USE;
-import static android.hardware.camera2.CameraAccessException.CAMERA_DEPRECATED_HAL;
-import static android.system.OsConstants.*;
-
-import android.os.DeadObjectException;
-import android.os.RemoteException;
-
-import java.lang.reflect.Method;
-
-/**
- * Translate camera device status_t return values into exceptions.
- *
- * @see android.hardware.camera2.utils.CameraBinderDecorator#newInstance
- * @hide
- */
-public class CameraBinderDecorator {
-
-    public static final int NO_ERROR = 0;
-    public static final int PERMISSION_DENIED = -EPERM;
-    public static final int ALREADY_EXISTS = -EEXIST;
-    public static final int BAD_VALUE = -EINVAL;
-    public static final int DEAD_OBJECT = -ENOSYS;
-    public static final int INVALID_OPERATION = -EPIPE;
-    public static final int TIMED_OUT = -ETIMEDOUT;
-
-    /**
-     * TODO: add as error codes in Errors.h
-     * - POLICY_PROHIBITS
-     * - RESOURCE_BUSY
-     * - NO_SUCH_DEVICE
-     * - NOT_SUPPORTED
-     * - TOO_MANY_USERS
-     */
-
-    static class CameraBinderDecoratorListener implements Decorator.DecoratorListener {
-
-        @Override
-        public void onBeforeInvocation(Method m, Object[] args) {
-        }
-
-        @Override
-        public void onAfterInvocation(Method m, Object[] args, Object result) {
-            // int return type => status_t => convert to exception
-            if (m.getReturnType() == Integer.TYPE) {
-                int returnValue = (Integer) result;
-                throwOnError(returnValue);
-            }
-        }
-
-        @Override
-        public boolean onCatchException(Method m, Object[] args, Throwable t) {
-
-            if (t instanceof DeadObjectException) {
-                throw new CameraRuntimeException(CAMERA_DISCONNECTED,
-                        "Process hosting the camera service has died unexpectedly",
-                        t);
-            } else if (t instanceof RemoteException) {
-                throw new UnsupportedOperationException("An unknown RemoteException was thrown" +
-                        " which should never happen.", t);
-            }
-
-            return false;
-        }
-
-        @Override
-        public void onFinally(Method m, Object[] args) {
-        }
-
-    }
-
-    /**
-     * Throw error codes returned by the camera service as exceptions.
-     *
-     * @param errorFlag error to throw as an exception.
-     */
-    public static void throwOnError(int errorFlag) {
-        if (errorFlag == NO_ERROR) {
-            return;
-        } else if (errorFlag == PERMISSION_DENIED) {
-            throw new SecurityException("Lacking privileges to access camera service");
-        } else if (errorFlag == ALREADY_EXISTS) {
-            // This should be handled at the call site. Typically this isn't bad,
-            // just means we tried to do an operation that already completed.
-            return;
-        } else if (errorFlag == BAD_VALUE) {
-            throw new IllegalArgumentException("Bad argument passed to camera service");
-        } else if (errorFlag == DEAD_OBJECT) {
-            throw new CameraRuntimeException(CAMERA_DISCONNECTED);
-        } else if (errorFlag == TIMED_OUT) {
-            throw new CameraRuntimeException(CAMERA_ERROR,
-                    "Operation timed out in camera service");
-        } else if (errorFlag == -EACCES) {
-            throw new CameraRuntimeException(CAMERA_DISABLED);
-        } else if (errorFlag == -EBUSY) {
-            throw new CameraRuntimeException(CAMERA_IN_USE);
-        } else if (errorFlag == -EUSERS) {
-            throw new CameraRuntimeException(MAX_CAMERAS_IN_USE);
-        } else if (errorFlag == -ENODEV) {
-            throw new CameraRuntimeException(CAMERA_DISCONNECTED);
-        } else if (errorFlag == -EOPNOTSUPP) {
-            throw new CameraRuntimeException(CAMERA_DEPRECATED_HAL);
-        } else if (errorFlag == INVALID_OPERATION) {
-            throw new CameraRuntimeException(CAMERA_ERROR,
-                    "Illegal state encountered in camera service.");
-        }
-
-        /**
-         * Trap the rest of the negative return values. If we have known
-         * error codes i.e. ALREADY_EXISTS that aren't really runtime
-         * errors, then add them to the top switch statement
-         */
-        if (errorFlag < 0) {
-            throw new CameraRuntimeException(CAMERA_ERROR,
-                    String.format("Unknown camera device error %d", errorFlag));
-        }
-    }
-
-    /**
-     * <p>
-     * Wraps the type T with a proxy that will check 'status_t' return codes
-     * from the native side of the camera service, and throw Java exceptions
-     * automatically based on the code.
-     * </p>
-     * <p>
-     * In addition it also rewrites binder's RemoteException into either a
-     * CameraAccessException or an UnsupportedOperationException.
-     * </p>
-     * <p>
-     * As a result of calling any method on the proxy, RemoteException is
-     * guaranteed never to be thrown.
-     * </p>
-     *
-     * @param obj object that will serve as the target for all method calls
-     * @param <T> the type of the element you want to wrap. This must be an interface.
-     * @return a proxy that will intercept all invocations to obj
-     */
-    public static <T> T newInstance(T obj) {
-        return Decorator.<T> newInstance(obj, new CameraBinderDecoratorListener());
-    }
-}
diff --git a/core/java/android/hardware/camera2/utils/CameraRuntimeException.java b/core/java/android/hardware/camera2/utils/CameraRuntimeException.java
deleted file mode 100644
index 9ed88a9..0000000
--- a/core/java/android/hardware/camera2/utils/CameraRuntimeException.java
+++ /dev/null
@@ -1,63 +0,0 @@
-package android.hardware.camera2.utils;
-
-import android.hardware.camera2.CameraAccessException;
-
-/**
- * @hide
- */
-public class CameraRuntimeException extends RuntimeException {
-
-    private final int mReason;
-    private String mMessage;
-    private Throwable mCause;
-
-    public final int getReason() {
-        return mReason;
-    }
-
-    public CameraRuntimeException(int problem) {
-        super();
-        mReason = problem;
-    }
-
-    public CameraRuntimeException(int problem, String message) {
-        super(message);
-        mReason = problem;
-        mMessage = message;
-    }
-
-    public CameraRuntimeException(int problem, String message, Throwable cause) {
-        super(message, cause);
-        mReason = problem;
-        mMessage = message;
-        mCause = cause;
-    }
-
-    public CameraRuntimeException(int problem, Throwable cause) {
-        super(cause);
-        mReason = problem;
-        mCause = cause;
-    }
-
-    /**
-     * Recreate this exception as the CameraAccessException equivalent.
-     * @return CameraAccessException
-     */
-    public CameraAccessException asChecked() {
-        CameraAccessException e;
-
-        if (mMessage != null && mCause != null) {
-            e = new CameraAccessException(mReason, mMessage, mCause);
-        } else if (mMessage != null) {
-            e = new CameraAccessException(mReason, mMessage);
-        } else if (mCause != null) {
-            e = new CameraAccessException(mReason, mCause);
-        } else {
-            e = new CameraAccessException(mReason);
-        }
-        // throw and catch, so java has a chance to fill out the stack trace
-        e.setStackTrace(this.getStackTrace());
-
-        return e;
-    }
-}
diff --git a/core/java/android/hardware/camera2/utils/CameraServiceBinderDecorator.java b/core/java/android/hardware/camera2/utils/CameraServiceBinderDecorator.java
deleted file mode 100644
index c1fb6b1..0000000
--- a/core/java/android/hardware/camera2/utils/CameraServiceBinderDecorator.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.camera2.utils;
-
-import android.os.DeadObjectException;
-import android.os.RemoteException;
-import android.util.Log;
-
-import java.lang.reflect.Method;
-
-/**
- * Translate camera service status_t return values into exceptions.
- *
- * @see android.hardware.camera2.utils.CameraBinderDecorator#newInstance
- * @hide
- */
-public class CameraServiceBinderDecorator extends CameraBinderDecorator {
-
-    private static final String TAG = "CameraServiceBinderDecorator";
-
-    static class CameraServiceBinderDecoratorListener
-            extends CameraBinderDecorator.CameraBinderDecoratorListener {
-
-        // Pass through remote exceptions, unlike CameraBinderDecorator
-        @Override
-        public boolean onCatchException(Method m, Object[] args, Throwable t) {
-
-            if (t instanceof DeadObjectException) {
-                // Can sometimes happen (camera service died)
-                // Pass on silently
-            } else if (t instanceof RemoteException) {
-                // Some other kind of remote exception - this is not normal, so let's at least
-                // note it before moving on
-                Log.e(TAG, "Unexpected RemoteException from camera service call.", t);
-            }
-            // All other exceptions also get sent onward
-            return false;
-        }
-
-    }
-
-    /**
-     * <p>
-     * Wraps the type T with a proxy that will check 'status_t' return codes
-     * from the native side of the camera service, and throw Java exceptions
-     * automatically based on the code.
-     * </p>
-     *
-     * @param obj object that will serve as the target for all method calls
-     * @param <T> the type of the element you want to wrap. This must be an interface.
-     * @return a proxy that will intercept all invocations to obj
-     */
-    public static <T> T newInstance(T obj) {
-        return Decorator.<T> newInstance(obj, new CameraServiceBinderDecoratorListener());
-    }
-}
diff --git a/core/java/android/hardware/camera2/utils/Decorator.java b/core/java/android/hardware/camera2/utils/Decorator.java
deleted file mode 100644
index 5826497..0000000
--- a/core/java/android/hardware/camera2/utils/Decorator.java
+++ /dev/null
@@ -1,92 +0,0 @@
-
-package android.hardware.camera2.utils;
-
-import java.lang.reflect.*;
-
-/**
- * This is an implementation of the 'decorator' design pattern using Java's proxy mechanism.
- *
- * @see android.hardware.camera2.utils.Decorator#newInstance
- *
- * @hide
- */
-public class Decorator<T> implements InvocationHandler {
-
-    public interface DecoratorListener {
-        /**
-         * This method is called before the target method is invoked
-         * @param args arguments to target method
-         * @param m Method being called
-         */
-        void onBeforeInvocation(Method m, Object[] args);
-        /**
-         * This function is called after the target method is invoked
-         * if there were no uncaught exceptions
-         * @param args arguments to target method
-         * @param m Method being called
-         * @param result return value of target method
-         */
-        void onAfterInvocation(Method m, Object[] args, Object result);
-        /**
-         * This method is called only if there was an exception thrown by the target method
-         * during its invocation.
-         *
-         * @param args arguments to target method
-         * @param m Method being called
-         * @param t Throwable that was thrown
-         * @return false to rethrow exception, true if the exception was handled
-         */
-        boolean onCatchException(Method m, Object[] args, Throwable t);
-        /**
-         * This is called after the target method is invoked, regardless of whether or not
-         * there were any exceptions.
-         * @param args arguments to target method
-         * @param m Method being called
-         */
-        void onFinally(Method m, Object[] args);
-    }
-
-    private final T mObject;
-    private final DecoratorListener mListener;
-
-    /**
-     * Create a decorator wrapping the specified object's method calls.
-     *
-     * @param obj the object whose method calls you want to intercept
-     * @param listener the decorator handler for intercepted method calls
-     * @param <T> the type of the element you want to wrap. This must be an interface.
-     * @return a wrapped interface-compatible T
-     */
-    @SuppressWarnings("unchecked")
-    public static<T> T newInstance(T obj, DecoratorListener listener) {
-        return (T)java.lang.reflect.Proxy.newProxyInstance(
-                obj.getClass().getClassLoader(),
-                obj.getClass().getInterfaces(),
-                new Decorator<T>(obj, listener));
-    }
-
-    private Decorator(T obj, DecoratorListener listener) {
-        this.mObject = obj;
-        this.mListener = listener;
-    }
-
-    @Override
-    public Object invoke(Object proxy, Method m, Object[] args)
-            throws Throwable
-    {
-        Object result = null;
-        try {
-            mListener.onBeforeInvocation(m, args);
-            result = m.invoke(mObject, args);
-            mListener.onAfterInvocation(m, args, result);
-        } catch (InvocationTargetException e) {
-            Throwable t = e.getTargetException();
-            if (!mListener.onCatchException(m, args, t)) {
-                throw t;
-            }
-        } finally {
-            mListener.onFinally(m, args);
-        }
-        return result;
-    }
-}
diff --git a/core/java/android/hardware/camera2/utils/LongParcelable.aidl b/core/java/android/hardware/camera2/utils/LongParcelable.aidl
deleted file mode 100644
index 98ad1b2..0000000
--- a/core/java/android/hardware/camera2/utils/LongParcelable.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.camera2.utils;
-
-/** @hide */
-parcelable LongParcelable;
diff --git a/core/java/android/hardware/camera2/utils/SubmitInfo.java b/core/java/android/hardware/camera2/utils/SubmitInfo.java
new file mode 100644
index 0000000..d1692b5
--- /dev/null
+++ b/core/java/android/hardware/camera2/utils/SubmitInfo.java
@@ -0,0 +1,106 @@
+/*
+ * 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 android.hardware.camera2.utils;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.hardware.camera2.ICameraDeviceUser;
+
+/**
+ * The status information returned for a successful capture request submission.
+ *
+ * Includes the request ID for the newly submitted capture request, and the
+ * last frame number of either the previous repeating request (for repeating
+ * requests), or of the request(s) just submitted (for single-shot capture).
+ *
+ * @hide
+ */
+public class SubmitInfo implements Parcelable {
+
+    private int mRequestId;
+    private long mLastFrameNumber;
+
+    public SubmitInfo() {
+        mRequestId = -1;
+        mLastFrameNumber = ICameraDeviceUser.NO_IN_FLIGHT_REPEATING_FRAMES;
+    }
+
+    public SubmitInfo(int requestId, long lastFrameNumber) {
+        mRequestId = requestId;
+        mLastFrameNumber = lastFrameNumber;
+    }
+
+    public static final Parcelable.Creator<SubmitInfo> CREATOR =
+            new Parcelable.Creator<SubmitInfo>() {
+        @Override
+        public SubmitInfo createFromParcel(Parcel in) {
+            return new SubmitInfo(in);
+        }
+
+        @Override
+        public SubmitInfo[] newArray(int size) {
+            return new SubmitInfo[size];
+        }
+    };
+
+    private SubmitInfo(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mRequestId);
+        dest.writeLong(mLastFrameNumber);
+    }
+
+    public void readFromParcel(Parcel in) {
+        mRequestId = in.readInt();
+        mLastFrameNumber = in.readLong();
+    }
+
+    /**
+     * Return the request ID for the submitted capture request/burst.
+     *
+     * This is used to track the completion status of the requested captures,
+     * and to cancel repeating requests.
+     */
+    public int getRequestId() {
+        return mRequestId;
+    }
+
+    /**
+     * Return the last frame number for the submitted capture request/burst.
+     *
+     * For a repeating request, this is the last frame number of the _prior_
+     * repeating request, to indicate when to fire the sequence completion callback
+     * for the prior repeating request.
+     *
+     * For a single-shot capture, this is the last frame number of _this_
+     * burst, to indicate when to fire the sequence completion callback for the request itself.
+     *
+     * For a repeating request, may be NO_IN_FLIGHT_REPEATING_FRAMES, if no
+     * instances of a prior repeating request were actually issued to the camera device.
+     */
+    public long getLastFrameNumber() {
+        return mLastFrameNumber;
+    }
+
+}
diff --git a/core/java/android/os/ServiceSpecificException.java b/core/java/android/os/ServiceSpecificException.java
index 20f237a5..04fca19 100644
--- a/core/java/android/os/ServiceSpecificException.java
+++ b/core/java/android/os/ServiceSpecificException.java
@@ -20,20 +20,23 @@
  *
  * <p>This exception includes an error code specific to the throwing
  * service.  This is mostly used by system services to indicate
- * domain specific error conditions.
+ * domain specific error conditions.</p>
+ *
+ * <p>Since these exceptions are designed to be passed through Binder
+ * interfaces, and to be generated by native-code Binder services,
+ * they do not support exception chaining.</p>
  *
  * @hide
  */
 public class ServiceSpecificException extends RuntimeException {
     public final int errorCode;
 
-    ServiceSpecificException(int errorCode, String message) {
+    public ServiceSpecificException(int errorCode, String message) {
         super(message);
         this.errorCode = errorCode;
     }
 
-    ServiceSpecificException(int errorCode) {
+    public ServiceSpecificException(int errorCode) {
         this.errorCode = errorCode;
     }
 }
-
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index cb45deb..f13f242 100755
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -4522,6 +4522,13 @@
         public static final String USER_SETUP_COMPLETE = "user_setup_complete";
 
         /**
+         * Prefix for category name that marks whether a suggested action from that category was
+         * completed.
+         * @hide
+         */
+        public static final String COMPLETED_CATEGORY_PREFIX = "suggested.completed_category.";
+
+        /**
          * List of input methods that are currently enabled.  This is a string
          * containing the IDs of all enabled input methods, each ID separated
          * by ':'.
diff --git a/core/java/android/view/Surface.aidl b/core/java/android/view/Surface.aidl
deleted file mode 100644
index 90bf37a..0000000
--- a/core/java/android/view/Surface.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/* //device/java/android/android/view/Surface.aidl
-**
-** Copyright 2007, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License"); 
-** you may not use this file except in compliance with the License. 
-** You may obtain a copy of the License at 
-**
-**     http://www.apache.org/licenses/LICENSE-2.0 
-**
-** Unless required by applicable law or agreed to in writing, software 
-** distributed under the License is distributed on an "AS IS" BASIS, 
-** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
-** See the License for the specific language governing permissions and 
-** limitations under the License.
-*/
-
-package android.view;
-
-parcelable Surface;
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index b58c68f..aa86c03 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -36,6 +36,7 @@
             throws OutOfResourcesException;
     private static native void nativeRelease(long nativeObject);
     private static native void nativeDestroy(long nativeObject);
+    private static native void nativeDisconnect(long nativeObject);
 
     private static native Bitmap nativeScreenshot(IBinder displayToken,
             Rect sourceCrop, int width, int height, int minLayer, int maxLayer,
@@ -341,6 +342,15 @@
         mCloseGuard.close();
     }
 
+    /**
+     * Disconnect any client still connected to the surface.
+     */
+    public void disconnect() {
+        if (mNativeObject != 0) {
+            nativeDisconnect(mNativeObject);
+        }
+    }
+
     private void checkNotReleased() {
         if (mNativeObject == 0) throw new NullPointerException(
                 "mNativeObject is null. Have you called release() already?");
diff --git a/core/jni/Android.mk b/core/jni/Android.mk
index ed3fe42..816a2c4 100644
--- a/core/jni/Android.mk
+++ b/core/jni/Android.mk
@@ -190,7 +190,6 @@
     $(call include-path-for, bluedroid) \
     $(call include-path-for, libhardware)/hardware \
     $(call include-path-for, libhardware_legacy)/hardware_legacy \
-    $(TOP)/frameworks/av/include \
     $(TOP)/frameworks/base/media/jni \
     $(TOP)/system/core/base/include \
     $(TOP)/system/core/include \
diff --git a/core/jni/android_hardware_camera2_CameraMetadata.cpp b/core/jni/android_hardware_camera2_CameraMetadata.cpp
index 7930027..afccfcf 100644
--- a/core/jni/android_hardware_camera2_CameraMetadata.cpp
+++ b/core/jni/android_hardware_camera2_CameraMetadata.cpp
@@ -33,9 +33,9 @@
 #include "core_jni_helpers.h"
 #include "android_runtime/android_hardware_camera2_CameraMetadata.h"
 
+#include <android/hardware/ICameraService.h>
 #include <binder/IServiceManager.h>
 #include <camera/CameraMetadata.h>
-#include <camera/ICameraService.h>
 #include <camera/VendorTagDescriptor.h>
 #include <nativehelper/ScopedUtfChars.h>
 #include <nativehelper/ScopedPrimitiveArray.h>
@@ -906,32 +906,36 @@
 
 static jint CameraMetadata_setupGlobalVendorTagDescriptor(JNIEnv *env, jobject thiz) {
     const String16 NAME("media.camera");
-    sp<ICameraService> cameraService;
+    sp<hardware::ICameraService> cameraService;
     status_t err = getService(NAME, /*out*/&cameraService);
 
     if (err != OK) {
         ALOGE("%s: Failed to get camera service, received error %s (%d)", __FUNCTION__,
                 strerror(-err), err);
-        return err;
+        return hardware::ICameraService::ERROR_DISCONNECTED;
     }
 
-    sp<VendorTagDescriptor> desc;
-    err = cameraService->getCameraVendorTagDescriptor(/*out*/desc);
+    sp<VendorTagDescriptor> desc = new VendorTagDescriptor();
+    binder::Status res = cameraService->getCameraVendorTagDescriptor(/*out*/desc.get());
 
-    if (err == -EOPNOTSUPP) {
+    if (res.serviceSpecificErrorCode() == hardware::ICameraService::ERROR_DEPRECATED_HAL) {
         ALOGW("%s: Camera HAL too old; does not support vendor tags", __FUNCTION__);
         VendorTagDescriptor::clearGlobalVendorTagDescriptor();
 
         return OK;
-    } else if (err != OK) {
-        ALOGE("%s: Failed to setup vendor tag descriptors, received error %s (%d)",
-                __FUNCTION__, strerror(-err), err);
-        return err;
+    } else if (!res.isOk()) {
+        ALOGE("%s: Failed to setup vendor tag descriptors: %s: %s",
+                __FUNCTION__, res.serviceSpecificErrorCode(),
+                res.toString8().string());
+        return res.serviceSpecificErrorCode();
     }
 
     err = VendorTagDescriptor::setAsGlobalVendorTagDescriptor(desc);
 
-    return err;
+    if (err != OK) {
+        return hardware::ICameraService::ERROR_INVALID_OPERATION;
+    }
+    return OK;
 }
 
 } // extern "C"
diff --git a/core/jni/android_view_Surface.cpp b/core/jni/android_view_Surface.cpp
index cf68449..b7701d6 100644
--- a/core/jni/android_view_Surface.cpp
+++ b/core/jni/android_view_Surface.cpp
@@ -411,22 +411,27 @@
         return 0;
     }
 
+    android::view::Surface surfaceShim;
+
+    // Calling code in Surface.java has already read the name of the Surface
+    // from the Parcel
+    surfaceShim.readFromParcel(parcel, /*nameAlreadyRead*/true);
+
     sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
-    sp<IBinder> binder(parcel->readStrongBinder());
 
     // update the Surface only if the underlying IGraphicBufferProducer
     // has changed.
-    if (self != NULL
-            && (IInterface::asBinder(self->getIGraphicBufferProducer()) == binder)) {
+    if (self != nullptr
+            && (IInterface::asBinder(self->getIGraphicBufferProducer()) ==
+                    IInterface::asBinder(surfaceShim.graphicBufferProducer))) {
         // same IGraphicBufferProducer, return ourselves
         return jlong(self.get());
     }
 
     sp<Surface> sur;
-    sp<IGraphicBufferProducer> gbp(interface_cast<IGraphicBufferProducer>(binder));
-    if (gbp != NULL) {
+    if (surfaceShim.graphicBufferProducer != nullptr) {
         // we have a new IGraphicBufferProducer, create a new Surface for it
-        sur = new Surface(gbp, true);
+        sur = new Surface(surfaceShim.graphicBufferProducer, true);
         // and keep a reference before passing to java
         sur->incStrong(&sRefBaseOwner);
     }
@@ -447,7 +452,13 @@
         return;
     }
     sp<Surface> self(reinterpret_cast<Surface *>(nativeObject));
-    parcel->writeStrongBinder( self != 0 ? IInterface::asBinder(self->getIGraphicBufferProducer()) : NULL);
+    android::view::Surface surfaceShim;
+    if (self != nullptr) {
+        surfaceShim.graphicBufferProducer = self->getIGraphicBufferProducer();
+    }
+    // Calling code in Surface.java has already written the name of the Surface
+    // to the Parcel
+    surfaceShim.writeToParcel(parcel, /*nameAlreadyWritten*/true);
 }
 
 static jint nativeGetWidth(JNIEnv* env, jclass clazz, jlong nativeObject) {
diff --git a/core/jni/android_view_SurfaceControl.cpp b/core/jni/android_view_SurfaceControl.cpp
index 1dfe40a..c838d03 100644
--- a/core/jni/android_view_SurfaceControl.cpp
+++ b/core/jni/android_view_SurfaceControl.cpp
@@ -110,6 +110,13 @@
     ctrl->decStrong((void *)nativeCreate);
 }
 
+static void nativeDisconnect(JNIEnv* env, jclass clazz, jlong nativeObject) {
+    SurfaceControl* const ctrl = reinterpret_cast<SurfaceControl *>(nativeObject);
+    if (ctrl != NULL) {
+        ctrl->disconnect();
+    }
+}
+
 static jobject nativeScreenshotBitmap(JNIEnv* env, jclass clazz,
         jobject displayTokenObj, jobject sourceCropObj, jint width, jint height,
         jint minLayer, jint maxLayer, bool allLayers, bool useIdentityTransform,
@@ -595,6 +602,8 @@
             (void*)nativeRelease },
     {"nativeDestroy", "(J)V",
             (void*)nativeDestroy },
+    {"nativeDisconnect", "(J)V",
+            (void*)nativeDisconnect },
     {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/graphics/Rect;IIIIZZI)Landroid/graphics/Bitmap;",
             (void*)nativeScreenshotBitmap },
     {"nativeScreenshot", "(Landroid/os/IBinder;Landroid/view/Surface;Landroid/graphics/Rect;IIIIZZ)V",
diff --git a/core/res/res/drawable-nodpi/default_wallpaper.png b/core/res/res/drawable-nodpi/default_wallpaper.png
index 91ad252..e9c4d5c 100644
--- a/core/res/res/drawable-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
index af8e251..9f3efa5 100644
--- a/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-sw600dp-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
index cb00d82..8199e70 100644
--- a/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
+++ b/core/res/res/drawable-sw720dp-nodpi/default_wallpaper.png
Binary files differ
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index b65f19b..be28449 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -2479,4 +2479,8 @@
          handle wallpaper cropping.
     -->
     <string name="config_wallpaperCropperPackage" translatable="false">com.android.wallpapercropper</string>
+
+    <!-- True if the device supports at least one form of multi-window.
+         E.g. freeform, split-screen, picture-in-picture. -->
+    <bool name="config_supportsMultiWindow">true</bool>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index eba2e6a..786b3cc 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -303,6 +303,7 @@
   <java-symbol type="bool" name="config_supportSpeakerNearUltrasound" />
   <java-symbol type="bool" name="config_supportAudioSourceUnprocessed" />
   <java-symbol type="bool" name="config_freeformWindowManagement" />
+  <java-symbol type="bool" name="config_supportsMultiWindow" />
   <java-symbol type="bool" name="config_guestUserEphemeral" />
   <java-symbol type="string" name="config_defaultPictureInPictureBounds" />
   <java-symbol type="string" name="config_centeredPictureInPictureBounds" />
diff --git a/data/keyboards/qwerty2.kcm b/data/keyboards/qwerty2.kcm
index d96914f..b981d83 100644
--- a/data/keyboards/qwerty2.kcm
+++ b/data/keyboards/qwerty2.kcm
@@ -23,8 +23,8 @@
     number:                             '2'
     base:                               'a'
     shift, capslock:                    'A'
-    alt:                                'a'
-    shift+alt, capslock+alt:            'A'
+    alt:                                '\u00e1'
+    shift+alt, capslock+alt:            '\u00c1'
 }
 
 key B {
@@ -41,8 +41,8 @@
     number:                             '2'
     base:                               'c'
     shift, capslock:                    'C'
-    alt:                                '\u00e7'
-    shift+alt, capslock+alt:            '\u00e7'
+    alt:                                '\u00a9'
+    shift+alt, capslock+alt:            '\u00a2'
 }
 
 key D {
@@ -50,8 +50,8 @@
     number:                             '3'
     base:                               'd'
     shift, capslock:                    'D'
-    alt:                                '\''
-    shift+alt, capslock+alt:            '\''
+    alt:                                '\u00f0'
+    shift+alt, capslock+alt:            '\u00d0'
 }
 
 key E {
@@ -59,8 +59,8 @@
     number:                             '3'
     base:                               'e'
     shift, capslock:                    'E'
-    alt:                                '"'
-    shift+alt, capslock+alt:            '\u0301'
+    alt:                                '\u00e9'
+    shift+alt, capslock+alt:            '\u00c9'
 }
 
 key F {
@@ -95,8 +95,8 @@
     number:                             '4'
     base:                               'i'
     shift, capslock:                    'I'
-    alt:                                '-'
-    shift+alt, capslock+alt:            '\u0302'
+    alt:                                '\u00ed'
+    shift+alt, capslock+alt:            '\u00cd'
 }
 
 key J {
@@ -122,8 +122,8 @@
     number:                             '5'
     base:                               'l'
     shift, capslock:                    'L'
-    alt:                                ':'
-    shift+alt, capslock+alt:            '`'
+    alt:                                '\u00f8'
+    shift+alt, capslock+alt:            '\u00d8'
 }
 
 key M {
@@ -131,7 +131,7 @@
     number:                             '6'
     base:                               'm'
     shift, capslock:                    'M'
-    alt:                                '%'
+    alt:                                '\u00b5'
     shift+alt, capslock+alt:            none
 }
 
@@ -140,8 +140,8 @@
     number:                             '6'
     base:                               'n'
     shift, capslock:                    'N'
-    alt:                                none
-    shift+alt, capslock+alt:            '\u0303'
+    alt:                                '\u00f1'
+    shift+alt, capslock+alt:            '\u00d1'
 }
 
 key O {
@@ -149,8 +149,8 @@
     number:                             '6'
     base:                               'o'
     shift, capslock:                    'O'
-    alt:                                '+'
-    shift+alt, capslock+alt:            '+'
+    alt:                                '\u00f3'
+    shift+alt, capslock+alt:            '\u00d3'
 }
 
 key P {
@@ -158,8 +158,8 @@
     number:                             '7'
     base:                               'p'
     shift, capslock:                    'P'
-    alt:                                '='
-    shift+alt, capslock+alt:            '\u00a5'
+    alt:                                '\u00f6'
+    shift+alt, capslock+alt:            '\u00d6'
 }
 
 key Q {
@@ -167,8 +167,8 @@
     number:                             '7'
     base:                               'q'
     shift, capslock:                    'Q'
-    alt:                                '|'
-    shift+alt, capslock+alt:            '\u0300'
+    alt:                                '\u00e4'
+    shift+alt, capslock+alt:            '\u00c4'
 }
 
 key R {
@@ -176,8 +176,8 @@
     number:                             '7'
     base:                               'r'
     shift, capslock:                    'R'
-    alt:                                '`'
-    shift+alt, capslock+alt:            '\u20ac'
+    alt:                                '\u00ae'
+    shift+alt, capslock+alt:            'R'
 }
 
 key S {
@@ -185,8 +185,8 @@
     number:                             '7'
     base:                               's'
     shift, capslock:                    'S'
-    alt:                                '\\'
-    shift+alt, capslock+alt:            '\u00df'
+    alt:                                '\u00df'
+    shift+alt, capslock+alt:            '\u00a7'
 }
 
 key T {
@@ -194,8 +194,8 @@
     number:                             '8'
     base:                               't'
     shift, capslock:                    'T'
-    alt:                                '{'
-    shift+alt, capslock+alt:            '\u00a3'
+    alt:                                '\u00fe'
+    shift+alt, capslock+alt:            '\u00de'
 }
 
 key U {
@@ -203,8 +203,8 @@
     number:                             '8'
     base:                               'u'
     shift, capslock:                    'U'
-    alt:                                '_'
-    shift+alt, capslock+alt:            '\u0308'
+    alt:                                '\u00fa'
+    shift+alt, capslock+alt:            '\u00da'
 }
 
 key V {
@@ -221,8 +221,8 @@
     number:                             '9'
     base:                               'w'
     shift, capslock:                    'W'
-    alt:                                '~'
-    shift+alt, capslock+alt:            '~'
+    alt:                                '\u00e5'
+    shift+alt, capslock+alt:            '\u00c5'
 }
 
 key X {
@@ -239,8 +239,8 @@
     number:                             '9'
     base:                               'y'
     shift, capslock:                    'Y'
-    alt:                                '}'
-    shift+alt, capslock+alt:            '\u00a1'
+    alt:                                '\u00fc'
+    shift+alt, capslock+alt:            '\u00dc'
 }
 
 key Z {
@@ -248,8 +248,8 @@
     number:                             '9'
     base:                               'z'
     shift, capslock:                    'Z'
-    alt:                                'z'
-    shift+alt, capslock+alt:            'Z'
+    alt:                                '\u00e6'
+    shift+alt, capslock+alt:            '\u00c6'
 }
 
 key COMMA {
@@ -257,8 +257,8 @@
     number:                             ','
     base:                               ','
     shift:                              '<'
-    alt:                                ','
-    shift+alt:                          ','
+    alt:                                '\u00e7'
+    shift+alt:                          '\u00c7'
 }
 
 key PERIOD {
@@ -284,7 +284,7 @@
     number:                             '/'
     base:                               '/'
     shift:                              '?'
-    alt:                                '?'
+    alt:                                '\u00bf'
     shift+alt:                          '?'
 }
 
@@ -320,7 +320,7 @@
     number:                             '0'
     base:                               '0'
     shift:                              ')'
-    alt:                                ')'
+    alt:                                '\u02bc'
     shift+alt:                          ')'
 }
 
@@ -329,8 +329,8 @@
     number:                             '1'
     base:                               '1'
     shift:                              '!'
-    alt:                                '!'
-    shift+alt:                          '!'
+    alt:                                '\u00a1'
+    shift+alt:                          '\u00b9'
 }
 
 key 2 {
@@ -338,7 +338,7 @@
     number:                             '2'
     base:                               '2'
     shift:                              '@'
-    alt:                                '@'
+    alt:                                '\u00b2'
     shift+alt:                          '@'
 }
 
@@ -347,7 +347,7 @@
     number:                             '3'
     base:                               '3'
     shift:                              '#'
-    alt:                                '#'
+    alt:                                '\u00b3'
     shift+alt:                          '#'
 }
 
@@ -356,8 +356,8 @@
     number:                             '4'
     base:                               '4'
     shift:                              '$'
-    alt:                                '$'
-    shift+alt:                          '$'
+    alt:                                '\u00a4'
+    shift+alt:                          '\u00a3'
 }
 
 key 5 {
@@ -365,7 +365,7 @@
     number:                             '5'
     base:                               '5'
     shift:                              '%'
-    alt:                                '%'
+    alt:                                '\u20ac'
     shift+alt:                          '%'
 }
 
@@ -374,8 +374,8 @@
     number:                             '6'
     base:                               '6'
     shift:                              '^'
-    alt:                                '^'
-    shift+alt:                          '^'
+    alt:                                '\u00bc'
+    shift+alt:                          '\u0302'
 }
 
 key 7 {
@@ -383,7 +383,7 @@
     number:                             '7'
     base:                               '7'
     shift:                              '&'
-    alt:                                '&'
+    alt:                                '\u00bd'
     shift+alt:                          '&'
 }
 
@@ -392,7 +392,7 @@
     number:                             '8'
     base:                               '8'
     shift:                              '*'
-    alt:                                '*'
+    alt:                                '\u00be'
     shift+alt:                          '*'
 }
 
@@ -401,7 +401,7 @@
     number:                             '9'
     base:                               '9'
     shift:                              '('
-    alt:                                '('
+    alt:                                '\u02bb'
     shift+alt:                          '('
 }
 
@@ -410,8 +410,8 @@
     number:                             '`'
     base:                               '`'
     shift:                              '~'
-    alt:                                '`'
-    shift+alt:                          '~'
+    alt:                                '\u0300'
+    shift+alt:                          '\u0303'
 }
 
 key MINUS {
@@ -419,7 +419,7 @@
     number:                             '-'
     base:                               '-'
     shift:                              '_'
-    alt:                                '-'
+    alt:                                '\u00a5'
     shift+alt:                          '_'
 }
 
@@ -428,8 +428,8 @@
     number:                             '='
     base:                               '='
     shift:                              '+'
-    alt:                                '='
-    shift+alt:                          '+'
+    alt:                                '\u00d7'
+    shift+alt:                          '\u00f7'
 }
 
 key LEFT_BRACKET {
@@ -437,7 +437,7 @@
     number:                             '['
     base:                               '['
     shift:                              '{'
-    alt:                                '['
+    alt:                                '\u00ab'
     shift+alt:                          '{'
 }
 
@@ -446,7 +446,7 @@
     number:                             ']'
     base:                               ']'
     shift:                              '}'
-    alt:                                ']'
+    alt:                                '\u00bb'
     shift+alt:                          '}'
 }
 
@@ -455,8 +455,8 @@
     number:                             '\\'
     base:                               '\\'
     shift:                              '|'
-    alt:                                '\\'
-    shift+alt:                          '|'
+    alt:                                '\u00ac'
+    shift+alt:                          '\u00a6'
 }
 
 key SEMICOLON {
@@ -464,8 +464,8 @@
     number:                             ';'
     base:                               ';'
     shift:                              ':'
-    alt:                                ';'
-    shift+alt:                          ':'
+    alt:                                '\u00b6'
+    shift+alt:                          '\u00b0'
 }
 
 key APOSTROPHE {
@@ -473,8 +473,8 @@
     number:                             '\''
     base:                               '\''
     shift:                              '"'
-    alt:                                '\''
-    shift+alt:                          '"'
+    alt:                                '\u0301'
+    shift+alt:                          '\u0308'
 }
 
 key STAR {
diff --git a/libs/hwui/BakedOpDispatcher.cpp b/libs/hwui/BakedOpDispatcher.cpp
index 1aab3c7..8018006 100644
--- a/libs/hwui/BakedOpDispatcher.cpp
+++ b/libs/hwui/BakedOpDispatcher.cpp
@@ -30,6 +30,7 @@
 #include <algorithm>
 #include <math.h>
 #include <SkPaintDefaults.h>
+#include <SkPathOps.h>
 
 namespace android {
 namespace uirenderer {
@@ -527,6 +528,12 @@
         SkPath path;
         SkRect rect = getBoundsOfFill(op);
         path.addOval(rect);
+
+        if (state.computedState.localProjectionPathMask != nullptr) {
+            // Mask the ripple path by the local space projection mask in local space.
+            // Note that this can create CCW paths.
+            Op(path, *state.computedState.localProjectionPathMask, kIntersect_SkPathOp, &path);
+        }
         renderConvexPath(renderer, state, path, *(op.paint));
     }
 }
diff --git a/libs/hwui/BakedOpState.cpp b/libs/hwui/BakedOpState.cpp
index 682bd04..26653f7 100644
--- a/libs/hwui/BakedOpState.cpp
+++ b/libs/hwui/BakedOpState.cpp
@@ -63,9 +63,22 @@
         clipState = nullptr;
         clippedBounds.setEmpty();
     } else {
-        // Not rejected! compute true clippedBounds and clipSideFlags
+        // Not rejected! compute true clippedBounds, clipSideFlags, and path mask
         clipSideFlags = computeClipSideFlags(clipRect, clippedBounds);
         clippedBounds.doIntersect(clipRect);
+
+        if (CC_UNLIKELY(snapshot.projectionPathMask)) {
+            // map projection path mask from render target space into op space,
+            // so intersection with op geometry is possible
+            Matrix4 inverseTransform;
+            inverseTransform.loadInverse(transform);
+            SkMatrix skInverseTransform;
+            inverseTransform.copyTo(skInverseTransform);
+
+            auto localMask = allocator.create<SkPath>();
+            snapshot.projectionPathMask->transform(skInverseTransform, localMask);
+            localProjectionPathMask = localMask;
+        }
     }
 }
 
@@ -73,13 +86,15 @@
         : transform(*snapshot.transform)
         , clipState(snapshot.mutateClipArea().serializeClip(allocator))
         , clippedBounds(clipState->rect)
-        , clipSideFlags(OpClipSideFlags::Full) {}
+        , clipSideFlags(OpClipSideFlags::Full)
+        , localProjectionPathMask(nullptr) {}
 
 ResolvedRenderState::ResolvedRenderState(const ClipRect* clipRect, const Rect& dstRect)
         : transform(Matrix4::identity())
         , clipState(clipRect)
         , clippedBounds(dstRect)
-        , clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect)) {
+        , clipSideFlags(computeClipSideFlags(clipRect->rect, dstRect))
+        , localProjectionPathMask(nullptr) {
     clippedBounds.doIntersect(clipRect->rect);
 }
 
diff --git a/libs/hwui/BakedOpState.h b/libs/hwui/BakedOpState.h
index 4365ef8..ffe2901 100644
--- a/libs/hwui/BakedOpState.h
+++ b/libs/hwui/BakedOpState.h
@@ -88,6 +88,7 @@
     const ClipBase* clipState = nullptr;
     Rect clippedBounds;
     int clipSideFlags = 0;
+    const SkPath* localProjectionPathMask = nullptr;
 };
 
 /**
@@ -154,7 +155,6 @@
     // simple state (straight pointer/value storage):
     const float alpha;
     const RoundRectClipState* roundRectClipState;
-    const ProjectionPathMask* projectionPathMask;
     const RecordedOp* op;
 
 private:
@@ -165,21 +165,18 @@
             : computedState(allocator, snapshot, recordedOp, expandForStroke)
             , alpha(snapshot.alpha)
             , roundRectClipState(snapshot.roundRectClipState)
-            , projectionPathMask(snapshot.projectionPathMask)
             , op(&recordedOp) {}
 
     BakedOpState(LinearAllocator& allocator, Snapshot& snapshot, const ShadowOp* shadowOpPtr)
             : computedState(allocator, snapshot)
             , alpha(snapshot.alpha)
             , roundRectClipState(snapshot.roundRectClipState)
-            , projectionPathMask(snapshot.projectionPathMask)
             , op(shadowOpPtr) {}
 
     BakedOpState(const ClipRect* clipRect, const Rect& dstRect, const RecordedOp& recordedOp)
             : computedState(clipRect, dstRect)
             , alpha(1.0f)
             , roundRectClipState(nullptr)
-            , projectionPathMask(nullptr)
             , op(&recordedOp) {}
 };
 
diff --git a/libs/hwui/FrameBuilder.cpp b/libs/hwui/FrameBuilder.cpp
index 04de98a..1bc37e2 100644
--- a/libs/hwui/FrameBuilder.cpp
+++ b/libs/hwui/FrameBuilder.cpp
@@ -389,34 +389,38 @@
 }
 
 void FrameBuilder::deferProjectedChildren(const RenderNode& renderNode) {
-    const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
     int count = mCanvasState.save(SaveFlags::MatrixClip);
+    const SkPath* projectionReceiverOutline = renderNode.properties().getOutline().getPath();
 
-    // can't be null, since DL=null node rejection happens before deferNodePropsAndOps
-    const DisplayList& displayList = *(renderNode.getDisplayList());
-
-    const RecordedOp* op = (displayList.getOps()[displayList.projectionReceiveIndex]);
-    const RenderNodeOp* backgroundOp = static_cast<const RenderNodeOp*>(op);
-    const RenderProperties& backgroundProps = backgroundOp->renderNode->properties();
-
-    // Transform renderer to match background we're projecting onto
-    // (by offsetting canvas by translationX/Y of background rendernode, since only those are set)
-    mCanvasState.translate(backgroundProps.getTranslationX(), backgroundProps.getTranslationY());
-
-    // If the projection receiver has an outline, we mask projected content to it
-    // (which we know, apriori, are all tessellated paths)
-    mCanvasState.setProjectionPathMask(mAllocator, projectionReceiverOutline);
-
-    // draw projected nodes
-    for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
-        RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
-
-        int restoreTo = mCanvasState.save(SaveFlags::Matrix);
-        mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
-        deferRenderNodeOpImpl(*childOp);
-        mCanvasState.restoreToCount(restoreTo);
+    SkPath transformedMaskPath; // on stack, since BakedOpState makes a deep copy
+    if (projectionReceiverOutline) {
+        // transform the mask for this projector into render target space
+        // TODO: consider combining both transforms by stashing transform instead of applying
+        SkMatrix skCurrentTransform;
+        mCanvasState.currentTransform()->copyTo(skCurrentTransform);
+        projectionReceiverOutline->transform(
+                skCurrentTransform,
+                &transformedMaskPath);
+        mCanvasState.setProjectionPathMask(mAllocator, &transformedMaskPath);
     }
 
+    for (size_t i = 0; i < renderNode.mProjectedNodes.size(); i++) {
+        RenderNodeOp* childOp = renderNode.mProjectedNodes[i];
+        RenderNode& childNode = *childOp->renderNode;
+
+        // Draw child if it has content, but ignore state in childOp - matrix already applied to
+        // transformFromCompositingAncestor, and record-time clip is ignored when projecting
+        if (!childNode.nothingToDraw()) {
+            int restoreTo = mCanvasState.save(SaveFlags::MatrixClip);
+
+            // Apply transform between ancestor and projected descendant
+            mCanvasState.concatMatrix(childOp->transformFromCompositingAncestor);
+
+            deferNodePropsAndOps(childNode);
+
+            mCanvasState.restoreToCount(restoreTo);
+        }
+    }
     mCanvasState.restoreToCount(count);
 }
 
diff --git a/libs/hwui/LayerBuilder.cpp b/libs/hwui/LayerBuilder.cpp
index bc39621..c5af279 100644
--- a/libs/hwui/LayerBuilder.cpp
+++ b/libs/hwui/LayerBuilder.cpp
@@ -140,7 +140,10 @@
         // Identical round rect clip state means both ops will clip in the same way, or not at all.
         // As the state objects are const, we can compare their pointers to determine mergeability
         if (lhs->roundRectClipState != rhs->roundRectClipState) return false;
-        if (lhs->projectionPathMask != rhs->projectionPathMask) return false;
+
+        // Local masks prevent merge, since they're potentially in different coordinate spaces
+        if (lhs->computedState.localProjectionPathMask
+                || rhs->computedState.localProjectionPathMask) return false;
 
         /* Clipping compatibility check
          *
diff --git a/libs/hwui/OpenGLRenderer.cpp b/libs/hwui/OpenGLRenderer.cpp
index b7a5923..7693fdc 100644
--- a/libs/hwui/OpenGLRenderer.cpp
+++ b/libs/hwui/OpenGLRenderer.cpp
@@ -1148,7 +1148,9 @@
 
     // always store/restore, since these are just pointers
     state.mRoundRectClipState = currentSnapshot()->roundRectClipState;
+#if !HWUI_NEW_OPS
     state.mProjectionPathMask = currentSnapshot()->projectionPathMask;
+#endif
     return false;
 }
 
@@ -1156,7 +1158,9 @@
     setGlobalMatrix(state.mMatrix);
     writableSnapshot()->alpha = state.mAlpha;
     writableSnapshot()->roundRectClipState = state.mRoundRectClipState;
+#if !HWUI_NEW_OPS
     writableSnapshot()->projectionPathMask = state.mProjectionPathMask;
+#endif
 
     if (state.mClipValid && !skipClipRestore) {
         writableSnapshot()->setClip(state.mClip.left, state.mClip.top,
@@ -1833,6 +1837,7 @@
         path.addCircle(x, y, radius);
     }
 
+#if !HWUI_NEW_OPS
     if (CC_UNLIKELY(currentSnapshot()->projectionPathMask != nullptr)) {
         // mask ripples with projection mask
         SkPath maskPath = *(currentSnapshot()->projectionPathMask->projectionMask);
@@ -1852,6 +1857,7 @@
         // in local space. Note that this can create CCW paths.
         Op(path, maskPath, kIntersect_SkPathOp, &path);
     }
+#endif
     drawConvexPath(path, p);
 }
 
diff --git a/libs/hwui/Snapshot.cpp b/libs/hwui/Snapshot.cpp
index 27fea1f..cf5e69a 100644
--- a/libs/hwui/Snapshot.cpp
+++ b/libs/hwui/Snapshot.cpp
@@ -146,6 +146,9 @@
 }
 
 void Snapshot::buildScreenSpaceTransform(Matrix4* outTransform) const {
+#if HWUI_NEW_OPS
+    LOG_ALWAYS_FATAL("not supported - not needed by new ops");
+#else
     // build (reverse ordered) list of the stack of snapshots, terminated with a NULL
     Vector<const Snapshot*> snapshotList;
     snapshotList.push(nullptr);
@@ -171,6 +174,7 @@
             outTransform->multiply(*(current->transform));
         }
     }
+#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////
@@ -223,15 +227,19 @@
 }
 
 void Snapshot::setProjectionPathMask(LinearAllocator& allocator, const SkPath* path) {
+#if HWUI_NEW_OPS
+    // TODO: remove allocator param for HWUI_NEW_OPS
+    projectionPathMask = path;
+#else
     if (path) {
         ProjectionPathMask* mask = new (allocator) ProjectionPathMask;
         mask->projectionMask = path;
         buildScreenSpaceTransform(&(mask->projectionMaskTransform));
-
         projectionPathMask = mask;
     } else {
         projectionPathMask = nullptr;
     }
+#endif
 }
 
 ///////////////////////////////////////////////////////////////////////////////
diff --git a/libs/hwui/Snapshot.h b/libs/hwui/Snapshot.h
index b03643f..3a01d04 100644
--- a/libs/hwui/Snapshot.h
+++ b/libs/hwui/Snapshot.h
@@ -63,6 +63,7 @@
     float radius;
 };
 
+// TODO: remove for HWUI_NEW_OPS
 class ProjectionPathMask {
 public:
     static void* operator new(size_t size) = delete;
@@ -219,6 +220,7 @@
      * Fills outTransform with the current, total transform to screen space,
      * across layer boundaries.
      */
+    // TODO: remove for HWUI_NEW_OPS
     void buildScreenSpaceTransform(Matrix4* outTransform) const;
 
     /**
@@ -294,9 +296,13 @@
     const RoundRectClipState* roundRectClipState;
 
     /**
-     * Current projection masking path - used exclusively to mask tessellated circles.
+     * Current projection masking path - used exclusively to mask projected, tessellated circles.
      */
+#if HWUI_NEW_OPS
+    const SkPath* projectionPathMask;
+#else
     const ProjectionPathMask* projectionPathMask;
+#endif
 
     void dump() const;
 
diff --git a/libs/hwui/tests/unit/FrameBuilderTests.cpp b/libs/hwui/tests/unit/FrameBuilderTests.cpp
index f86898f..8802d07 100644
--- a/libs/hwui/tests/unit/FrameBuilderTests.cpp
+++ b/libs/hwui/tests/unit/FrameBuilderTests.cpp
@@ -990,21 +990,26 @@
                 EXPECT_EQ(Rect(100, 100), op.unmappedBounds);
                 EXPECT_EQ(SK_ColorWHITE, op.paint->getColor());
                 expectedMatrix.loadIdentity();
+                EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
                 break;
             case 1:
                 EXPECT_EQ(Rect(-10, -10, 60, 60), op.unmappedBounds);
                 EXPECT_EQ(SK_ColorDKGRAY, op.paint->getColor());
-                expectedMatrix.loadTranslate(50, 50, 0); // TODO: should scroll be respected here?
+                expectedMatrix.loadTranslate(50 - scrollX, 50 - scrollY, 0);
+                ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
+                EXPECT_EQ(Rect(-35, -30, 45, 50),
+                        Rect(state.computedState.localProjectionPathMask->getBounds()));
                 break;
             case 2:
                 EXPECT_EQ(Rect(100, 50), op.unmappedBounds);
                 EXPECT_EQ(SK_ColorBLUE, op.paint->getColor());
                 expectedMatrix.loadTranslate(-scrollX, 50 - scrollY, 0);
+                EXPECT_EQ(nullptr, state.computedState.localProjectionPathMask);
                 break;
             default:
                 ADD_FAILURE();
             }
-            EXPECT_MATRIX_APPROX_EQ(expectedMatrix, state.computedState.transform);
+            EXPECT_EQ(expectedMatrix, state.computedState.transform);
         }
     };
 
@@ -1045,6 +1050,9 @@
     });
     auto parent = TestUtils::createNode(0, 0, 100, 100,
             [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
+        // Set a rect outline for the projecting ripple to be masked against.
+        properties.mutableOutline().setRoundRect(10, 10, 90, 90, 5, 1.0f);
+
         canvas.save(SaveFlags::MatrixClip);
         canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
         canvas.drawRenderNode(receiverBackground.get());
@@ -1059,6 +1067,145 @@
     EXPECT_EQ(3, renderer.getIndex());
 }
 
+RENDERTHREAD_TEST(FrameBuilder, projectionHwLayer) {
+    static const int scrollX = 5;
+    static const int scrollY = 10;
+    class ProjectionHwLayerTestRenderer : public TestRendererBase {
+    public:
+        void startRepaintLayer(OffscreenBuffer* offscreenBuffer, const Rect& repaintRect) override {
+            EXPECT_EQ(0, mIndex++);
+        }
+        void onArcOp(const ArcOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
+        }
+        void endLayer() override {
+            EXPECT_EQ(2, mIndex++);
+        }
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(3, mIndex++);
+            ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
+        }
+        void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(4, mIndex++);
+            ASSERT_NE(nullptr, state.computedState.localProjectionPathMask);
+            Matrix4 expected;
+            expected.loadTranslate(100 - scrollX, 100 - scrollY, 0);
+            EXPECT_EQ(expected, state.computedState.transform);
+            EXPECT_EQ(Rect(-85, -80, 295, 300),
+                    Rect(state.computedState.localProjectionPathMask->getBounds()));
+        }
+        void onLayerOp(const LayerOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(5, mIndex++);
+            ASSERT_EQ(nullptr, state.computedState.localProjectionPathMask);
+        }
+    };
+    auto receiverBackground = TestUtils::createNode(0, 0, 400, 400,
+            [](RenderProperties& properties, RecordingCanvas& canvas) {
+        properties.setProjectionReceiver(true);
+        // scroll doesn't apply to background, so undone via translationX/Y
+        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+        properties.setTranslationX(scrollX);
+        properties.setTranslationY(scrollY);
+
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+    });
+    auto projectingRipple = TestUtils::createNode(0, 0, 200, 200,
+            [](RenderProperties& properties, RecordingCanvas& canvas) {
+        properties.setProjectBackwards(true);
+        properties.setClipToBounds(false);
+        canvas.drawOval(100, 100, 300, 300, SkPaint()); // drawn mostly out of layer bounds
+    });
+    auto child = TestUtils::createNode(100, 100, 300, 300,
+            [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
+        properties.mutateLayerProperties().setType(LayerType::RenderLayer);
+        canvas.drawRenderNode(projectingRipple.get());
+        canvas.drawArc(0, 0, 200, 200, 0.0f, 280.0f, true, SkPaint());
+    });
+    auto parent = TestUtils::createNode(0, 0, 400, 400,
+            [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
+        // Set a rect outline for the projecting ripple to be masked against.
+        properties.mutableOutline().setRoundRect(10, 10, 390, 390, 0, 1.0f);
+        canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
+        canvas.drawRenderNode(receiverBackground.get());
+        canvas.drawRenderNode(child.get());
+    });
+
+    OffscreenBuffer** layerHandle = child->getLayerHandle();
+
+    // create RenderNode's layer here in same way prepareTree would, setting windowTransform
+    OffscreenBuffer layer(renderThread.renderState(), Caches::getInstance(), 200, 200);
+    Matrix4 windowTransform;
+    windowTransform.loadTranslate(100, 100, 0); // total transform of layer's origin
+    layer.setWindowTransform(windowTransform);
+    *layerHandle = &layer;
+
+    auto syncedList = TestUtils::createSyncedNodeList(parent);
+    LayerUpdateQueue layerUpdateQueue; // Note: enqueue damage post-sync, so bounds are valid
+    layerUpdateQueue.enqueueLayerWithDamage(child.get(), Rect(200, 200));
+    FrameBuilder frameBuilder(layerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
+            syncedList, sLightGeometry, nullptr);
+    ProjectionHwLayerTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(6, renderer.getIndex());
+
+    // clean up layer pointer, so we can safely destruct RenderNode
+    *layerHandle = nullptr;
+}
+
+RENDERTHREAD_TEST(FrameBuilder, projectionChildScroll) {
+    static const int scrollX = 500000;
+    static const int scrollY = 0;
+    class ProjectionChildScrollTestRenderer : public TestRendererBase {
+    public:
+        void onRectOp(const RectOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(0, mIndex++);
+            EXPECT_TRUE(state.computedState.transform.isIdentity());
+        }
+        void onOvalOp(const OvalOp& op, const BakedOpState& state) override {
+            EXPECT_EQ(1, mIndex++);
+            ASSERT_NE(nullptr, state.computedState.clipState);
+            ASSERT_EQ(ClipMode::Rectangle, state.computedState.clipState->mode);
+            ASSERT_EQ(Rect(400, 400), state.computedState.clipState->rect);
+            EXPECT_TRUE(state.computedState.transform.isIdentity());
+        }
+    };
+    auto receiverBackground = TestUtils::createNode(0, 0, 400, 400,
+            [](RenderProperties& properties, RecordingCanvas& canvas) {
+        properties.setProjectionReceiver(true);
+        canvas.drawRect(0, 0, 400, 400, SkPaint());
+    });
+    auto projectingRipple = TestUtils::createNode(0, 0, 200, 200,
+            [](RenderProperties& properties, RecordingCanvas& canvas) {
+        // scroll doesn't apply to background, so undone via translationX/Y
+        // NOTE: translationX/Y only! no other transform properties may be set for a proj receiver!
+        properties.setTranslationX(scrollX);
+        properties.setTranslationY(scrollY);
+        properties.setProjectBackwards(true);
+        properties.setClipToBounds(false);
+        canvas.drawOval(0, 0, 200, 200, SkPaint());
+    });
+    auto child = TestUtils::createNode(0, 0, 400, 400,
+            [&projectingRipple](RenderProperties& properties, RecordingCanvas& canvas) {
+        // Record time clip will be ignored by projectee
+        canvas.clipRect(100, 100, 300, 300, SkRegion::kIntersect_Op);
+
+        canvas.translate(-scrollX, -scrollY); // Apply scroll (note: bg undoes this internally)
+        canvas.drawRenderNode(projectingRipple.get());
+    });
+    auto parent = TestUtils::createNode(0, 0, 400, 400,
+            [&receiverBackground, &child](RenderProperties& properties, RecordingCanvas& canvas) {
+        canvas.drawRenderNode(receiverBackground.get());
+        canvas.drawRenderNode(child.get());
+    });
+
+    FrameBuilder frameBuilder(sEmptyLayerUpdateQueue, SkRect::MakeWH(400, 400), 400, 400,
+            TestUtils::createSyncedNodeList(parent), sLightGeometry, nullptr);
+    ProjectionChildScrollTestRenderer renderer;
+    frameBuilder.replayBakedOps<TestDispatcher>(renderer);
+    EXPECT_EQ(2, renderer.getIndex());
+}
+
 // creates a 100x100 shadow casting node with provided translationZ
 static sp<RenderNode> createWhiteRectShadowCaster(float translationZ) {
     return TestUtils::createNode(0, 0, 100, 100,
diff --git a/media/jni/Android.mk b/media/jni/Android.mk
index fa9c48c..29739ca 100644
--- a/media/jni/Android.mk
+++ b/media/jni/Android.mk
@@ -64,7 +64,6 @@
     frameworks/av/media/mtp \
     frameworks/native/include/media/openmax \
     $(call include-path-for, libhardware)/hardware \
-    system/media/camera/include \
     $(PV_INCLUDES) \
     $(JNI_H_INCLUDE)
 
diff --git a/media/jni/android_media_MediaRecorder.cpp b/media/jni/android_media_MediaRecorder.cpp
index 701f7ac..922ad79 100644
--- a/media/jni/android_media_MediaRecorder.cpp
+++ b/media/jni/android_media_MediaRecorder.cpp
@@ -26,7 +26,6 @@
 #include <utils/Log.h>
 
 #include <gui/Surface.h>
-#include <camera/ICameraService.h>
 #include <camera/Camera.h>
 #include <media/mediarecorder.h>
 #include <media/stagefright/PersistentSurface.h>
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
index 61dede3..9be7004 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/MediaFrameworkUnitTestRunner.java
@@ -60,10 +60,7 @@
     }
 
     private void addCameraUnitTests(TestSuite suite) {
-        suite.addTestSuite(CameraUtilsDecoratorTest.class);
-        suite.addTestSuite(CameraUtilsRuntimeExceptionTest.class);
         suite.addTestSuite(CameraUtilsUncheckedThrowTest.class);
-        suite.addTestSuite(CameraUtilsBinderDecoratorTest.class);
         suite.addTestSuite(CameraUtilsTypeReferenceTest.class);
         suite.addTestSuite(CameraMetadataTest.class);
     }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
index 6f74203..9a0946e 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTest.java
@@ -19,16 +19,16 @@
 import android.hardware.CameraInfo;
 import android.hardware.ICamera;
 import android.hardware.ICameraClient;
+import android.hardware.ICameraService;
 import android.hardware.ICameraServiceListener;
 import android.hardware.camera2.ICameraDeviceCallbacks;
 import android.hardware.camera2.ICameraDeviceUser;
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
-import android.hardware.camera2.utils.BinderHolder;
-import android.hardware.camera2.utils.CameraBinderDecorator;
 import android.os.Binder;
 import android.os.IBinder;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.util.Log;
@@ -84,14 +84,7 @@
     public void testCameraInfo() throws Exception {
         for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
 
-            CameraInfo info = new CameraInfo();
-            info.info.facing = -1;
-            info.info.orientation = -1;
-
-            assertTrue(
-                    "Camera service returned info for camera " + cameraId,
-                    mUtils.getCameraService().getCameraInfo(cameraId, info) ==
-                    CameraBinderTestUtils.NO_ERROR);
+            CameraInfo info = mUtils.getCameraService().getCameraInfo(cameraId);
             assertTrue("Facing was not set for camera " + cameraId, info.info.facing != -1);
             assertTrue("Orientation was not set for camera " + cameraId,
                     info.info.orientation != -1);
@@ -105,20 +98,17 @@
     public void testGetLegacyParameters() throws Exception {
         for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
 
-            String[] parameters = new String[1];
-            assertEquals("Camera service returned parameters for camera " + cameraId,
-                    CameraBinderTestUtils.NO_ERROR,
-                    mUtils.getCameraService().getLegacyParameters(cameraId, /*out*/parameters));
-            assertNotNull(parameters[0]);
+            String parameters = mUtils.getCameraService().getLegacyParameters(cameraId);
+            assertNotNull(parameters);
             assertTrue("Parameters should have at least one character in it",
-                    parameters[0].length() > 0);
+                    parameters.length() > 0);
 
-            int end = parameters[0].length();
+            int end = parameters.length();
             if (end > MAX_PARAMETERS_LENGTH) {
                 end = MAX_PARAMETERS_LENGTH;
             }
 
-            Log.v(TAG, "Camera " + cameraId + " parameters: " + parameters[0].substring(0, end));
+            Log.v(TAG, "Camera " + cameraId + " parameters: " + parameters.substring(0, end));
         }
     }
 
@@ -127,14 +117,8 @@
     public void testSupportsCamera2Api() throws Exception {
         for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
 
-            int res = mUtils.getCameraService().supportsCameraApi(cameraId, API_VERSION_2);
+            boolean supports = mUtils.getCameraService().supportsCameraApi(cameraId, API_VERSION_2);
 
-            if (res != CameraBinderTestUtils.NO_ERROR && res != -android.system.OsConstants.EOPNOTSUPP) {
-                fail("Camera service returned bad value when queried if it supports camera2 api: "
-                        + res + " for camera ID " + cameraId);
-            }
-
-            boolean supports = res == CameraBinderTestUtils.NO_ERROR;
             Log.v(TAG, "Camera " + cameraId + " supports api2: " + supports);
         }
     }
@@ -144,10 +128,10 @@
     public void testSupportsCamera1Api() throws Exception {
         for (int cameraId = 0; cameraId < mUtils.getGuessedNumCameras(); ++cameraId) {
 
-            int res = mUtils.getCameraService().supportsCameraApi(cameraId, API_VERSION_1);
-            assertEquals(
-                    "Camera service returned bad value when queried if it supports camera1 api: "
-                    + res + " for camera ID " + cameraId, CameraBinderTestUtils.NO_ERROR, res);
+            boolean supports = mUtils.getCameraService().supportsCameraApi(cameraId, API_VERSION_1);
+            assertTrue(
+                    "Camera service returned false when queried if it supports camera1 api " +
+                    " for camera ID " + cameraId, supports);
         }
     }
 
@@ -169,11 +153,10 @@
 
             String clientPackageName = getContext().getPackageName();
 
-            BinderHolder holder = new BinderHolder();
-            CameraBinderDecorator.newInstance(mUtils.getCameraService())
+            ICamera cameraUser = mUtils.getCameraService()
                     .connect(dummyCallbacks, cameraId, clientPackageName,
-                    CameraBinderTestUtils.USE_CALLING_UID, holder);
-            ICamera cameraUser = ICamera.Stub.asInterface(holder.getBinder());
+                            ICameraService.USE_CALLING_UID,
+                            ICameraService.USE_CALLING_PID);
             assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
 
             Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -191,14 +174,11 @@
 
             String clientPackageName = getContext().getPackageName();
 
-            BinderHolder holder = new BinderHolder();
-
             try {
-                CameraBinderDecorator.newInstance(mUtils.getCameraService())
+                cameraUser = mUtils.getCameraService()
                         .connectLegacy(dummyCallbacks, cameraId, CAMERA_HAL_API_VERSION_1_0,
-                        clientPackageName,
-                        CameraBinderTestUtils.USE_CALLING_UID, holder);
-                cameraUser = ICamera.Stub.asInterface(holder.getBinder());
+                                clientPackageName,
+                                ICameraService.USE_CALLING_UID);
                 assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
 
                 Log.v(TAG, String.format("Camera %s connected as HAL1 legacy device", cameraId));
@@ -284,11 +264,11 @@
 
             String clientPackageName = getContext().getPackageName();
 
-            BinderHolder holder = new BinderHolder();
-            CameraBinderDecorator.newInstance(mUtils.getCameraService())
-                    .connectDevice(dummyCallbacks, cameraId,
-                    clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder);
-            ICameraDeviceUser cameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
+            ICameraDeviceUser cameraUser =
+                    mUtils.getCameraService().connectDevice(
+                        dummyCallbacks, cameraId,
+                        clientPackageName,
+                        ICameraService.USE_CALLING_UID);
             assertNotNull(String.format("Camera %s was null", cameraId), cameraUser);
 
             Log.v(TAG, String.format("Camera %s connected", cameraId));
@@ -323,27 +303,33 @@
 
             ICameraServiceListener listener = new DummyCameraServiceListener();
 
-            assertTrue(
-                    "Listener was removed before added",
-                    mUtils.getCameraService().removeListener(listener) ==
-                    CameraBinderTestUtils.BAD_VALUE);
+            try {
+                mUtils.getCameraService().removeListener(listener);
+                fail("Listener was removed before added");
+            } catch (ServiceSpecificException e) {
+                assertEquals("Listener was removed before added",
+                        e.errorCode, ICameraService.ERROR_ILLEGAL_ARGUMENT);
+            }
 
-            assertTrue("Listener was not added",
-                    mUtils.getCameraService().addListener(listener) ==
-                    CameraBinderTestUtils.NO_ERROR);
-            assertTrue(
-                    "Listener was wrongly added again",
-                    mUtils.getCameraService().addListener(listener) ==
-                    CameraBinderTestUtils.ALREADY_EXISTS);
+            mUtils.getCameraService().addListener(listener);
 
-            assertTrue(
-                    "Listener was not removed",
-                    mUtils.getCameraService().removeListener(listener) ==
-                    CameraBinderTestUtils.NO_ERROR);
-            assertTrue(
-                    "Listener was wrongly removed again",
-                    mUtils.getCameraService().removeListener(listener) ==
-                    CameraBinderTestUtils.BAD_VALUE);
+            try {
+                mUtils.getCameraService().addListener(listener);
+                fail("Listener was wrongly added again");
+            } catch (ServiceSpecificException e) {
+                assertEquals("Listener was wrongly added again",
+                        e.errorCode, ICameraService.ERROR_ALREADY_EXISTS);
+            }
+
+            mUtils.getCameraService().removeListener(listener);
+
+            try {
+                mUtils.getCameraService().removeListener(listener);
+                fail("Listener was wrongly removed twice");
+            } catch (ServiceSpecificException e) {
+                assertEquals("Listener was wrongly removed twice",
+                        e.errorCode, ICameraService.ERROR_ILLEGAL_ARGUMENT);
+            }
         }
     }
 }
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java
index 5c4b23b..38fc49f 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraBinderTestUtils.java
@@ -18,10 +18,6 @@
 
     static final String CAMERA_SERVICE_BINDER_NAME = "media.camera";
 
-    protected static final int USE_CALLING_UID = -1;
-    protected static final int BAD_VALUE = -EINVAL;
-    protected static final int INVALID_OPERATION = -ENOSYS;
-    protected static final int ALREADY_EXISTS = -EEXIST;
     public static final int NO_ERROR = 0;
     private final Context mContext;
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
index d71b44b..5c1d8a7 100644
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/integration/CameraDeviceBinderTest.java
@@ -18,6 +18,7 @@
 
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
+import android.hardware.ICameraService;
 import android.hardware.camera2.CameraMetadata;
 import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CameraCharacteristics;
@@ -27,12 +28,13 @@
 import android.hardware.camera2.impl.CameraMetadataNative;
 import android.hardware.camera2.impl.CaptureResultExtras;
 import android.hardware.camera2.params.OutputConfiguration;
-import android.hardware.camera2.utils.BinderHolder;
+import android.hardware.camera2.utils.SubmitInfo;
 import android.media.Image;
 import android.media.ImageReader;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.RemoteException;
+import android.os.ServiceSpecificException;
 import android.os.SystemClock;
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
@@ -164,11 +166,10 @@
     }
 
     private CaptureRequest.Builder createDefaultBuilder(boolean needStream) throws Exception {
-        CameraMetadataNative metadata = new CameraMetadataNative();
+        CameraMetadataNative metadata = null;
         assertTrue(metadata.isEmpty());
 
-        int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata);
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        metadata = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW);
         assertFalse(metadata.isEmpty());
 
         CaptureRequest.Builder request = new CaptureRequest.Builder(metadata, /*reprocess*/false,
@@ -183,12 +184,13 @@
         return request;
     }
 
-    private int submitCameraRequest(CaptureRequest request, boolean streaming) throws Exception {
-        int requestId = mCameraUser.submitRequest(request, streaming, null);
+    private SubmitInfo submitCameraRequest(CaptureRequest request, boolean streaming) throws Exception {
+        SubmitInfo requestInfo = mCameraUser.submitRequest(request, streaming);
         assertTrue(
-                "Request IDs should be non-negative (expected: >= 0, actual: " + requestId + ")",
-                requestId >= 0);
-        return requestId;
+                "Request IDs should be non-negative (expected: >= 0, actual: " +
+                requestInfo.getRequestId() + ")",
+                requestInfo.getRequestId() >= 0);
+        return requestInfo;
     }
 
     @Override
@@ -214,10 +216,8 @@
 
         mMockCb = spy(dummyCallbacks);
 
-        BinderHolder holder = new BinderHolder();
-        mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
-                clientPackageName, CameraBinderTestUtils.USE_CALLING_UID, holder);
-        mCameraUser = ICameraDeviceUser.Stub.asInterface(holder.getBinder());
+        mCameraUser = mUtils.getCameraService().connectDevice(mMockCb, mCameraId,
+                clientPackageName, ICameraService.USE_CALLING_UID);
         assertNotNull(String.format("Camera %s was null", mCameraId), mCameraUser);
         mHandlerThread = new HandlerThread(TAG);
         mHandlerThread.start();
@@ -238,11 +238,10 @@
 
     @SmallTest
     public void testCreateDefaultRequest() throws Exception {
-        CameraMetadataNative metadata = new CameraMetadataNative();
+        CameraMetadataNative metadata = null;
         assertTrue(metadata.isEmpty());
 
-        int status = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW, /* out */metadata);
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        metadata = mCameraUser.createDefaultRequest(TEMPLATE_PREVIEW);
         assertFalse(metadata.isEmpty());
 
     }
@@ -252,18 +251,28 @@
         int streamId = mCameraUser.createStream(mOutputConfiguration);
         assertEquals(0, streamId);
 
-        assertEquals(CameraBinderTestUtils.ALREADY_EXISTS,
-                mCameraUser.createStream(mOutputConfiguration));
+        try {
+            mCameraUser.createStream(mOutputConfiguration);
+            fail("Creating same stream twice");
+        } catch (ServiceSpecificException e) {
+            assertEquals("Creating same stream twice",
+                    e.errorCode, ICameraService.ERROR_ALREADY_EXISTS);
+        }
 
-        assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId));
+        mCameraUser.deleteStream(streamId);
     }
 
     @SmallTest
     public void testDeleteInvalidStream() throws Exception {
-        assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(-1));
-        assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(0));
-        assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(1));
-        assertEquals(CameraBinderTestUtils.BAD_VALUE, mCameraUser.deleteStream(0xC0FFEE));
+        int[] badStreams = { -1, 0, 1, 0xC0FFEE };
+        for (int badStream : badStreams) {
+            try {
+                mCameraUser.deleteStream(badStream);
+                fail("Allowed bad stream delete");
+            } catch (ServiceSpecificException e) {
+                assertEquals(e.errorCode, ICameraService.ERROR_ILLEGAL_ARGUMENT);
+            }
+        }
     }
 
     @SmallTest
@@ -273,8 +282,13 @@
         int streamId = mCameraUser.createStream(mOutputConfiguration);
         assertEquals(0, streamId);
 
-        assertEquals(CameraBinderTestUtils.ALREADY_EXISTS,
-                mCameraUser.createStream(mOutputConfiguration));
+        try {
+            mCameraUser.createStream(mOutputConfiguration);
+            fail("Created same stream twice");
+        } catch (ServiceSpecificException e) {
+            assertEquals("Created same stream twice",
+                    ICameraService.ERROR_ALREADY_EXISTS, e.errorCode);
+        }
 
         // Create second stream with a different surface.
         SurfaceTexture surfaceTexture = new SurfaceTexture(/* ignored */0);
@@ -286,8 +300,8 @@
         assertEquals(1, streamId2);
 
         // Clean up streams
-        assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId));
-        assertEquals(CameraBinderTestUtils.NO_ERROR, mCameraUser.deleteStream(streamId2));
+        mCameraUser.deleteStream(streamId);
+        mCameraUser.deleteStream(streamId2);
     }
 
     @SmallTest
@@ -295,16 +309,25 @@
 
         CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */false);
         CaptureRequest request1 = builder.build();
-        int status = mCameraUser.submitRequest(request1, /* streaming */false, null);
-        assertEquals("Expected submitRequest to return BAD_VALUE " +
-                "since we had 0 surface targets set.", CameraBinderTestUtils.BAD_VALUE, status);
+        try {
+            SubmitInfo requestInfo = mCameraUser.submitRequest(request1, /* streaming */false);
+            fail("Exception expected");
+        } catch(ServiceSpecificException e) {
+            assertEquals("Expected submitRequest to throw ServiceSpecificException with BAD_VALUE " +
+                    "since we had 0 surface targets set.", ICameraService.ERROR_ILLEGAL_ARGUMENT,
+                    e.errorCode);
+        }
 
         builder.addTarget(mSurface);
         CaptureRequest request2 = builder.build();
-        status = mCameraUser.submitRequest(request2, /* streaming */false, null);
-        assertEquals("Expected submitRequest to return BAD_VALUE since " +
-                "the target surface wasn't registered with createStream.",
-                CameraBinderTestUtils.BAD_VALUE, status);
+        try {
+            SubmitInfo requestInfo = mCameraUser.submitRequest(request2, /* streaming */false);
+            fail("Exception expected");
+        } catch(ServiceSpecificException e) {
+            assertEquals("Expected submitRequest to throw ILLEGAL_ARGUMENT " +
+                    "ServiceSpecificException since the target wasn't registered with createStream.",
+                    ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
+        }
     }
 
     @SmallTest
@@ -314,9 +337,10 @@
         CaptureRequest request = builder.build();
 
         // Submit valid request twice.
-        int requestId1 = submitCameraRequest(request, /* streaming */false);
-        int requestId2 = submitCameraRequest(request, /* streaming */false);
-        assertNotSame("Request IDs should be unique for multiple requests", requestId1, requestId2);
+        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
+        assertNotSame("Request IDs should be unique for multiple requests",
+                requestInfo1.getRequestId(), requestInfo2.getRequestId());
 
     }
 
@@ -329,32 +353,35 @@
 
         // Submit valid request once (non-streaming), and another time
         // (streaming)
-        int requestId1 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
 
-        int requestIdStreaming = submitCameraRequest(request, /* streaming */true);
-        assertNotSame("Request IDs should be unique for multiple requests", requestId1,
-                requestIdStreaming);
+        SubmitInfo requestInfoStreaming = submitCameraRequest(request, /* streaming */true);
+        assertNotSame("Request IDs should be unique for multiple requests",
+                requestInfo1.getRequestId(),
+                requestInfoStreaming.getRequestId());
 
-        int status = mCameraUser.cancelRequest(-1, null);
-        assertEquals("Invalid request IDs should not be cancellable",
-                CameraBinderTestUtils.BAD_VALUE, status);
+        try {
+            long lastFrameNumber = mCameraUser.cancelRequest(-1);
+            fail("Expected exception");
+        } catch (ServiceSpecificException e) {
+            assertEquals("Invalid request IDs should not be cancellable",
+                    ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
+        }
 
-        status = mCameraUser.cancelRequest(requestId1, null);
-        assertEquals("Non-streaming request IDs should not be cancellable",
-                CameraBinderTestUtils.BAD_VALUE, status);
+        try {
+            long lastFrameNumber = mCameraUser.cancelRequest(requestInfo1.getRequestId());
+            fail("Expected exception");
+        } catch (ServiceSpecificException e) {
+            assertEquals("Non-streaming request IDs should not be cancellable",
+                    ICameraService.ERROR_ILLEGAL_ARGUMENT, e.errorCode);
+        }
 
-        status = mCameraUser.cancelRequest(requestIdStreaming, null);
-        assertEquals("Streaming request IDs should be cancellable", CameraBinderTestUtils.NO_ERROR,
-                status);
-
+        long lastFrameNumber = mCameraUser.cancelRequest(requestInfoStreaming.getRequestId());
     }
 
     @SmallTest
     public void testCameraInfo() throws RemoteException {
-        CameraMetadataNative info = new CameraMetadataNative();
-
-        int status = mCameraUser.getCameraInfo(/*out*/info);
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        CameraMetadataNative info = mCameraUser.getCameraInfo();
 
         assertFalse(info.isEmpty());
         assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
@@ -362,10 +389,7 @@
 
     @SmallTest
     public void testCameraCharacteristics() throws RemoteException {
-        CameraMetadataNative info = new CameraMetadataNative();
-
-        int status = mUtils.getCameraService().getCameraCharacteristics(mCameraId, /*out*/info);
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        CameraMetadataNative info = mUtils.getCameraService().getCameraCharacteristics(mCameraId);
 
         assertFalse(info.isEmpty());
         assertNotNull(info.get(CameraCharacteristics.SCALER_AVAILABLE_FORMATS));
@@ -374,18 +398,19 @@
     @SmallTest
     public void testWaitUntilIdle() throws Exception {
         CaptureRequest.Builder builder = createDefaultBuilder(/* needStream */true);
-        int requestIdStreaming = submitCameraRequest(builder.build(), /* streaming */true);
+        SubmitInfo requestInfoStreaming = submitCameraRequest(builder.build(), /* streaming */true);
 
         // Test Bad case first: waitUntilIdle when there is active repeating request
-        int status = mCameraUser.waitUntilIdle();
-        assertEquals("waitUntilIdle is invalid operation when there is active repeating request",
-            CameraBinderTestUtils.INVALID_OPERATION, status);
+        try {
+            mCameraUser.waitUntilIdle();
+        } catch (ServiceSpecificException e) {
+            assertEquals("waitUntilIdle is invalid operation when there is active repeating request",
+                    ICameraService.ERROR_INVALID_OPERATION, e.errorCode);
+        }
 
         // Test good case, waitUntilIdle when there is no active repeating request
-        status = mCameraUser.cancelRequest(requestIdStreaming, null);
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
-        status = mCameraUser.waitUntilIdle();
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        long lastFrameNumber = mCameraUser.cancelRequest(requestInfoStreaming.getRequestId());
+        mCameraUser.waitUntilIdle();
     }
 
     @SmallTest
@@ -411,12 +436,12 @@
         ArgumentCaptor<Long> timestamps = ArgumentCaptor.forClass(Long.class);
 
         // Test both single request and streaming request.
-        int requestId1 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
         verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).times(1)).onCaptureStarted(
                 any(CaptureResultExtras.class),
                 anyLong());
 
-        int streamingId = submitCameraRequest(request, /* streaming */true);
+        SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
         verify(mMockCb, timeout(WAIT_FOR_COMPLETE_TIMEOUT_MS).atLeast(NUM_CALLBACKS_CHECKED))
                 .onCaptureStarted(
                         any(CaptureResultExtras.class),
@@ -436,22 +461,22 @@
         CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
 
         // Try streaming
-        int streamingId = submitCameraRequest(request, /* streaming */true);
+        SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
 
         // Wait a bit to fill up the queue
         SystemClock.sleep(WAIT_FOR_WORK_MS);
 
         // Cancel and make sure we eventually quiesce
-        status = mCameraUser.cancelRequest(streamingId, null);
+        long lastFrameNumber = mCameraUser.cancelRequest(streamingInfo.getRequestId());
 
         verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(1)).onDeviceIdle();
 
         // Submit a few capture requests
-        int requestId1 = submitCameraRequest(request, /* streaming */false);
-        int requestId2 = submitCameraRequest(request, /* streaming */false);
-        int requestId3 = submitCameraRequest(request, /* streaming */false);
-        int requestId4 = submitCameraRequest(request, /* streaming */false);
-        int requestId5 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo3 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo4 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo5 = submitCameraRequest(request, /* streaming */false);
 
         // And wait for more idle
         verify(mMockCb, timeout(WAIT_FOR_IDLE_TIMEOUT_MS).times(2)).onDeviceIdle();
@@ -463,38 +488,34 @@
         int status;
 
         // Initial flush should work
-        status = mCameraUser.flush(null);
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        long lastFrameNumber = mCameraUser.flush();
 
         // Then set up a stream
         CaptureRequest request = createDefaultBuilder(/* needStream */true).build();
 
         // Flush should still be a no-op, really
-        status = mCameraUser.flush(null);
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        lastFrameNumber = mCameraUser.flush();
 
         // Submit a few capture requests
-        int requestId1 = submitCameraRequest(request, /* streaming */false);
-        int requestId2 = submitCameraRequest(request, /* streaming */false);
-        int requestId3 = submitCameraRequest(request, /* streaming */false);
-        int requestId4 = submitCameraRequest(request, /* streaming */false);
-        int requestId5 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo1 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo2 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo3 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo4 = submitCameraRequest(request, /* streaming */false);
+        SubmitInfo requestInfo5 = submitCameraRequest(request, /* streaming */false);
 
         // Then flush and wait for idle
-        status = mCameraUser.flush(null);
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        lastFrameNumber = mCameraUser.flush();
 
         verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(1)).onDeviceIdle();
 
         // Now a streaming request
-        int streamingId = submitCameraRequest(request, /* streaming */true);
+        SubmitInfo streamingInfo = submitCameraRequest(request, /* streaming */true);
 
         // Wait a bit to fill up the queue
         SystemClock.sleep(WAIT_FOR_WORK_MS);
 
         // Then flush and wait for the idle callback
-        status = mCameraUser.flush(null);
-        assertEquals(CameraBinderTestUtils.NO_ERROR, status);
+        lastFrameNumber = mCameraUser.flush();
 
         verify(mMockCb, timeout(WAIT_FOR_FLUSH_TIMEOUT_MS).times(2)).onDeviceIdle();
 
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java
deleted file mode 100644
index 33c6388..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsBinderDecoratorTest.java
+++ /dev/null
@@ -1,173 +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.mediaframeworktest.unit;
-
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.utils.CameraBinderDecorator;
-import android.hardware.camera2.utils.CameraRuntimeException;
-import android.os.DeadObjectException;
-import android.os.RemoteException;
-import android.os.TransactionTooLargeException;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import static org.mockito.Mockito.*;
-import static android.hardware.camera2.utils.CameraBinderDecorator.*;
-import static android.hardware.camera2.CameraAccessException.*;
-import static android.system.OsConstants.*;
-
-import junit.framework.Assert;
-
-public class CameraUtilsBinderDecoratorTest extends junit.framework.TestCase {
-
-    private interface ICameraBinderStereotype {
-
-        double doNothing();
-
-        // int is a 'status_t'
-        int doSomethingPositive();
-
-        int doSomethingNoError();
-
-        int doSomethingPermissionDenied();
-
-        int doSomethingAlreadyExists();
-
-        int doSomethingBadValue();
-
-        int doSomethingDeadObject() throws CameraRuntimeException;
-
-        int doSomethingBadPolicy() throws CameraRuntimeException;
-
-        int doSomethingDeviceBusy() throws CameraRuntimeException;
-
-        int doSomethingNoSuchDevice() throws CameraRuntimeException;
-
-        int doSomethingUnknownErrorCode();
-
-        int doSomethingThrowDeadObjectException() throws RemoteException;
-
-        int doSomethingThrowTransactionTooLargeException() throws RemoteException;
-    }
-
-    private static final double SOME_ARBITRARY_DOUBLE = 1.0;
-    private static final int SOME_ARBITRARY_POSITIVE_INT = 5;
-    private static final int SOME_ARBITRARY_NEGATIVE_INT = -0xC0FFEE;
-
-    @SmallTest
-    public void testStereotypes() {
-
-        ICameraBinderStereotype mock = mock(ICameraBinderStereotype.class);
-        try {
-            when(mock.doNothing()).thenReturn(SOME_ARBITRARY_DOUBLE);
-            when(mock.doSomethingPositive()).thenReturn(SOME_ARBITRARY_POSITIVE_INT);
-            when(mock.doSomethingNoError()).thenReturn(NO_ERROR);
-            when(mock.doSomethingPermissionDenied()).thenReturn(PERMISSION_DENIED);
-            when(mock.doSomethingAlreadyExists()).thenReturn(ALREADY_EXISTS);
-            when(mock.doSomethingBadValue()).thenReturn(BAD_VALUE);
-            when(mock.doSomethingDeadObject()).thenReturn(DEAD_OBJECT);
-            when(mock.doSomethingBadPolicy()).thenReturn(-EACCES);
-            when(mock.doSomethingDeviceBusy()).thenReturn(-EBUSY);
-            when(mock.doSomethingNoSuchDevice()).thenReturn(-ENODEV);
-            when(mock.doSomethingUnknownErrorCode()).thenReturn(SOME_ARBITRARY_NEGATIVE_INT);
-            when(mock.doSomethingThrowDeadObjectException()).thenThrow(new DeadObjectException());
-            when(mock.doSomethingThrowTransactionTooLargeException()).thenThrow(
-                    new TransactionTooLargeException());
-        } catch (RemoteException e) {
-            Assert.fail("Unreachable");
-        }
-
-        ICameraBinderStereotype decoratedMock = CameraBinderDecorator.newInstance(mock);
-
-        // ignored by decorator because return type is double, not int
-        assertEquals(SOME_ARBITRARY_DOUBLE, decoratedMock.doNothing());
-
-        // pass through for positive values
-        assertEquals(SOME_ARBITRARY_POSITIVE_INT, decoratedMock.doSomethingPositive());
-
-        // pass through NO_ERROR
-        assertEquals(NO_ERROR, decoratedMock.doSomethingNoError());
-
-        try {
-            decoratedMock.doSomethingPermissionDenied();
-            Assert.fail("Should've thrown SecurityException");
-        } catch (SecurityException e) {
-        }
-
-        assertEquals(ALREADY_EXISTS, decoratedMock.doSomethingAlreadyExists());
-
-        try {
-            decoratedMock.doSomethingBadValue();
-            Assert.fail("Should've thrown IllegalArgumentException");
-        } catch (IllegalArgumentException e) {
-        }
-
-        try {
-            decoratedMock.doSomethingDeadObject();
-            Assert.fail("Should've thrown CameraRuntimeException");
-        } catch (CameraRuntimeException e) {
-            assertEquals(CAMERA_DISCONNECTED, e.getReason());
-        }
-
-        try {
-            decoratedMock.doSomethingBadPolicy();
-            Assert.fail("Should've thrown CameraRuntimeException");
-        } catch (CameraRuntimeException e) {
-            assertEquals(CAMERA_DISABLED, e.getReason());
-        }
-
-        try {
-            decoratedMock.doSomethingDeviceBusy();
-            Assert.fail("Should've thrown CameraRuntimeException");
-        } catch (CameraRuntimeException e) {
-            assertEquals(CAMERA_IN_USE, e.getReason());
-        }
-
-        try {
-            decoratedMock.doSomethingNoSuchDevice();
-            Assert.fail("Should've thrown CameraRuntimeException");
-        } catch (CameraRuntimeException e) {
-            assertEquals(CAMERA_DISCONNECTED, e.getReason());
-        }
-
-        try {
-            decoratedMock.doSomethingUnknownErrorCode();
-            Assert.fail("Should've thrown UnsupportedOperationException");
-        } catch (UnsupportedOperationException e) {
-            assertEquals(String.format("Unknown error %d",
-                    SOME_ARBITRARY_NEGATIVE_INT), e.getMessage());
-        }
-
-        try {
-            decoratedMock.doSomethingThrowDeadObjectException();
-            Assert.fail("Should've thrown CameraRuntimeException");
-        } catch (CameraRuntimeException e) {
-            assertEquals(CAMERA_DISCONNECTED, e.getReason());
-        } catch (RemoteException e) {
-            Assert.fail("Should not throw a DeadObjectException directly, but rethrow");
-        }
-
-        try {
-            decoratedMock.doSomethingThrowTransactionTooLargeException();
-            Assert.fail("Should've thrown UnsupportedOperationException");
-        } catch (UnsupportedOperationException e) {
-            assertTrue(e.getCause() instanceof TransactionTooLargeException);
-        } catch (RemoteException e) {
-            Assert.fail("Should not throw a TransactionTooLargeException directly, but rethrow");
-        }
-    }
-
-}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java
deleted file mode 100644
index c3b6006..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsDecoratorTest.java
+++ /dev/null
@@ -1,171 +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.mediaframeworktest.unit;
-
-import android.test.suitebuilder.annotation.SmallTest;
-import android.hardware.camera2.utils.*;
-import android.hardware.camera2.utils.Decorator.DecoratorListener;
-
-import junit.framework.Assert;
-
-import java.lang.reflect.Method;
-
-/**
- * adb shell am instrument -e class 'com.android.mediaframeworktest.unit.CameraUtilsDecoratorTest' \
- *      -w com.android.mediaframeworktest/.MediaFrameworkUnitTestRunner
- */
-public class CameraUtilsDecoratorTest extends junit.framework.TestCase {
-    private DummyListener mDummyListener;
-    private DummyInterface mIface;
-
-    @Override
-    public void setUp() {
-        mDummyListener = new DummyListener();
-        mIface = Decorator.newInstance(new DummyImpl(), mDummyListener);
-    }
-
-    interface DummyInterface {
-        int addValues(int x, int y, int z);
-
-        void raiseException() throws Exception;
-
-        void raiseUnsupportedOperationException() throws UnsupportedOperationException;
-    }
-
-    class DummyImpl implements DummyInterface {
-        @Override
-        public int addValues(int x, int y, int z) {
-            return x + y + z;
-        }
-
-        @Override
-        public void raiseException() throws Exception {
-            throw new Exception("Test exception");
-        }
-
-        @Override
-        public void raiseUnsupportedOperationException() throws UnsupportedOperationException {
-            throw new UnsupportedOperationException("Test exception");
-        }
-    }
-
-    class DummyListener implements DecoratorListener {
-
-        public boolean beforeCalled = false;
-        public boolean afterCalled = false;
-        public boolean catchCalled = false;
-        public boolean finallyCalled = false;
-        public Object resultValue = null;
-
-        public boolean raiseException = false;
-
-        @Override
-        public void onBeforeInvocation(Method m, Object[] args) {
-            beforeCalled = true;
-        }
-
-        @Override
-        public void onAfterInvocation(Method m, Object[] args, Object result) {
-            afterCalled = true;
-            resultValue = result;
-
-            if (raiseException) {
-                throw new UnsupportedOperationException("Test exception");
-            }
-        }
-
-        @Override
-        public boolean onCatchException(Method m, Object[] args, Throwable t) {
-            catchCalled = true;
-            return false;
-        }
-
-        @Override
-        public void onFinally(Method m, Object[] args) {
-            finallyCalled = true;
-        }
-
-    };
-
-    @SmallTest
-    public void testDecorator() {
-
-        // TODO rewrite this using mocks
-
-        assertTrue(mIface.addValues(1, 2, 3) == 6);
-        assertTrue(mDummyListener.beforeCalled);
-        assertTrue(mDummyListener.afterCalled);
-
-        int resultValue = (Integer)mDummyListener.resultValue;
-        assertTrue(resultValue == 6);
-        assertTrue(mDummyListener.finallyCalled);
-        assertFalse(mDummyListener.catchCalled);
-    }
-
-    @SmallTest
-    public void testDecoratorExceptions() {
-
-        boolean gotExceptions = false;
-        try {
-            mIface.raiseException();
-        } catch (Exception e) {
-            gotExceptions = true;
-            assertTrue(e.getMessage() == "Test exception");
-        }
-        assertTrue(gotExceptions);
-        assertTrue(mDummyListener.beforeCalled);
-        assertFalse(mDummyListener.afterCalled);
-        assertTrue(mDummyListener.catchCalled);
-        assertTrue(mDummyListener.finallyCalled);
-    }
-
-    @SmallTest
-    public void testDecoratorUnsupportedOperationException() {
-
-        boolean gotExceptions = false;
-        try {
-            mIface.raiseUnsupportedOperationException();
-        } catch (UnsupportedOperationException e) {
-            gotExceptions = true;
-            assertTrue(e.getMessage() == "Test exception");
-        }
-        assertTrue(gotExceptions);
-        assertTrue(mDummyListener.beforeCalled);
-        assertFalse(mDummyListener.afterCalled);
-        assertTrue(mDummyListener.catchCalled);
-        assertTrue(mDummyListener.finallyCalled);
-    }
-
-    @SmallTest
-    public void testDecoratorRaisesException() {
-
-        boolean gotExceptions = false;
-        try {
-            mDummyListener.raiseException = true;
-            mIface.addValues(1, 2, 3);
-            Assert.fail("unreachable");
-        } catch (UnsupportedOperationException e) {
-            gotExceptions = true;
-            assertTrue(e.getMessage() == "Test exception");
-        }
-        assertTrue(gotExceptions);
-        assertTrue(mDummyListener.beforeCalled);
-        assertTrue(mDummyListener.afterCalled);
-        assertFalse(mDummyListener.catchCalled);
-        assertTrue(mDummyListener.finallyCalled);
-    }
-}
diff --git a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java
deleted file mode 100644
index 02c9f2a..0000000
--- a/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/CameraUtilsRuntimeExceptionTest.java
+++ /dev/null
@@ -1,77 +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.mediaframeworktest.unit;
-
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.utils.CameraRuntimeException;
-import android.hardware.camera2.utils.UncheckedThrow;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import junit.framework.Assert;
-
-public class CameraUtilsRuntimeExceptionTest extends junit.framework.TestCase {
-
-    @SmallTest
-    public void testCameraRuntimeException1() {
-        try {
-            CameraRuntimeException runtimeExc = new CameraRuntimeException(12345);
-            throw runtimeExc.asChecked();
-        } catch (CameraAccessException e) {
-            assertEquals(12345, e.getReason());
-            assertNull(e.getMessage());
-            assertNull(e.getCause());
-        }
-    }
-
-    @SmallTest
-    public void testCameraRuntimeException2() {
-        try {
-            CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, "Hello");
-            throw runtimeExc.asChecked();
-        } catch (CameraAccessException e) {
-            assertEquals(12345, e.getReason());
-            assertEquals("Hello", e.getMessage());
-            assertNull(e.getCause());
-        }
-    }
-
-    @SmallTest
-    public void testCameraRuntimeException3() {
-        Throwable cause = new IllegalStateException("For great justice");
-        try {
-            CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, cause);
-            throw runtimeExc.asChecked();
-        } catch (CameraAccessException e) {
-            assertEquals(12345, e.getReason());
-            assertNull(e.getMessage());
-            assertEquals(cause, e.getCause());
-        }
-    }
-
-    @SmallTest
-    public void testCameraRuntimeException4() {
-        Throwable cause = new IllegalStateException("For great justice");
-        try {
-            CameraRuntimeException runtimeExc = new CameraRuntimeException(12345, "Hello", cause);
-            throw runtimeExc.asChecked();
-        } catch (CameraAccessException e) {
-            assertEquals(12345, e.getReason());
-            assertEquals("Hello", e.getMessage());
-            assertEquals(cause, e.getCause());
-        }
-    }
-}
diff --git a/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml b/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml
new file mode 100644
index 0000000..990ce0b
--- /dev/null
+++ b/packages/DocumentsUI/res/layout/dialog_delete_confirmation.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 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.
+-->
+
+<TextView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:paddingTop="30dp"
+    android:gravity="center"
+    android:textAppearance="@android:style/TextAppearance.Material.Subhead"
+    android:textColor="@*android:color/primary_text_default_material_light">
+</TextView>
diff --git a/packages/DocumentsUI/res/layout/item_root.xml b/packages/DocumentsUI/res/layout/item_root.xml
index ff80d07..816cb8a 100644
--- a/packages/DocumentsUI/res/layout/item_root.xml
+++ b/packages/DocumentsUI/res/layout/item_root.xml
@@ -27,8 +27,6 @@
     <FrameLayout
         android:layout_width="@dimen/icon_size"
         android:layout_height="@dimen/icon_size"
-        android:layout_marginStart="@dimen/root_icon_margin"
-        android:layout_marginEnd="@dimen/root_icon_margin"
         android:duplicateParentState="true">
 
         <ImageView
@@ -55,7 +53,7 @@
             android:singleLine="true"
             android:ellipsize="end"
             android:textAlignment="viewStart"
-            android:textAppearance="@android:style/TextAppearance.Material.Body1"
+            android:textAppearance="@android:style/TextAppearance.Material.Menu"
             android:textColor="@color/item_root_primary_text" />
 
         <TextView
@@ -65,7 +63,7 @@
             android:singleLine="true"
             android:ellipsize="end"
             android:textAlignment="viewStart"
-            android:textAppearance="@android:style/TextAppearance.Material.Body1"
+            android:textAppearance="@android:style/TextAppearance.Material.Caption"
             android:textColor="@color/item_root_primary_text" />
 
     </LinearLayout>
diff --git a/packages/DocumentsUI/res/values-sw720dp/dimens.xml b/packages/DocumentsUI/res/values-sw720dp/dimens.xml
index 2488fa2..b5d1150 100644
--- a/packages/DocumentsUI/res/values-sw720dp/dimens.xml
+++ b/packages/DocumentsUI/res/values-sw720dp/dimens.xml
@@ -18,4 +18,6 @@
     <dimen name="grid_padding_horiz">16dp</dimen>
     <dimen name="grid_padding_vert">16dp</dimen>
 
+    <dimen name="list_item_padding">24dp</dimen>
+
 </resources>
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index 3dc111a..6e1b30e 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -208,11 +208,9 @@
     <string name="allow">Allow</string>
     <!-- Text in the button asking user to deny access to a given directory. -->
     <string name="deny">Deny</string>
-    <!-- Dialog title shown to users when asking if they want to delete files (a confirmation). -->
-    <string name="delete_confirmation_title">Delete files?</string>
     <!-- Dialog text shown to users when asking if they want to delete files (a confirmation). -->
     <plurals name="delete_confirmation_message">
-        <item quantity="one">Are you sure you want to delete <xliff:g id="count" example="1">%1$d</xliff:g> file?</item>
-        <item quantity="other">Are you sure you want to delete <xliff:g id="count" example="3">%1$d</xliff:g> files?</item>
+        <item quantity="one">Delete <xliff:g id="count" example="1">%1$d</xliff:g> file?</item>
+        <item quantity="other">Delete <xliff:g id="count" example="3">%1$d</xliff:g> files?</item>
     </plurals>
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 1348a58..83838d3 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -168,6 +168,7 @@
     private GridLayoutManager mLayout;
     private int mColumnCount = 1;  // This will get updated when layout changes.
 
+    private LayoutInflater mInflater;
     private MessageBar mMessageBar;
     private View mProgressBar;
 
@@ -182,13 +183,12 @@
     @Override
     public View onCreateView(
             LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+        mInflater = inflater;
         final View view = inflater.inflate(R.layout.fragment_directory, container, false);
 
         mMessageBar = MessageBar.create(getChildFragmentManager());
         mProgressBar = view.findViewById(R.id.progressbar);
-
         mEmptyView = view.findViewById(android.R.id.empty);
-
         mRecView = (RecyclerView) view.findViewById(R.id.dir_list);
         mRecView.setRecyclerListener(
                 new RecyclerListener() {
@@ -708,13 +708,27 @@
         new GetDocumentsTask() {
             @Override
             void onDocumentsReady(final List<DocumentInfo> docs) {
+
+                TextView message =
+                        (TextView) mInflater.inflate(R.layout.dialog_delete_confirmation, null);
+                message.setText(
+                        Shared.getQuantityString(
+                                getActivity(),
+                                R.plurals.delete_confirmation_message,
+                                docs.size()));
+
+                // This "insta-hides" files that are being deleted, because
+                // the delete operation may be not execute immediately (it
+                // may be queued up on the FileOperationService.)
+                // To hide the files locally, we call the hide method on the adapter
+                // ...which a live object...cannot be parceled.
+                // For that reason, for now, we implement this dialog NOT
+                // as a fragment (which can survive rotation and have its own state),
+                // but as a simple runtime dialog. So rotating a device with an
+                // active delete dialog...results in that dialog disappearing.
+                // We can do better, but don't have cycles for it now.
                 new AlertDialog.Builder(getActivity())
-                    .setTitle(R.string.delete_confirmation_title)
-                    .setMessage(
-                            Shared.getQuantityString(
-                                    getActivity(),
-                                    R.plurals.delete_confirmation_message,
-                                    docs.size()))
+                    .setView(message)
                     .setPositiveButton(
                          android.R.string.yes,
                          new DialogInterface.OnClickListener() {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
index 0930c22..0bbecf9 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DocumentsAdapter.java
@@ -74,13 +74,6 @@
     abstract public SparseArray<String> hide(String... ids);
 
     /**
-     * Unhides a set of previously hidden items.
-     *
-     * @param ids A sparse array of IDs from a previous call to {@link #hide}.
-     */
-    abstract void unhide(SparseArray<String> ids);
-
-    /**
      * Returns a class that yields the span size for a particular element. This is
      * primarily useful in {@link SectionBreakDocumentsAdapterWrapper} where
      * we adjust sizes.
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
index 42dba45..2b07339 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
@@ -24,12 +24,12 @@
 
 import android.database.Cursor;
 import android.provider.DocumentsContract.Document;
-import android.support.annotation.VisibleForTesting;
 import android.util.Log;
 import android.util.SparseArray;
 import android.view.ViewGroup;
 
 import com.android.documentsui.State;
+
 import com.google.common.collect.Sets;
 
 import java.util.ArrayList;
@@ -181,29 +181,6 @@
         return hiddenItems;
     }
 
-    @VisibleForTesting
-    @Override
-    public void unhide(SparseArray<String> ids) {
-        if (DEBUG) Log.d(TAG, "Unhiding ids: " + ids);
-
-        // An ArrayList can shrink at runtime...and in fact
-        // it does when we clear it completely.
-        // This means we can't call add(pos, id) without
-        // first checking the list size.
-        List<String> oldIds = mModelIds;
-        mModelIds = new ArrayList<>(oldIds.size() + ids.size());
-        mModelIds.addAll(oldIds);
-
-        // Finally insert the unhidden items.
-        for (int i = 0; i < ids.size(); i++) {
-            int pos = ids.keyAt(i);
-            String id = ids.get(pos);
-            mHiddenIds.remove(id);
-            mModelIds.add(pos, id);
-            notifyItemInserted(pos);
-        }
-    }
-
     @Override
     public List<String> getModelIds() {
         return mModelIds;
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
index 3ee5cfc..b698059 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/SectionBreakDocumentsAdapterWrapper.java
@@ -169,13 +169,6 @@
     }
 
     @Override
-    void unhide(SparseArray<String> ids) {
-        // NOTE: We hear about these changes and adjust break position
-        // in our AdapterDataObserver.
-        mDelegate.unhide(ids);
-    }
-
-    @Override
     List<String> getModelIds() {
         return mDelegate.getModelIds();
     }
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
index 2244be9..adc8141 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapterTest.java
@@ -73,28 +73,6 @@
         assertEquals(mModel.getItemCount() - 2, mAdapter.getItemCount());
     }
 
-    // Tests that the items can be hidden and unhidden.
-    public void testUnhide_ItemCount() {
-        List<String> ids = mModel.getModelIds();
-        SparseArray<String> hidden = mAdapter.hide(ids.toArray(new String[ids.size()]));
-        mAdapter.unhide(hidden);
-        assertEquals(mModel.getItemCount(), mAdapter.getItemCount());
-    }
-
-    // Tests that the items can be hidden and unhidden.
-    public void testUnhide_PreservesOrder() {
-        List<String> ids = mModel.getModelIds();
-        SparseArray<String> hidden = mAdapter.hide(
-                ids.get(0), ids.get(1), ids.get(5), ids.get(9));
-        mAdapter.unhide(hidden);
-
-        // Finally ensure the restored items are in the original order
-        // by checking them against the model.
-        for (int i = 0; i < mAdapter.getItemCount(); i++) {
-            assertEquals(mModel.idForPosition(i), mAdapter.getModelId(i));
-        }
-    }
-
     private final class TestEnvironment implements DocumentsAdapter.Environment {
         private final Context testContext;
 
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestDocumentsAdapter.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestDocumentsAdapter.java
index 267f47d..e170dbb 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestDocumentsAdapter.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestDocumentsAdapter.java
@@ -61,11 +61,6 @@
     }
 
     @Override
-    void unhide(SparseArray<String> ids) {
-        throw new UnsupportedOperationException();
-    }
-
-    @Override
     public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         throw new UnsupportedOperationException();
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java b/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
index 1859207..741b0ea 100644
--- a/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
+++ b/packages/SettingsLib/src/com/android/settingslib/SuggestionParser.java
@@ -25,6 +25,12 @@
 import android.util.Log;
 import android.util.Pair;
 import android.util.Xml;
+import android.provider.Settings;
+import android.accounts.Account;
+import android.accounts.AccountManager;
+import android.content.ContentValues;
+import android.content.pm.PackageManager;
+import android.content.res.Resources;
 import android.view.InflateException;
 import com.android.settingslib.drawer.Tile;
 import com.android.settingslib.drawer.TileUtils;
@@ -42,6 +48,12 @@
     // If defined, only returns this suggestion if the feature is supported.
     public static final String META_DATA_REQUIRE_FEATURE = "com.android.settings.require_feature";
 
+    // If defined, only display this optional step if an account of that type exists.
+    private static final String META_DATA_REQUIRE_ACCOUNT = "com.android.settings.require_account";
+
+    // If defined and not true, do not should optional step.
+    private static final String META_DATA_IS_SUPPORTED = "com.android.settings.is_supported";
+
     /**
      * Allows suggestions to appear after a certain number of days, and to re-appear if dismissed.
      * For instance:
@@ -110,7 +122,10 @@
         TileUtils.getTilesForIntent(mContext, new UserHandle(UserHandle.myUserId()), intent,
                 addCache, null, suggestions, true, false);
         for (int i = countBefore; i < suggestions.size(); i++) {
-            if (!isAvailable(suggestions.get(i)) || isDismissed(suggestions.get(i))) {
+            if (!isAvailable(suggestions.get(i)) ||
+                    !isSupported(suggestions.get(i)) ||
+                    !satisfiesRequiredAccount(suggestions.get(i)) ||
+                    isDismissed(suggestions.get(i))) {
                 suggestions.remove(i--);
             }
         }
@@ -124,7 +139,10 @@
                     item = last;
                 }
             }
-            suggestions.add(item);
+            // If category is marked as done, do not add any item.
+            if (!isCategoryDone(category.category)) {
+                suggestions.add(item);
+            }
         }
     }
 
@@ -136,6 +154,41 @@
         return true;
     }
 
+    public boolean satisfiesRequiredAccount(Tile suggestion) {
+        String requiredAccountType = suggestion.metaData.getString(META_DATA_REQUIRE_ACCOUNT);
+        if (requiredAccountType == null) {
+            return true;
+        }
+        AccountManager accountManager = AccountManager.get(mContext);
+        Account[] accounts = accountManager.getAccountsByType(requiredAccountType);
+        return accounts.length > 0;
+    }
+
+    public boolean isSupported(Tile suggestion) {
+        int isSupportedResource = suggestion.metaData.getInt(META_DATA_IS_SUPPORTED);
+        try {
+            if (suggestion.intent == null) {
+                return false;
+            }
+            final Resources res = mContext.getPackageManager().getResourcesForActivity(
+                    suggestion.intent.getComponent());
+            return isSupportedResource != 0 ? res.getBoolean(isSupportedResource) : true;
+        } catch (PackageManager.NameNotFoundException e) {
+            Log.w(TAG, "Cannot find resources for " + suggestion.intent.getComponent());
+            return false;
+        }
+    }
+
+    public boolean isCategoryDone(String category) {
+        String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
+        return Settings.Secure.getInt(mContext.getContentResolver(), name, 0) != 0;
+    }
+
+    public void markCategoryDone(String category) {
+        String name = Settings.Secure.COMPLETED_CATEGORY_PREFIX + category;
+        Settings.Secure.putInt(mContext.getContentResolver(), name, 1);
+    }
+
     private boolean isDismissed(Tile suggestion) {
         Object dismissObj = suggestion.metaData.get(META_DATA_DISMISS_CONTROL);
         if (dismissObj == null) {
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
index 9842e28..a424d55 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProvider.java
@@ -1128,10 +1128,15 @@
 
     private PackageInfo getCallingPackageInfoOrThrow(int userId) {
         try {
-            return mPackageManager.getPackageInfo(getCallingPackage(), 0, userId);
+            PackageInfo packageInfo = mPackageManager.getPackageInfo(
+                    getCallingPackage(), 0, userId);
+            if (packageInfo != null) {
+                return packageInfo;
+            }
         } catch (RemoteException e) {
-            throw new IllegalStateException("Calling package doesn't exist");
+            /* ignore */
         }
+        throw new IllegalStateException("Calling package doesn't exist");
     }
 
     private int getGroupParentLocked(int userId) {
diff --git a/packages/SystemUI/res/drawable/qs_btn_borderless_rect.xml b/packages/SystemUI/res/drawable/qs_btn_borderless_rect.xml
new file mode 100644
index 0000000..03bfd1a
--- /dev/null
+++ b/packages/SystemUI/res/drawable/qs_btn_borderless_rect.xml
@@ -0,0 +1,32 @@
+<!--
+     Copyright (C) 2016 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.
+-->
+<inset
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:insetTop="4dp"
+    android:insetBottom="4dp">
+    <ripple
+        android:color="?android:attr/colorControlHighlight" >
+
+        <item android:id="@android:id/mask">
+            <shape>
+                <corners android:radius="@dimen/borderless_button_radius" />
+
+                <solid android:color="@android:color/white" />
+            </shape>
+        </item>
+
+    </ripple>
+</inset>
diff --git a/packages/SystemUI/res/values/styles.xml b/packages/SystemUI/res/values/styles.xml
index a51b931..11df681 100644
--- a/packages/SystemUI/res/values/styles.xml
+++ b/packages/SystemUI/res/values/styles.xml
@@ -214,7 +214,7 @@
 
     <style name="QSBorderlessButton">
         <item name="android:padding">12dp</item>
-        <item name="android:background">@drawable/btn_borderless_rect</item>
+        <item name="android:background">@drawable/qs_btn_borderless_rect</item>
         <item name="android:gravity">center</item>
     </style>
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
index 2c5cb89..8ccf60d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/PagedTileLayout.java
@@ -77,7 +77,9 @@
 
     @Override
     public int getOffsetTop(TileRecord tile) {
-        return ((ViewGroup) tile.tileView.getParent()).getTop() + getTop();
+        final ViewGroup parent = (ViewGroup) tile.tileView.getParent();
+        if (parent == null) return 0;
+        return parent.getTop() + getTop();
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
index 225c10f..5e02428 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/QSCustomizer.java
@@ -87,6 +87,7 @@
         mToolbar.setOnMenuItemClickListener(this);
         mToolbar.getMenu().add(Menu.NONE, MENU_RESET, 0,
                 mContext.getString(com.android.internal.R.string.reset));
+        mToolbar.setTitle(R.string.qs_edit);
 
         mRecyclerView = (RecyclerView) findViewById(android.R.id.list);
         mTileAdapter = new TileAdapter(getContext());
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
index aba9251..d9b3b3f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileAdapter.java
@@ -25,7 +25,6 @@
 import android.support.v7.widget.RecyclerView.ViewHolder;
 import android.support.v7.widget.helper.ItemTouchHelper;
 import android.view.LayoutInflater;
-import android.view.MotionEvent;
 import android.view.View;
 import android.view.ViewGroup;
 import android.widget.FrameLayout;
@@ -277,9 +276,6 @@
                     return false;
                 }
             }
-            if (target.getItemViewType() == TYPE_EDIT && from < mDividerIndex) {
-                to++;
-            }
             move(from, to, mTiles);
             mDividerIndex = mTiles.indexOf(null);
             notifyItemMoved(from, to);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
index 548bffd..2e87525 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BatteryTile.java
@@ -182,6 +182,9 @@
         }
 
         private void bindView() {
+            if (mCurrentView == null) {
+                return;
+            }
             mDrawable.onBatteryLevelChanged(100, false, false);
             mDrawable.onPowerSaveChanged(true);
             mDrawable.disableShowPercent();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
index 178838e..f63e97a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/BaseStatusBar.java
@@ -194,6 +194,7 @@
     // public mode, private notifications, etc
     private boolean mLockscreenPublicMode = false;
     private final SparseBooleanArray mUsersAllowingPrivateNotifications = new SparseBooleanArray();
+    private final SparseBooleanArray mUsersAllowingNotifications = new SparseBooleanArray();
 
     private UserManager mUserManager;
     private int mDensity;
@@ -1302,6 +1303,26 @@
     }
 
     /**
+     * Has the given user chosen to allow notifications to be shown even when the lockscreen is in
+     * "public" (secure & locked) mode?
+     */
+    public boolean userAllowsNotificationsInPublic(int userHandle) {
+        if (userHandle == UserHandle.USER_ALL) {
+            return true;
+        }
+
+        if (mUsersAllowingNotifications.indexOfKey(userHandle) < 0) {
+            final boolean allowed = 0 != Settings.Secure.getIntForUser(
+                    mContext.getContentResolver(),
+                    Settings.Secure.LOCK_SCREEN_SHOW_NOTIFICATIONS, 0, userHandle);
+            mUsersAllowingNotifications.append(userHandle, allowed);
+            return allowed;
+        }
+
+        return mUsersAllowingNotifications.get(userHandle);
+    }
+
+    /**
      * Has the given user chosen to allow their private (full) notifications to be shown even
      * when the lockscreen is in "public" (secure & locked) mode?
      */
@@ -1333,13 +1354,30 @@
     }
 
     /**
-     * Returns true if we're on a secure lockscreen and the user wants to hide "sensitive"
-     * notification data. If so, private notifications should show their (possibly
-     * auto-generated) publicVersion, and secret notifications should be totally invisible.
+     * Returns true if we're on a secure lockscreen and the user wants to hide notification data.
+     * If so, notifications should be hidden.
      */
     @Override  // NotificationData.Environment
-    public boolean shouldHideSensitiveContents(int userid) {
-        return isLockscreenPublicMode() && !userAllowsPrivateNotificationsInPublic(userid);
+    public boolean shouldHideNotifications(int userid) {
+        return isLockscreenPublicMode() && !userAllowsNotificationsInPublic(userid);
+    }
+
+    /**
+     * Returns true if we're on a secure lockscreen and the user wants to hide notifications via
+     * package-specific override.
+     */
+    @Override // NotificationDate.Environment
+    public boolean shouldHideNotifications(String key) {
+        return isLockscreenPublicMode()
+                && mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_SECRET;
+    }
+
+    /**
+     * Returns true if we're on a secure lockscreen.
+     */
+    @Override  // NotificationData.Environment
+    public boolean onSecureLockScreen() {
+        return isLockscreenPublicMode();
     }
 
     public void onNotificationClear(StatusBarNotification notification) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
index 7cb9127..c9fe2bd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationData.java
@@ -353,8 +353,10 @@
             return true;
         }
 
-        if (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET &&
-                mEnvironment.shouldHideSensitiveContents(sbn.getUserId())) {
+        if (mEnvironment.onSecureLockScreen() &&
+                (sbn.getNotification().visibility == Notification.VISIBILITY_SECRET
+                        || mEnvironment.shouldHideNotifications(sbn.getUserId())
+                        || mEnvironment.shouldHideNotifications(sbn.getKey()))) {
             return true;
         }
 
@@ -433,7 +435,9 @@
      * Provides access to keyguard state and user settings dependent data.
      */
     public interface Environment {
-        public boolean shouldHideSensitiveContents(int userid);
+        public boolean onSecureLockScreen();
+        public boolean shouldHideNotifications(int userid);
+        public boolean shouldHideNotifications(String key);
         public boolean isDeviceProvisioned();
         public boolean isNotificationForCurrentProfiles(StatusBarNotification sbn);
         public String getCurrentMediaNotificationKey();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
index e739944..c152171 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBar.java
@@ -1597,8 +1597,7 @@
     }
 
     private boolean packageHasVisibilityOverride(String key) {
-        return mNotificationData.getVisibilityOverride(key)
-                != NotificationListenerService.Ranking.VISIBILITY_NO_OVERRIDE;
+        return mNotificationData.getVisibilityOverride(key) == Notification.VISIBILITY_PRIVATE;
     }
 
     private void updateClearAll() {
diff --git a/services/core/java/com/android/server/LockSettingsService.java b/services/core/java/com/android/server/LockSettingsService.java
index ba2a2e0..6fb0671 100644
--- a/services/core/java/com/android/server/LockSettingsService.java
+++ b/services/core/java/com/android/server/LockSettingsService.java
@@ -345,9 +345,9 @@
                     final int userId = users.get(user).id;
                     final String OWNER_INFO = Secure.LOCK_SCREEN_OWNER_INFO;
                     String ownerInfo = Settings.Secure.getStringForUser(cr, OWNER_INFO, userId);
-                    if (ownerInfo != null) {
+                    if (!TextUtils.isEmpty(ownerInfo)) {
                         setString(OWNER_INFO, ownerInfo, userId);
-                        Settings.Secure.putStringForUser(cr, ownerInfo, "", userId);
+                        Settings.Secure.putStringForUser(cr, OWNER_INFO, "", userId);
                     }
 
                     // Migrate owner info enabled.  Note there was a bug where older platforms only
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index c229a69..565ec82 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -1294,6 +1294,7 @@
     boolean mAlwaysFinishActivities = false;
     boolean mLenientBackgroundCheck = false;
     boolean mForceResizableActivities;
+    boolean mSupportsMultiWindow;
     boolean mSupportsFreeformWindowManagement;
     boolean mSupportsPictureInPicture;
     Rect mDefaultPinnedStackBounds;
@@ -5359,17 +5360,6 @@
 
     @Override
     public void killAllBackgroundProcesses() {
-        killAllBackgroundProcesses(-1);
-    }
-
-    /**
-     * Kills all background processes with targetSdkVersion below the specified
-     * target SDK version.
-     *
-     * @param targetSdkVersion the target SDK version below which to kill
-     *                         processes, or {@code -1} to kill all processes
-     */
-    private void killAllBackgroundProcesses(int targetSdkVersion) {
         if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES)
                 != PackageManager.PERMISSION_GRANTED) {
             final String msg = "Permission Denial: killAllBackgroundProcesses() from pid="
@@ -5393,10 +5383,6 @@
                             // We don't kill persistent processes.
                             continue;
                         }
-                        if (targetSdkVersion > 0
-                                && app.info.targetSdkVersion >= targetSdkVersion) {
-                            continue;
-                        }
                         if (app.removed) {
                             procs.add(app);
                         } else if (app.setAdj >= ProcessList.CACHED_APP_MIN_ADJ) {
@@ -5421,6 +5407,55 @@
         }
     }
 
+    /**
+     * Kills all background processes, except those matching any of the
+     * specified properties.
+     *
+     * @param minTargetSdk the target SDK version at or above which to preserve
+     *                     processes, or {@code -1} to ignore the target SDK
+     * @param maxProcState the process state at or below which to preserve
+     *                     processes, or {@code -1} to ignore the process state
+     */
+    private void killAllBackgroundProcessesExcept(int minTargetSdk, int maxProcState) {
+        if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES)
+                != PackageManager.PERMISSION_GRANTED) {
+            final String msg = "Permission Denial: killAllBackgroundProcessesExcept() from pid="
+                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                    + " requires " + android.Manifest.permission.KILL_BACKGROUND_PROCESSES;
+            Slog.w(TAG, msg);
+            throw new SecurityException(msg);
+        }
+
+        final long callingId = Binder.clearCallingIdentity();
+        try {
+            synchronized (this) {
+                final ArrayList<ProcessRecord> procs = new ArrayList<>();
+                final int NP = mProcessNames.getMap().size();
+                for (int ip = 0; ip < NP; ip++) {
+                    final SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip);
+                    final int NA = apps.size();
+                    for (int ia = 0; ia < NA; ia++) {
+                        final ProcessRecord app = apps.valueAt(ia);
+                        if (app.removed) {
+                            procs.add(app);
+                        } else if ((minTargetSdk < 0 || app.info.targetSdkVersion < minTargetSdk)
+                                && (maxProcState < 0 || app.setProcState > maxProcState)) {
+                            app.removed = true;
+                            procs.add(app);
+                        }
+                    }
+                }
+
+                final int N = procs.size();
+                for (int i = 0; i < N; i++) {
+                    removeProcessLocked(procs.get(i), false, true, "kill all background except");
+                }
+            }
+        } finally {
+            Binder.restoreCallingIdentity(callingId);
+        }
+    }
+
     @Override
     public void forceStopPackage(final String packageName, int userId) {
         if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES)
@@ -12337,6 +12372,7 @@
         final boolean supportsPictureInPicture =
                 mContext.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE);
 
+        final boolean supportsMultiWindow = ActivityManager.supportsMultiWindow();
         final String debugApp = Settings.Global.getString(resolver, DEBUG_APP);
         final boolean waitForDebugger = Settings.Global.getInt(resolver, WAIT_FOR_DEBUGGER, 0) != 0;
         final boolean alwaysFinishActivities =
@@ -12363,8 +12399,15 @@
             mLenientBackgroundCheck = lenientBackgroundCheck;
             mForceResizableActivities = forceResizable;
             mWindowManager.setForceResizableTasks(mForceResizableActivities);
-            mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
-            mSupportsPictureInPicture = supportsPictureInPicture || forceResizable;
+            if (supportsMultiWindow || forceResizable) {
+                mSupportsMultiWindow = true;
+                mSupportsFreeformWindowManagement = freeformWindowManagement || forceResizable;
+                mSupportsPictureInPicture = supportsPictureInPicture || forceResizable;
+            } else {
+                mSupportsMultiWindow = false;
+                mSupportsFreeformWindowManagement = false;
+                mSupportsPictureInPicture = false;
+            }
             // This happens before any activities are started, so we can
             // change mConfiguration in-place.
             updateConfigurationLocked(configuration, null, true);
@@ -18056,7 +18099,8 @@
 
                 final boolean isDensityChange = (changes & ActivityInfo.CONFIG_DENSITY) != 0;
                 if (isDensityChange) {
-                    killAllBackgroundProcesses(Build.VERSION_CODES.N);
+                    killAllBackgroundProcessesExcept(Build.VERSION_CODES.N,
+                            ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE);
                 }
 
                 for (int i=mLruProcesses.size()-1; i>=0; i--) {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index c6813604..c58cad0 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2174,6 +2174,12 @@
      */
     ActivityStack moveTaskToStackUncheckedLocked(
             TaskRecord task, int stackId, boolean toTop, boolean forceFocus, String reason) {
+
+        if (StackId.isMultiWindowStack(stackId) && !mService.mSupportsMultiWindow) {
+            throw new IllegalStateException("moveTaskToStackUncheckedLocked: Device doesn't "
+                    + "support multi-window task=" + task + " to stackId=" + stackId);
+        }
+
         final ActivityRecord r = task.getTopActivity();
         final ActivityStack prevStack = task.stack;
         final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() == r);
@@ -2184,8 +2190,6 @@
         final boolean wasFront = isFrontStack(prevStack)
                 && (prevStack.topRunningActivityLocked() == r);
 
-        final int resizeMode = task.mResizeMode;
-
         if (stackId == DOCKED_STACK_ID && !task.isResizeable()) {
             // We don't allow moving a unresizeable task to the docked stack since the docked
             // stack is used for split-screen mode and will cause things like the docked divider to
diff --git a/services/core/java/com/android/server/am/ActivityStarter.java b/services/core/java/com/android/server/am/ActivityStarter.java
index bcdc800..46389e2 100644
--- a/services/core/java/com/android/server/am/ActivityStarter.java
+++ b/services/core/java/com/android/server/am/ActivityStarter.java
@@ -1772,12 +1772,13 @@
             return false;
         }
 
-        if (stackId == DOCKED_STACK_ID && r.canGoInDockedStack()) {
-            return true;
+        if (stackId != FULLSCREEN_WORKSPACE_STACK_ID
+                && (!mService.mSupportsMultiWindow || !r.isResizeableOrForced())) {
+            return false;
         }
 
-        if (stackId != FULLSCREEN_WORKSPACE_STACK_ID && !r.isResizeableOrForced()) {
-            return false;
+        if (stackId == DOCKED_STACK_ID && r.canGoInDockedStack()) {
+            return true;
         }
 
         if (stackId == FREEFORM_WORKSPACE_STACK_ID && !mService.mSupportsFreeformWindowManagement) {
diff --git a/services/core/java/com/android/server/camera/CameraService.java b/services/core/java/com/android/server/camera/CameraService.java
index f82454a..cd8eb4e 100644
--- a/services/core/java/com/android/server/camera/CameraService.java
+++ b/services/core/java/com/android/server/camera/CameraService.java
@@ -60,10 +60,6 @@
 
     public static final String CAMERA_SERVICE_PROXY_BINDER_NAME = "media.camera.proxy";
 
-    // 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
-
     // State arguments to use with the notifyCameraState call from camera service:
     public static final int CAMERA_STATE_OPEN = 0;
     public static final int CAMERA_STATE_ACTIVE = 1;
@@ -224,7 +220,7 @@
         if (mEnabledCameraUsers == null || !mEnabledCameraUsers.equals(currentUserHandles)) {
             // Some user handles have been added or removed, update mediaserver.
             mEnabledCameraUsers = currentUserHandles;
-            notifyMediaserverLocked(USER_SWITCHED, currentUserHandles);
+            notifyMediaserverLocked(ICameraService.EVENT_USER_SWITCHED, currentUserHandles);
         }
     }
 
@@ -244,7 +240,7 @@
             if (mEnabledCameraUsers == null) {
                 return;
             }
-            if (notifyMediaserverLocked(USER_SWITCHED, mEnabledCameraUsers)) {
+            if (notifyMediaserverLocked(ICameraService.EVENT_USER_SWITCHED, mEnabledCameraUsers)) {
                 retries = 0;
             }
         }
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index f901f95..6218c4e 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -60,6 +60,7 @@
 import com.android.internal.app.IAppOpsService;
 import com.android.internal.app.IBatteryStats;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.util.ArrayUtils;
 import com.android.server.EventLogTags;
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
@@ -254,6 +255,9 @@
     // True if boot completed occurred.  We keep the screen on until this happens.
     private boolean mBootCompleted;
 
+    // Runnables that should be triggered on boot completed
+    private Runnable[] mBootCompletedRunnables;
+
     // True if auto-suspend mode is enabled.
     // Refer to autosuspend.h.
     private boolean mHalAutoSuspendModeEnabled;
@@ -525,6 +529,14 @@
                 userActivityNoUpdateLocked(
                         now, PowerManager.USER_ACTIVITY_EVENT_OTHER, 0, Process.SYSTEM_UID);
                 updatePowerStateLocked();
+
+                if (!ArrayUtils.isEmpty(mBootCompletedRunnables)) {
+                    Slog.d(TAG, "Posting " + mBootCompletedRunnables.length + " delayed runnables");
+                    for (Runnable r : mBootCompletedRunnables) {
+                        BackgroundThread.getHandler().post(r);
+                    }
+                }
+                mBootCompletedRunnables = null;
             }
         }
     }
@@ -750,6 +762,16 @@
         mDirty |= DIRTY_SETTINGS;
     }
 
+    private void postAfterBootCompleted(Runnable r) {
+        if (mBootCompleted) {
+            BackgroundThread.getHandler().post(r);
+        } else {
+            Slog.d(TAG, "Delaying runnable until system is booted");
+            mBootCompletedRunnables = ArrayUtils.appendElement(Runnable.class,
+                    mBootCompletedRunnables, r);
+        }
+    }
+
     void updateLowPowerModeLocked() {
         if (mIsPowered && mLowPowerModeSetting) {
             if (DEBUG_SPEW) {
@@ -767,7 +789,7 @@
         if (mLowPowerModeEnabled != lowPowerModeEnabled) {
             mLowPowerModeEnabled = lowPowerModeEnabled;
             powerHintInternal(POWER_HINT_LOW_POWER, lowPowerModeEnabled ? 1 : 0);
-            BackgroundThread.getHandler().post(new Runnable() {
+            postAfterBootCompleted(new Runnable() {
                 @Override
                 public void run() {
                     Intent intent = new Intent(PowerManager.ACTION_POWER_SAVE_MODE_CHANGING)
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 40b6b50..e8f1b5d 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1902,6 +1902,12 @@
             mWinAnimator.hide("saved surface");
             mWinAnimator.mDrawState = WindowStateAnimator.NO_SURFACE;
             setHasSurface(false);
+            // The client should have disconnected at this point, but if it doesn't,
+            // we need to make sure it's disconnected. Otherwise when we reuse the surface
+            // the client can't reconnect to the buffer queue, and rendering will fail.
+            if (mWinAnimator.mSurfaceController != null) {
+                mWinAnimator.mSurfaceController.disconnectInTransaction();
+            }
         } else {
             mWinAnimator.destroySurfaceLocked();
         }
diff --git a/services/core/java/com/android/server/wm/WindowSurfaceController.java b/services/core/java/com/android/server/wm/WindowSurfaceController.java
index 2972a24..2cdf471 100644
--- a/services/core/java/com/android/server/wm/WindowSurfaceController.java
+++ b/services/core/java/com/android/server/wm/WindowSurfaceController.java
@@ -152,6 +152,20 @@
         }
     }
 
+    void disconnectInTransaction() {
+        if (SHOW_TRANSACTIONS || SHOW_SURFACE_ALLOC) {
+            Slog.i(TAG, "Disconnecting client: " + this);
+        }
+
+        try {
+            if (mSurfaceControl != null) {
+                mSurfaceControl.disconnect();
+            }
+        } catch (RuntimeException e) {
+            Slog.w(TAG, "Error disconnecting surface in: " + this, e);
+        }
+    }
+
     void setCropInTransaction(Rect clipRect, boolean recoveringMemory) {
         if (SHOW_TRANSACTIONS) logSurface(
                 "CROP " + clipRect.toShortString(), null);
diff --git a/tests/HwAccelerationTest/res/drawable/default_wallpaper.png b/tests/HwAccelerationTest/res/drawable/default_wallpaper.png
index 91ad252..187a6c0 100644
--- a/tests/HwAccelerationTest/res/drawable/default_wallpaper.png
+++ b/tests/HwAccelerationTest/res/drawable/default_wallpaper.png
Binary files differ
diff --git a/tests/HwAccelerationTest/res/layout/projection_clipping.xml b/tests/HwAccelerationTest/res/layout/projection_clipping.xml
index 1f2b939..1ea9f9c 100644
--- a/tests/HwAccelerationTest/res/layout/projection_clipping.xml
+++ b/tests/HwAccelerationTest/res/layout/projection_clipping.xml
@@ -3,24 +3,32 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="match_parent">
-    <FrameLayout
+    <ScrollView
+        android:orientation="vertical"
         android:translationX="50dp"
         android:translationY="50dp"
         android:elevation="30dp"
         android:layout_width="200dp"
         android:layout_height="200dp"
         android:background="@drawable/round_rect_background">
-        <View
-            android:id="@+id/clickable1"
-            android:layout_width="100dp"
-            android:layout_height="100dp"
-            android:background="?android:attr/selectableItemBackgroundBorderless"/>
-        <View
-            android:id="@+id/clickable2"
-            android:translationX="50dp"
-            android:translationY="10dp"
-            android:layout_width="150dp"
-            android:layout_height="100dp"
-            android:background="?android:attr/selectableItemBackgroundBorderless"/>
-    </FrameLayout>
+        <FrameLayout
+            android:layout_width="200dp"
+            android:layout_height="wrap_content">
+            <View
+                android:layout_width="200dp"
+                android:layout_height="2000dp"/>
+            <View
+                android:id="@+id/clickable1"
+                android:layout_width="100dp"
+                android:layout_height="100dp"
+                android:background="?android:attr/selectableItemBackgroundBorderless"/>
+            <View
+                android:id="@+id/clickable2"
+                android:translationX="50dp"
+                android:translationY="10dp"
+                android:layout_width="150dp"
+                android:layout_height="100dp"
+                android:background="?android:attr/selectableItemBackgroundBorderless"/>
+       </FrameLayout>
+    </ScrollView>
 </LinearLayout>
diff --git a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
index 2a3f143..0da1bb1 100644
--- a/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
+++ b/tests/StatusBar/src/com/android/statusbartest/NotificationTestList.java
@@ -93,11 +93,7 @@
                             .setSmallIcon(R.drawable.icon2)
                             .setContentTitle("Min priority")
                             .setLights(0xff0000ff, 1, 0)
-                            .setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
-                            .setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
-                                    getPackageName() + "/raw/ringer"))
                             .setPriority(Notification.PRIORITY_MIN)
-                            .setFullScreenIntent(makeIntent2(), false)
                             .build();
                     mNM.notify(7000, n);
                 }
@@ -125,11 +121,7 @@
                             .setSmallIcon(R.drawable.icon2)
                             .setContentTitle("Low priority")
                             .setLights(0xff0000ff, 1, 0)
-                            .setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
-                            .setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
-                                    getPackageName() + "/raw/ringer"))
                             .setPriority(Notification.PRIORITY_LOW)
-                            .setFullScreenIntent(makeIntent2(), false)
                             .build();
                     mNM.notify(7002, n);
                 }
@@ -141,11 +133,7 @@
                             .setSmallIcon(R.drawable.icon2)
                             .setContentTitle("Default priority")
                             .setLights(0xff0000ff, 1, 0)
-                            .setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
-                            .setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
-                                    getPackageName() + "/raw/ringer"))
                             .setPriority(Notification.PRIORITY_DEFAULT)
-                            .setFullScreenIntent(makeIntent2(), false)
                             .build();
                     mNM.notify(7004, n);
                 }
@@ -161,7 +149,6 @@
                             .setSound(Uri.parse(ContentResolver.SCHEME_ANDROID_RESOURCE + "://" +
                                     getPackageName() + "/raw/ringer"))
                             .setPriority(Notification.PRIORITY_HIGH)
-                            .setFullScreenIntent(makeIntent2(), false)
                             .build();
                     mNM.notify(7006, n);
                 }
@@ -179,7 +166,7 @@
                             .setPriority(Notification.PRIORITY_MAX)
                             .setFullScreenIntent(makeIntent2(), false)
                             .build();
-                    mNM.notify(7008, n);
+                    mNM.notify(7007, n);
                 }
             },
             new Test("Max priority with delay") {
@@ -202,6 +189,64 @@
                     mNM.notify(7008, n);
                 }
             },
+            new Test("public notification") {
+                public void run()
+                {
+                    Notification n = new Notification.Builder(NotificationTestList.this)
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle("public notification")
+                            .setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
+                            .setPriority(Notification.PRIORITY_DEFAULT)
+                            .setVisibility(Notification.VISIBILITY_PUBLIC)
+                            .build();
+                    mNM.notify(7009, n);
+                }
+            },
+            new Test("private notification, no public") {
+                public void run()
+                {
+                    Notification n = new Notification.Builder(NotificationTestList.this)
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle("private only notification")
+                            .setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
+                            .setPriority(Notification.PRIORITY_DEFAULT)
+                            .setVisibility(Notification.VISIBILITY_PRIVATE)
+                            .build();
+                    mNM.notify(7010, n);
+                }
+            },
+            new Test("private notification, has public") {
+                public void run()
+                {
+                    Notification n = new Notification.Builder(NotificationTestList.this)
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle("private version of notification")
+                            .setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
+                            .setPriority(Notification.PRIORITY_DEFAULT)
+                            .setVisibility(Notification.VISIBILITY_PRIVATE)
+                            .setPublicVersion(new Notification.Builder(NotificationTestList.this)
+                                    .setSmallIcon(R.drawable.icon2)
+                                    .setContentTitle("public notification of private notification")
+                                    .setPriority(Notification.PRIORITY_DEFAULT)
+                                    .setVisibility(Notification.VISIBILITY_PUBLIC)
+                                    .build())
+                            .build();
+                    mNM.notify(7011, n);
+                }
+            },
+            new Test("secret notification") {
+                public void run()
+                {
+                    Notification n = new Notification.Builder(NotificationTestList.this)
+                            .setSmallIcon(R.drawable.icon2)
+                            .setContentTitle("secret notification")
+                            .setDefaults(Notification.DEFAULT_LIGHTS|Notification.DEFAULT_VIBRATE)
+                            .setPriority(Notification.PRIORITY_DEFAULT)
+                            .setVisibility(Notification.VISIBILITY_SECRET)
+                            .build();
+                    mNM.notify(7012, n);
+                }
+            },
         new Test("Off") {
             public void run() {
                 PowerManager pm = (PowerManager)NotificationTestList.this.getSystemService(Context.POWER_SERVICE);