Merge "SnapdragonCamera:Fix Camera FC in split screen" into camera.lnx.1.0-dev.1.0
diff --git a/AndroidManifest.xml b/AndroidManifest.xml
old mode 100755
new mode 100644
index 6ec0df3..c2f80cd
--- a/AndroidManifest.xml
+++ b/AndroidManifest.xml
@@ -63,6 +63,7 @@
         <activity
                 android:name="com.android.camera.PermissionsActivity"
                 android:label="@string/app_name"
+                android:configChanges="orientation|screenSize|keyboardHidden"
                 android:parentActivityName="com.android.camera.CameraActivity" >
             <meta-data
                     android:name="android.support.PARENT_ACTIVITY"
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 608d7f8..e7c55df 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -210,6 +210,7 @@
     private int mResultCodeForTesting;
     private Intent mResultDataForTesting;
     private OnScreenHint mStorageHint;
+    private final Object mStorageSpaceLock = new Object();
     private long mStorageSpaceBytes = Storage.LOW_STORAGE_THRESHOLD_BYTES;
     private boolean mSecureCamera;
     private int mLastRawOrientation;
@@ -1713,19 +1714,17 @@
         } else {
             mHasCriticalPermissions = false;
         }
-        if (!mHasCriticalPermissions) {
-            final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
-            boolean isRequestShown = prefs.getBoolean(CameraSettings.KEY_REQUEST_PERMISSION, false);
-            if(!isRequestShown || !mHasCriticalPermissions) {
-                Log.v(TAG, "Request permission");
-                Intent intent = new Intent(this, PermissionsActivity.class);
-                startActivity(intent);
-                SharedPreferences.Editor editor = prefs.edit();
-                editor.putBoolean(CameraSettings.KEY_REQUEST_PERMISSION, true);
-                editor.apply();
-                requestPermission = true;
-           }
-        }
+        final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
+        boolean isRequestShown = prefs.getBoolean(CameraSettings.KEY_REQUEST_PERMISSION, false);
+        if(!isRequestShown || !mHasCriticalPermissions) {
+            Log.v(TAG, "Request permission");
+            Intent intent = new Intent(this, PermissionsActivity.class);
+            startActivity(intent);
+            SharedPreferences.Editor editor = prefs.edit();
+            editor.putBoolean(CameraSettings.KEY_REQUEST_PERMISSION, true);
+            editor.apply();
+            requestPermission = true;
+       }
         return requestPermission;
     }
 
@@ -1864,16 +1863,21 @@
         mFilmStripView.setPreviewGestures(previewGestures);
     }
 
-    protected void updateStorageSpace() {
-        mStorageSpaceBytes = Storage.getAvailableSpace();
-        if (Storage.switchSavePath()) {
+    protected long updateStorageSpace() {
+        synchronized (mStorageSpaceLock) {
             mStorageSpaceBytes = Storage.getAvailableSpace();
-            mCurrentModule.onSwitchSavePath();
+            if (Storage.switchSavePath()) {
+                mStorageSpaceBytes = Storage.getAvailableSpace();
+                mCurrentModule.onSwitchSavePath();
+            }
+            return mStorageSpaceBytes;
         }
     }
 
     protected long getStorageSpaceBytes() {
-        return mStorageSpaceBytes;
+        synchronized (mStorageSpaceLock) {
+            return mStorageSpaceBytes;
+        }
     }
 
     protected void updateStorageSpaceAndHint() {
@@ -1881,6 +1885,31 @@
         updateStorageHint(mStorageSpaceBytes);
     }
 
+    protected interface OnStorageUpdateDoneListener {
+        void onStorageUpdateDone(long storageSpace);
+    }
+
+    protected void updateStorageSpaceAndHint(final OnStorageUpdateDoneListener callback) {
+        (new AsyncTask<Void, Void, Long>() {
+            @Override
+            protected Long doInBackground(Void ... arg) {
+                return updateStorageSpace();
+            }
+
+            @Override
+            protected void onPostExecute(Long storageSpace) {
+                updateStorageHint(storageSpace);
+                // This callback returns after I/O to check disk, so we could be
+                // pausing and shutting down. If so, don't bother invoking.
+                if (callback != null && !mPaused) {
+                    callback.onStorageUpdateDone(storageSpace);
+                } else {
+                    Log.v(TAG, "ignoring storage callback after activity pause");
+                }
+            }
+        }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
+    }
+
     protected void updateStorageHint(long storageSpace) {
         String message = null;
         if (storageSpace == Storage.UNAVAILABLE) {
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index bfdba9e..81d8584 100644
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -44,6 +44,7 @@
 import android.hardware.camera2.CaptureResult;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.Face;
+import android.hardware.camera2.params.InputConfiguration;
 import android.hardware.camera2.params.MeteringRectangle;
 import android.hardware.camera2.params.StreamConfigurationMap;
 import android.location.Location;
@@ -89,6 +90,7 @@
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.PersistUtil;
 import com.android.camera.util.SettingTranslation;
+import com.android.camera.util.ApiHelper;
 import com.android.internal.util.MemInfoReader;
 
 import org.codeaurora.snapcam.R;
@@ -105,6 +107,7 @@
 import java.util.List;
 import java.util.concurrent.Semaphore;
 import java.util.concurrent.TimeUnit;
+import java.lang.reflect.Method;
 
 public class CaptureModule implements CameraModule, PhotoController,
         MediaSaveService.Listener, ClearSightImageProcessor.Callback,
@@ -114,8 +117,8 @@
     public static final int BAYER_MODE = 1;
     public static final int MONO_MODE = 2;
     public static final int BAYER_ID = 0;
-    public static int MONO_ID = 1;
-    public static int FRONT_ID = 1;
+    public static int MONO_ID = -1;
+    public static int FRONT_ID = -1;
     private static final int BACK_MODE = 0;
     private static final int FRONT_MODE = 1;
     private static final int CANCEL_TOUCH_FOCUS_DELAY = 3000;
@@ -176,6 +179,9 @@
     public static CaptureRequest.Key<int[]> JpegCropRectKey =
             new CaptureRequest.Key<>("org.codeaurora.qcamera3.jpeg_encode_crop.rect",
                     int[].class);
+    public static CaptureRequest.Key<int[]> JpegRoiRectKey =
+            new CaptureRequest.Key<>("org.codeaurora.qcamera3.jpeg_encode_crop.roi",
+                    int[].class);
     public static CameraCharacteristics.Key<Byte> MetaDataMonoOnlyKey =
             new CameraCharacteristics.Key<>("org.codeaurora.qcamera3.sensor_meta_data.is_mono_only",
                     Byte.class);
@@ -464,6 +470,7 @@
             Face[] faces = result.get(CaptureResult.STATISTICS_FACES);
             updateFaceView(faces);
             processCaptureResult(result);
+            mPostProcessor.onMetaAvailable(result);
         }
     };
 
@@ -773,6 +780,16 @@
         }
     }
 
+    private CaptureRequest.Builder getRequestBuilder(int id) throws CameraAccessException {
+        CaptureRequest.Builder builder;
+        if(mPostProcessor.isZSLEnabled() && id == getMainCameraId()) {
+            builder = mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_ZERO_SHUTTER_LAG);
+        } else {
+            builder = mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_PREVIEW);
+        }
+        return builder;
+    }
+
     private void createSession(final int id) {
         if (mPaused || !mCameraOpened[id] || !mSurfaceReady) return;
         Log.d(TAG, "createSession " + id);
@@ -780,8 +797,7 @@
         try {
             Surface surface = getPreviewSurfaceForSession(id);
             // We set up a CaptureRequest.Builder with the output Surface.
-            mPreviewRequestBuilder[id] = mCameraDevice[id].createCaptureRequest(CameraDevice
-                    .TEMPLATE_PREVIEW);
+            mPreviewRequestBuilder[id] = getRequestBuilder(id);
             mPreviewRequestBuilder[id].setTag(id);
 
             CameraCaptureSession.StateCallback captureSessionCallback =
@@ -817,6 +833,8 @@
                                 }
                                 if (isClearSightOn()) {
                                     ClearSightImageProcessor.getInstance().onCaptureSessionConfigured(id == BAYER_ID, cameraCaptureSession);
+                                } else if(id == getMainCameraId() && mPostProcessor.isZSLEnabled()) {
+                                    mPostProcessor.onSessionConfiguredForZSL(mCaptureSession[id]);
                                 }
                             } catch (CameraAccessException e) {
                                 e.printStackTrace();
@@ -870,7 +888,14 @@
                     list.add(surs);
                 }
                 list.add(mImageReader[id].getSurface());
-                mCameraDevice[id].createCaptureSession(list, captureSessionCallback, null);
+                if(mPostProcessor.isZSLEnabled()) {
+                    mPreviewRequestBuilder[id].addTarget(mImageReader[id].getSurface());
+                    mCameraDevice[id].createReprocessableCaptureSession(new InputConfiguration(mImageReader[id].getWidth(),
+                                    mImageReader[id].getHeight(), mImageReader[id].getImageFormat()),
+                                    list, captureSessionCallback, null);
+                } else {
+                    mCameraDevice[id].createCaptureSession(list, captureSessionCallback, null);
+                }
             } else {
                 mPreviewRequestBuilder[id].addTarget(surface);
                 list.add(surface);
@@ -972,6 +997,9 @@
                     lockFocus(MONO_ID);
                     break;
                 case BAYER_MODE:
+                    if(takeZSLPicture(BAYER_ID)) {
+                        return;
+                    }
                     lockFocus(BAYER_ID);
                     break;
                 case MONO_MODE:
@@ -979,10 +1007,22 @@
                     break;
             }
         } else {
+            if(takeZSLPicture(FRONT_ID)) {
+                    return;
+            }
             lockFocus(FRONT_ID);
         }
     }
 
+    private boolean takeZSLPicture(int cameraId) {
+        if(mPostProcessor.isZSLEnabled() && mPostProcessor.takeZSLPicture(mCameraDevice[cameraId], mCaptureSession[cameraId], mImageReader[cameraId])) {
+            checkAndPlayShutterSound(cameraId);
+            mUI.enableShutter(true);
+            return true;
+        }
+        return false;
+    }
+
     /**
      * Lock the focus as the first step for a still image capture.
      */
@@ -1012,8 +1052,7 @@
         }
 
         try {
-            CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice
-                    .TEMPLATE_PREVIEW);
+            CaptureRequest.Builder builder = getRequestBuilder(id);
             builder.setTag(id);
             addPreviewSurface(builder, null, id);
 
@@ -1034,8 +1073,7 @@
             return;
         }
         try {
-            CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice
-                    .TEMPLATE_PREVIEW);
+            CaptureRequest.Builder builder = getRequestBuilder(id);
             builder.setTag(id);
             addPreviewSurface(builder, null, id);
 
@@ -1072,6 +1110,11 @@
             mPreviewRequestBuilder[id].set(BayerMonoLinkEnableKey, (byte) 0);
         }
     }
+
+    public PostProcessor getPostProcessor() {
+        return mPostProcessor;
+    }
+
     private void captureStillPicture(final int id) {
         Log.d(TAG, "captureStillPicture " + id);
         mIsRefocus = false;
@@ -1191,30 +1234,7 @@
                         mMpoSaveHandler.obtainMessage(MpoSaveHandler.MSG_CONFIGURE,
                                 Long.valueOf(mCaptureStartTime)).sendToTarget();
                     }
-                    mCaptureSession[id].capture(captureBuilder.build(),
-                            new CameraCaptureSession.CaptureCallback() {
-
-                                @Override
-                                public void onCaptureCompleted(CameraCaptureSession session,
-                                                               CaptureRequest request,
-                                                               TotalCaptureResult result) {
-                                    Log.d(TAG, "captureStillPicture onCaptureCompleted: " + id);
-                                }
-
-                                @Override
-                                public void onCaptureFailed(CameraCaptureSession session,
-                                                            CaptureRequest request,
-                                                            CaptureFailure result) {
-                                    Log.d(TAG, "captureStillPicture onCaptureFailed: " + id);
-                                }
-
-                                @Override
-                                public void onCaptureSequenceCompleted(CameraCaptureSession session, int
-                                        sequenceId, long frameNumber) {
-                                    Log.d(TAG, "captureStillPicture onCaptureSequenceCompleted: " + id);
-                                    unlockFocus(id);
-                                }
-                            }, mCaptureCallbackHandler);
+                    mCaptureSession[id].capture(captureBuilder.build(), captureCallback, mCaptureCallbackHandler);
                 }
             }
         } catch (CameraAccessException e) {
@@ -1275,8 +1295,7 @@
     private void runPrecaptureSequence(int id) {
         Log.d(TAG, "runPrecaptureSequence: " + id);
         try {
-            CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice
-                    .TEMPLATE_PREVIEW);
+            CaptureRequest.Builder builder = getRequestBuilder(id);
             builder.setTag(id);
             addPreviewSurface(builder, null, id);
             applySettingsForPrecapture(builder, id);
@@ -1324,7 +1343,7 @@
 
                 if (isClearSightOn()) {
                     if(i == getMainCameraId()) {
-                        ClearSightImageProcessor.getInstance().init(mPictureSize.getWidth(),
+                        ClearSightImageProcessor.getInstance().init(map, mPictureSize.getWidth(),
                                 mPictureSize.getHeight(), mActivity, mOnMediaSavedListener);
                         ClearSightImageProcessor.getInstance().setCallback(this);
                     }
@@ -1332,9 +1351,9 @@
                     // No Clearsight
                     mImageReader[i] = ImageReader.newInstance(mPictureSize.getWidth(),
                             mPictureSize.getHeight(), imageFormat, PostProcessor.MAX_REQUIRED_IMAGE_NUM);
-                    if((mPostProcessor.isFilterOn() || getFrameFilters().size() != 0)
+                    if ((mPostProcessor.isFilterOn() || getFrameFilters().size() != 0 || mPostProcessor.isZSLEnabled())
                             && i == getMainCameraId()) {
-                        mImageReader[i].setOnImageAvailableListener(mPostProcessor, mImageAvailableHandler);
+                        mImageReader[i].setOnImageAvailableListener(mPostProcessor.getImageHandler(), mImageAvailableHandler);
                     } else {
                         mImageReader[i].setOnImageAvailableListener(new ImageAvailableListener(i) {
                             @Override
@@ -1416,14 +1435,21 @@
     private void unlockFocus(int id) {
         Log.d(TAG, "unlockFocus " + id);
         try {
-            CaptureRequest.Builder builder = mCameraDevice[id].createCaptureRequest(CameraDevice
-                    .TEMPLATE_PREVIEW);
+            CaptureRequest.Builder builder = getRequestBuilder(id);
             builder.setTag(id);
             addPreviewSurface(builder, null, id);
 
             applySettingsForUnlockFocus(builder, id);
             mCaptureSession[id].capture(builder.build(), mCaptureCallback, mCameraHandler);
             mState[id] = STATE_PREVIEW;
+            if (id == getMainCameraId()) {
+                mActivity.runOnUiThread(new Runnable() {
+                    @Override
+                    public void run() {
+                        mUI.clearFocus();
+                    }
+                });
+            }
             mControlAFMode = CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE;
             applySettingsForUnlockExposure(mPreviewRequestBuilder[id], id);
             setAFModeToPreview(id, mControlAFMode);
@@ -1758,9 +1784,10 @@
             return PostProcessor.FILTER_SHARPSHOOTER;
         } else if (mode == SettingsManager.SCENE_MODE_UBIFOCUS_INT) {
             return PostProcessor.FILTER_UBIFOCUS;
-        } else if (mode == SettingsManager.SCENE_MODE_AUTO_INT && StillmoreFilter.isSupportedStatic()) {
-            return PostProcessor.FILTER_STILLMORE;
-        } else if (mode == SettingsManager.SCENE_MODE_BESTPICTURE_INT) {
+        }// else if (mode == SettingsManager.SCENE_MODE_AUTO_INT && StillmoreFilter.isSupportedStatic()) {
+         //   return PostProcessor.FILTER_STILLMORE;
+         //TODO: Need to put this back
+        else if (mode == SettingsManager.SCENE_MODE_BESTPICTURE_INT) {
             return PostProcessor.FILTER_BESTPICTURE;
         }
         return PostProcessor.FILTER_NONE;
@@ -1827,20 +1854,24 @@
         }
 
         if(mPostProcessor != null) {
+            String longshot = mSettingsManager.getValue(SettingsManager.KEY_LONGSHOT);
+            String flashMode = mSettingsManager.getValue(SettingsManager.KEY_FLASH_MODE);
             String scene = mSettingsManager.getValue(SettingsManager.KEY_SCENE_MODE);
             if (scene != null) {
                 int mode = Integer.parseInt(scene);
                 Log.d(TAG, "Chosen postproc filter id : " + getPostProcFilterId(mode));
-                mPostProcessor.onOpen(getPostProcFilterId(mode));
+                mPostProcessor.onOpen(getPostProcFilterId(mode), longshot != null && longshot.equals("on"),
+                                      flashMode != null && flashMode.equals(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH+""));
             } else {
-                mPostProcessor.onOpen(PostProcessor.FILTER_NONE);
+                mPostProcessor.onOpen(PostProcessor.FILTER_NONE, longshot != null && longshot.equals("on"),
+                                      flashMode != null && flashMode.equals(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH+""));
             }
         }
         if(mFrameProcessor != null) {
             mFrameProcessor.onOpen(getFrameProcFilterId());
         }
 
-        if(mPostProcessor.isFilterOn() || getFrameFilters().size() != 0) {
+        if(mPostProcessor.isFilterOn() || getFrameFilters().size() != 0 || mPostProcessor.isZSLEnabled()) {
             setUpCameraOutputs(ImageFormat.YUV_420_888);
         } else {
             setUpCameraOutputs(ImageFormat.JPEG);
@@ -2549,7 +2580,16 @@
         mMediaRecorderPausing = false;
         mRecordingStartTime = SystemClock.uptimeMillis();
         updateRecordingTime();
-        mMediaRecorder.start();
+        if (!ApiHelper.HAS_RESUME_SUPPORTED){
+            mMediaRecorder.start();
+        } else {
+            try {
+                Method resumeRec = Class.forName("android.media.MediaRecorder").getMethod("resume");
+                resumeRec.invoke(mMediaRecorder);
+            } catch (Exception e) {
+                Log.v(TAG, "resume method not implemented");
+            }
+        }
     }
 
     public void onButtonPause() {
@@ -3265,11 +3305,14 @@
                     if (count == 0) restart();
                     return;
                 case SettingsManager.KEY_SCENE_MODE:
-                    if (count == 0 && checkNeedToRestart(value)) {
-                        restart();
-                        return;
-                    }
-                    break;
+                    if (count == 0) restart();
+                    return;
+                case SettingsManager.KEY_LONGSHOT:
+                    if (count == 0) restart();
+                    return;
+                case SettingsManager.KEY_FLASH_MODE:
+                    if (count == 0) restart(); //Restart is due to ZSL mode change
+                    return;
             }
 
             if (isBackCamera()) {
@@ -3404,20 +3447,6 @@
         return mUI.getTrackingFocusRenderer();
     }
 
-    /**
-     * Compares two {@code Size}s based on their areas.
-     */
-    static class CompareSizesByArea implements Comparator<Size> {
-
-        @Override
-        public int compare(Size lhs, Size rhs) {
-            // We cast here to ensure the multiplications won't overflow
-            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
-                    (long) rhs.getWidth() * rhs.getHeight());
-        }
-
-    }
-
     private class MyCameraHandler extends Handler {
 
         public MyCameraHandler(Looper looper) {
diff --git a/src/com/android/camera/ListPreference.java b/src/com/android/camera/ListPreference.java
index 000baf1..722e958 100644
--- a/src/com/android/camera/ListPreference.java
+++ b/src/com/android/camera/ListPreference.java
@@ -171,7 +171,11 @@
     }
 
     public String getEntry() {
-        return mEntries[findIndexOfValue(getValue())].toString();
+        int index  = findIndexOfValue(getValue());
+        if(index < 0) {
+            return findSupportedDefaultValue();
+        }
+        return mEntries[index].toString();
     }
 
     public String getLabel() {
diff --git a/src/com/android/camera/PermissionsActivity.java b/src/com/android/camera/PermissionsActivity.java
index 6cc38f3..2bb5f0f 100644
--- a/src/com/android/camera/PermissionsActivity.java
+++ b/src/com/android/camera/PermissionsActivity.java
@@ -182,21 +182,22 @@
 
     private void handlePermissionsSuccess() {
         if (mIntent != null) {
-           mIsReturnResult = true;
-           mIntent.setClass(this, CameraActivity.class);
-           startActivityForResult(mIntent, 1);
+            mIsReturnResult = true;
+            mIntent.setClass(this, CameraActivity.class);
+            mIntent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
+            startActivity(mIntent);
+            finish();
         } else {
-           mIsReturnResult = false;
-           Intent intent = new Intent(this, CameraActivity.class);
-           startActivity(intent);
-           finish();
+            mIsReturnResult = false;
+            Intent intent = new Intent(this, CameraActivity.class);
+            startActivity(intent);
+            finish();
         }
     }
 
     private void handlePermissionsFailure() {
         new AlertDialog.Builder(this).setTitle(getResources().getString(R.string.camera_error_title))
                 .setMessage(getResources().getString(R.string.error_permissions))
-                .setCancelable(false)
                 .setOnKeyListener(new Dialog.OnKeyListener() {
                     @Override
                     public boolean onKey(DialogInterface dialog, int keyCode, KeyEvent event) {
@@ -215,11 +216,4 @@
                 })
                 .show();
     }
-
-    @Override
-    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
-        super.onActivityResult(requestCode, resultCode, data);
-        setResult(resultCode, data);
-        finish();
-    }
 }
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 30f67a3..ca4f30b 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -1123,7 +1123,7 @@
                     }
                 });
             }
-            if (mRefocus) {
+            if (mRefocus && isShutterSoundOn()) {
                 mSoundPool.play(mRefocusSound, 1.0f, 1.0f, 0, 0, 1.0f);
             }
         }
@@ -1488,9 +1488,17 @@
                             onCaptureDone();
                         }
                     }
-                    if(!mLongshotActive)
-                        mActivity.updateStorageSpaceAndHint();
-                    mUI.updateRemainingPhotos(--mRemainingPhotos);
+                    if(!mLongshotActive) {
+                        mActivity.updateStorageSpaceAndHint(
+                                new CameraActivity.OnStorageUpdateDoneListener() {
+                            @Override
+                            public void onStorageUpdateDone(long storageSpace) {
+                                mUI.updateRemainingPhotos(--mRemainingPhotos);
+                            }
+                        });
+                    } else {
+                        mUI.updateRemainingPhotos(--mRemainingPhotos);
+                    }
                     long now = System.currentTimeMillis();
                     mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
                     Log.v(TAG, "mJpegCallbackFinishTime = "
@@ -4856,6 +4864,10 @@
 
     @Override
     public void onMakeupLevel(String key, String value) {
+        if (mCameraDevice == null) {
+            Log.d(TAG,"MakeupLevel failed CameraDevice not yet initialized");
+            return;
+        }
         synchronized (mCameraDevice) {
             onMakeupLevelSync(key, value);
         }
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index bae16bd..92a2e80 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -106,9 +106,6 @@
 
     private static final long SHUTTER_BUTTON_TIMEOUT = 0L; // 0ms
 
-    public static final boolean HAS_RESUME_SUPPORTED =
-            Build.VERSION.SDK_INT > Build.VERSION_CODES.M;
-
     /**
      * An unpublished intent flag requesting to start recording straight away
      * and return as soon as recording is stopped.
@@ -1416,6 +1413,7 @@
         if (valid) {
             resultCode = Activity.RESULT_OK;
             resultIntent.setData(mCurrentVideoUri);
+            resultIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
         } else {
             resultCode = Activity.RESULT_CANCELED;
         }
@@ -1979,7 +1977,7 @@
         mMediaRecorderPausing = false;
         mRecordingStartTime = SystemClock.uptimeMillis();
         updateRecordingTime();
-        if (!HAS_RESUME_SUPPORTED){
+        if (!ApiHelper.HAS_RESUME_SUPPORTED){
             mMediaRecorder.start();
         } else {
             try {
diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java
index 00f3c70..c4ff511 100644
--- a/src/com/android/camera/VideoUI.java
+++ b/src/com/android/camera/VideoUI.java
@@ -798,8 +798,11 @@
     }
 
     public boolean sendTouchToMenu(MotionEvent ev) {
-        View v = mMenuLayout.getChildAt(0);
-        return v.dispatchTouchEvent(ev);
+        if (mMenuLayout != null) {
+            View v = mMenuLayout.getChildAt(0);
+            return v.dispatchTouchEvent(ev);
+        }
+        return false;
     }
 
     public void dismissSceneModeMenu() {
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
old mode 100644
new mode 100755
index 4643d03..4b810a0
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -42,7 +42,6 @@
     private static final String TAG = "CAM_CameraDataAdapter";
 
     private static final int DEFAULT_DECODE_SIZE = 1600;
-    private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "/%" ,SDCard.instance().getDirectory() + "/%"};
 
     private LocalDataList mImages;
 
@@ -137,7 +136,7 @@
         Cursor c = cr.query(uri,
                 LocalMediaData.VideoData.QUERY_PROJECTION,
                 MediaStore.Video.Media.DATA + " like ? or " +
-                MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH,
+                MediaStore.Video.Media.DATA + " like ? ", getCameraPath(),
                 LocalMediaData.VideoData.QUERY_ORDER);
         if (c == null || !c.moveToFirst()) {
             return;
@@ -164,7 +163,7 @@
         Cursor c = cr.query(uri,
                 LocalMediaData.PhotoData.QUERY_PROJECTION,
                 MediaStore.Images.Media.DATA + " like ? or " +
-                MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
+                MediaStore.Images.Media.DATA + " like ? ", getCameraPath(),
                 LocalMediaData.PhotoData.QUERY_ORDER);
         if (c == null || !c.moveToFirst()) {
             return;
@@ -272,6 +271,12 @@
         }
     }
 
+    private static String[] getCameraPath() {
+        String[] cameraPath =
+                {Storage.DIRECTORY + "/%", SDCard.instance().getDirectory() + "/%"};
+        return cameraPath;
+    }
+
     private class QueryTask extends AsyncTask<ContentResolver, Void, LocalDataList> {
 
         /**
@@ -289,7 +294,7 @@
                     LocalMediaData.PhotoData.CONTENT_URI,
                     LocalMediaData.PhotoData.QUERY_PROJECTION,
                     MediaStore.Images.Media.DATA + " like ? or " +
-                    MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
+                    MediaStore.Images.Media.DATA + " like ? ", getCameraPath(),
                     LocalMediaData.PhotoData.QUERY_ORDER);
             if (c != null && c.moveToFirst()) {
                 // build up the list.
@@ -319,7 +324,7 @@
                     LocalMediaData.VideoData.CONTENT_URI,
                     LocalMediaData.VideoData.QUERY_PROJECTION,
                     MediaStore.Video.Media.DATA + " like ? or " +
-                    MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH,
+                    MediaStore.Video.Media.DATA + " like ? ", getCameraPath(),
                     LocalMediaData.VideoData.QUERY_ORDER);
             if (c != null && c.moveToFirst()) {
                 // build up the list.
diff --git a/src/com/android/camera/exif/ExifInterface.java b/src/com/android/camera/exif/ExifInterface.java
index 7735188..0f495f2 100644
--- a/src/com/android/camera/exif/ExifInterface.java
+++ b/src/com/android/camera/exif/ExifInterface.java
@@ -2047,7 +2047,11 @@
     }
 
     public boolean addMakeAndModelTag() {
-        ExifTag t = buildTag(TAG_MAKE, Build.MANUFACTURER);
+        String maker = Build.MANUFACTURER;
+        if ( maker.equals("unknown") ) {
+            maker = "QCOM-AA";
+        }
+        ExifTag t = buildTag(TAG_MAKE, maker);
         if (t == null) {
             return false;
         }
diff --git a/src/com/android/camera/imageprocessor/PostProcessor.java b/src/com/android/camera/imageprocessor/PostProcessor.java
index 295707a..df389c0 100644
--- a/src/com/android/camera/imageprocessor/PostProcessor.java
+++ b/src/com/android/camera/imageprocessor/PostProcessor.java
@@ -35,9 +35,15 @@
 import android.graphics.YuvImage;
 import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCaptureSession;
+import android.hardware.camera2.CameraDevice;
+import android.hardware.camera2.CameraMetadata;
+import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.CaptureRequest;
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
 import android.media.Image;
 import android.media.ImageReader;
+import android.media.ImageWriter;
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -65,11 +71,12 @@
 import java.util.ArrayList;
 import java.util.List;
 import java.util.TimeZone;
+import java.util.concurrent.Semaphore;
 
 import com.android.camera.imageprocessor.filter.ImageFilter;
 import com.android.camera.util.CameraUtil;
 
-public class PostProcessor implements ImageReader.OnImageAvailableListener{
+public class PostProcessor{
 
     private CaptureModule mController;
 
@@ -82,8 +89,8 @@
     public static final int FILTER_BESTPICTURE = 5;
     public static final int FILTER_MAX = 6;
 
-    //Max image is now Bestpicture filter with 10
-    public static final int MAX_REQUIRED_IMAGE_NUM = 10;
+    //BestPicture requires 10 which is the biggest among filters
+    public static final int MAX_REQUIRED_IMAGE_NUM = 11;
     private int mCurrentNumImage = 0;
     private ImageFilter mFilter;
     private int mFilterIndex;
@@ -99,26 +106,192 @@
     private PhotoModule.NamedImages mNamedImages;
     private WatchdogThread mWatchdog;
     private int mOrientation = 0;
+    private ImageWriter mZSLImageWriter;
 
     //This is for the debug feature.
     private static boolean DEBUG_FILTER = false;
+    private static boolean DEBUG_ZSL = false;
     private ImageFilter.ResultImage mDebugResultImage;
+    private ZSLQueue mZSLQueue;
+    private CameraDevice mCameraDevice;
+    private CameraCaptureSession mCaptureSession;
+    private ImageReader mImageReader;
+    private boolean mUseZSL = true;
+    private Handler mZSLHandler;
+    private HandlerThread mZSLHandlerThread;
+    private ImageHandlerTask mImageHandlerTask;
 
-    @Override
-    public void onImageAvailable(ImageReader reader) {
-        try {
-            Image image = reader.acquireNextImage();
-            addImage(image);
-            if (isReadyToProcess()) {
-                long captureStartTime = System.currentTimeMillis();
-                mNamedImages.nameNewImage(captureStartTime);
-                PhotoModule.NamedImages.NamedEntity name = mNamedImages.getNextNameEntity();
-                String title = (name == null) ? null : name.title;
-                long date = (name == null) ? -1 : name.date;
-                processImage(title, date, mController.getMediaSavedListener(), mActivity.getContentResolver());
+    public boolean isZSLEnabled() {
+        return mUseZSL;
+    }
+
+    public ImageHandlerTask getImageHandler() {
+        return mImageHandlerTask;
+    }
+
+    private class ImageWrapper {
+        Image mImage;
+        boolean mIsTaken;
+
+        public ImageWrapper(Image image) {
+            mImage = image;
+            mIsTaken = false;
+        }
+
+        public boolean isTaken() {
+            return mIsTaken;
+        }
+
+        public Image getImage() {
+            mIsTaken = true;
+            return mImage;
+        }
+    }
+
+    class ImageHandlerTask implements Runnable, ImageReader.OnImageAvailableListener {
+        private ImageWrapper mImageWrapper = null;
+        Semaphore mMutureLock = new Semaphore(1);
+
+        @Override
+        public void onImageAvailable(ImageReader reader) {
+            try {
+                Image image = reader.acquireLatestImage();
+                if(image == null) {
+                    return;
+                }
+                if (!mMutureLock.tryAcquire()) {
+                    image.close();
+                    return;
+                }
+                if(mImageWrapper == null || mImageWrapper.isTaken()) {
+                    mImageWrapper = new ImageWrapper(image);
+                    mMutureLock.release();
+                } else {
+                    image.close();
+                    mMutureLock.release();
+                    return;
+                }
+                if(mZSLHandler != null) {
+                    mZSLHandler.post(this);
+                }
+            } catch (IllegalStateException e) {
+                Log.e(TAG, "Max images has been already acquired. ");
             }
-        } catch (IllegalStateException e) {
-            Log.e(TAG, "Max images has been already acquired. ");
+        }
+
+        @Override
+        public void run() {
+            try {
+                mMutureLock.acquire();
+                Image image = mImageWrapper.getImage();
+                if (mUseZSL) {
+                    if (mZSLQueue != null) {
+                        mZSLQueue.add(image);
+                    }
+                } else {
+                    onImageToProcess(image);
+                }
+                mMutureLock.release();
+            } catch (InterruptedException e) {
+            }
+        }
+    }
+
+    public void onMetaAvailable(TotalCaptureResult metadata) {
+        if(mUseZSL && mZSLQueue != null) {
+            mZSLQueue.add(metadata);
+        }
+    }
+
+    public void onSessionConfiguredForZSL(CameraCaptureSession captureSession) {
+        mZSLImageWriter = ImageWriter.newInstance(captureSession.getInputSurface(), 2);
+    }
+
+    public boolean takeZSLPicture(CameraDevice cameraDevice, CameraCaptureSession captureSession, ImageReader imageReader) {
+        if(mCameraDevice == null || mCaptureSession == null || mImageReader == null) {
+            mCameraDevice = cameraDevice;
+            mCaptureSession = captureSession;
+            mImageReader = imageReader;
+        }
+        ZSLQueue.ImageItem imageItem = mZSLQueue.tryToGetMatchingItem();
+        if(mController.getPreviewCaptureResult().get(CaptureResult.CONTROL_AE_STATE) == CameraMetadata.CONTROL_AE_STATE_FLASH_REQUIRED) {
+            if(DEBUG_ZSL) Log.d(TAG, "Flash required image");
+            imageItem = null;
+        }
+        if (imageItem != null) {
+            if(DEBUG_ZSL) Log.d(TAG,"Got the item from the queue");
+            reprocessImage(imageItem);
+            return true;
+        } else {
+            if(DEBUG_ZSL) Log.d(TAG, "No good item in queue, reigster the request for the future");
+            mZSLQueue.addPictureRequest();
+            return false;
+        }
+    }
+
+    public void onMatchingZSLPictureAvailable(ZSLQueue.ImageItem imageItem) {
+        reprocessImage(imageItem);
+    }
+
+    private void reprocessImage(ZSLQueue.ImageItem imageItem) {
+        if(mCameraDevice == null || mCaptureSession == null || mImageReader == null) {
+            Log.e(TAG, "Reprocess request is called even before taking picture");
+            new Throwable().printStackTrace();
+            return;
+        }
+        CaptureRequest.Builder builder = null;
+        try {
+            builder = mCameraDevice.createReprocessCaptureRequest(imageItem.getMetadata());
+            builder.addTarget(mImageReader.getSurface());
+            builder.set(CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE,
+                    CaptureRequest.COLOR_CORRECTION_ABERRATION_MODE_HIGH_QUALITY);
+            builder.set(CaptureRequest.EDGE_MODE,
+                    CaptureRequest.EDGE_MODE_HIGH_QUALITY);
+            builder.set(CaptureRequest.NOISE_REDUCTION_MODE,
+                    CaptureRequest.NOISE_REDUCTION_MODE_HIGH_QUALITY);
+            try {
+                mZSLImageWriter.queueInputImage(imageItem.getImage());
+            } catch(IllegalStateException e) {
+                Log.e(TAG, "Queueing more than it can have");
+            }
+            mCaptureSession.capture(builder.build(),
+                    new CameraCaptureSession.CaptureCallback() {
+                        @Override
+                        public void onCaptureCompleted(
+                                CameraCaptureSession session,
+                                CaptureRequest request,
+                                TotalCaptureResult result) {
+                            super.onCaptureCompleted(session, request, result);
+                            Log.d(TAG, "Success to reprocess zsl image");
+                            try {
+                                onImageToProcess(mZSLImageWriter.dequeueInputImage());
+                            } catch(IllegalStateException e) {
+                                Log.e(TAG, "Dequeueing more than what it has");
+                            }
+                        }
+
+                        @Override
+                        public void onCaptureFailed(
+                                CameraCaptureSession session,
+                                CaptureRequest request,
+                                CaptureFailure failure) {
+                            super.onCaptureFailed(session, request, failure);
+                            Log.e(TAG, "failed to reprocess");
+                        }
+                    }, null);
+        } catch (CameraAccessException e) {
+        }
+    }
+
+    private void onImageToProcess(Image image) {
+        addImage(image);
+        if (isReadyToProcess()) {
+            long captureStartTime = System.currentTimeMillis();
+            mNamedImages.nameNewImage(captureStartTime);
+            PhotoModule.NamedImages.NamedEntity name = mNamedImages.getNextNameEntity();
+            String title = (name == null) ? null : name.title;
+            long date = (name == null) ? -1 : name.date;
+            processImage(title, date, mController.getMediaSavedListener(), mActivity.getContentResolver());
         }
     }
 
@@ -133,7 +306,6 @@
         mController = module;
         mActivity = activity;
         mNamedImages = new PhotoModule.NamedImages();
-
     }
 
     public boolean isItBusy() {
@@ -168,10 +340,18 @@
         return false;
     }
 
-    public void onOpen(int postFilterId) {
-        setFilter(postFilterId);
-        startBackgroundThread();
+    public void onOpen(int postFilterId, boolean isLongShotOn, boolean isFlashModeOn) {
+        mImageHandlerTask = new ImageHandlerTask();
 
+        if(setFilter(postFilterId) || isLongShotOn || isFlashModeOn) {
+            mUseZSL = false;
+        } else {
+            mUseZSL = true;
+        }
+        startBackgroundThread();
+        if(mUseZSL) {
+            mZSLQueue = new ZSLQueue(mController);
+        }
     }
 
     public int getFilterIndex() {
@@ -186,6 +366,17 @@
             stopBackgroundThread();
         }
         setFilter(FILTER_NONE);
+        if(mZSLQueue != null) {
+            mZSLQueue.onClose();
+            mZSLQueue = null;
+        }
+        if (mZSLImageWriter != null) {
+            mZSLImageWriter.close();
+            mZSLImageWriter = null;
+        }
+        mCameraDevice = null;
+        mCaptureSession = null;
+        mImageReader = null;
     }
 
     private void startBackgroundThread() {
@@ -193,6 +384,10 @@
         mHandlerThread.start();
         mHandler = new ProcessorHandler(mHandlerThread.getLooper());
 
+        mZSLHandlerThread = new HandlerThread("ZSLHandlerThread");
+        mZSLHandlerThread.start();
+        mZSLHandler = new ProcessorHandler(mZSLHandlerThread.getLooper());
+
         mWatchdog = new WatchdogThread();
         mWatchdog.start();
     }
@@ -215,7 +410,6 @@
                     }
                 }
             }
-
         }
 
         public void startMonitor() {
@@ -261,6 +455,15 @@
             mHandlerThread = null;
             mHandler = null;
         }
+        if (mZSLHandlerThread != null) {
+            mZSLHandlerThread.quitSafely();
+            try {
+                mZSLHandlerThread.join();
+            } catch (InterruptedException e) {
+            }
+            mZSLHandlerThread = null;
+            mZSLHandler = null;
+        }
         if(mWatchdog != null) {
             mWatchdog.kill();
             mWatchdog = null;
diff --git a/src/com/android/camera/imageprocessor/ZSLQueue.java b/src/com/android/camera/imageprocessor/ZSLQueue.java
new file mode 100644
index 0000000..ce2da1c
--- /dev/null
+++ b/src/com/android/camera/imageprocessor/ZSLQueue.java
@@ -0,0 +1,348 @@
+/*
+Copyright (c) 2016, The Linux Foundation. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+    * Redistributions of source code must retain the above copyright
+      notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above
+      copyright notice, this list of conditions and the following
+      disclaimer in the documentation and/or other materials provided
+      with the distribution.
+    * Neither the name of The Linux Foundation nor the names of its
+      contributors may be used to endorse or promote products derived
+      from this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT
+ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
+BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
+BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
+IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.android.camera.imageprocessor;
+
+import android.hardware.camera2.CaptureResult;
+import android.hardware.camera2.TotalCaptureResult;
+import android.media.Image;
+import android.util.Log;
+
+import com.android.camera.CaptureModule;
+import android.os.SystemProperties;
+
+import java.util.LinkedList;
+
+public class ZSLQueue {
+    private static final String CIRCULAR_BUFFER_SIZE_PERSIST = "persist.camera.zsl.buffer.size";
+    private static final int CIRCULAR_BUFFER_SIZE_DEFAULT = 5;
+    private int mCircularBufferSize = CIRCULAR_BUFFER_SIZE_DEFAULT;
+    private ImageItem[] mBuffer;
+    private int mImageHead;
+    private int mMetaHead;
+    private Object mLock = new Object();
+    private LinkedList<PendingRequest> mPendingRequestList = new LinkedList<PendingRequest>();
+    private CaptureModule mModule;
+    private static final boolean DEBUG  = false;
+    private static final boolean DEBUG_QUEUE  = false;
+    private static final String TAG = "ZSLQueue";
+    private static final int REQUEST_LIFESPAN = 5; //This is in frame count.
+
+    class PendingRequest {
+        private int mLifeTimeInFrame;
+
+        public PendingRequest(){
+            mLifeTimeInFrame = 0;
+        }
+
+        public int getLifeTime() {
+            return mLifeTimeInFrame;
+        }
+
+        public void incLifeTime() {
+            mLifeTimeInFrame++;
+        }
+    }
+
+    public ZSLQueue(CaptureModule module) {
+        mCircularBufferSize = SystemProperties.getInt(CIRCULAR_BUFFER_SIZE_PERSIST, CIRCULAR_BUFFER_SIZE_DEFAULT);
+        synchronized (mLock) {
+            mBuffer = new ImageItem[mCircularBufferSize];
+            mImageHead = 0;
+            mMetaHead = 0;
+            mPendingRequestList.clear();
+            mModule = module;
+        }
+    }
+
+    private int findMeta(long timestamp, int index) {
+        int startIndex = index;
+        do {
+            if(mBuffer[index] != null && mBuffer[index].getMetadata() != null &&
+                    mBuffer[index].getMetadata().get(CaptureResult.SENSOR_TIMESTAMP).longValue() == timestamp) {
+                return index;
+            }
+            index = (index + 1) % mBuffer.length;
+        } while(index != startIndex);
+        return -1;
+    }
+
+    private int findImage(long timestamp, int index) {
+        int startIndex = index;
+        do {
+            if(mBuffer[index] != null && mBuffer[index].getImage() != null &&
+                    mBuffer[index].getImage().getTimestamp() == timestamp) {
+                return index;
+            }
+            index = (index + 1) % mBuffer.length;
+        } while(index != startIndex);
+        return -1;
+    }
+
+    public void add(Image image) {
+        int lastIndex = -1;
+        synchronized (mLock) {
+            if(mBuffer == null)
+                return;
+            if(mBuffer[mImageHead] != null) {
+                mBuffer[mImageHead].closeImage();
+            } else {
+                mBuffer[mImageHead] = new ImageItem();
+            }
+            if(mBuffer[mImageHead].getMetadata() != null) {
+                if((mBuffer[mImageHead].getMetadata().get(CaptureResult.SENSOR_TIMESTAMP)).longValue() == image.getTimestamp()) {
+                    mBuffer[mImageHead].setImage(image);
+                    lastIndex = mImageHead;
+                    mImageHead = (mImageHead + 1) % mBuffer.length;
+                } else if((mBuffer[mImageHead].getMetadata().get(CaptureResult.SENSOR_TIMESTAMP)).longValue() > image.getTimestamp()) {
+                    image.close();
+                } else {
+                    int i = findMeta(image.getTimestamp(), mImageHead);
+                    if(i == -1) {
+                        mBuffer[mImageHead].setImage(image);
+                        mBuffer[mImageHead].setMetadata(null);
+                        mImageHead = (mImageHead + 1) % mBuffer.length;
+                    } else {
+                        lastIndex = mImageHead = i;
+                        mBuffer[mImageHead].setImage(image);
+                        mImageHead = (mImageHead + 1) % mBuffer.length;
+                    }
+                }
+            } else {
+                mBuffer[mImageHead].setImage(image);
+                lastIndex = mImageHead;
+                mImageHead = (mImageHead + 1) % mBuffer.length;
+            }
+        }
+
+        if(DEBUG_QUEUE) Log.d(TAG, "imageIndex: " + lastIndex + " " + image.getTimestamp());
+
+        if(mPendingRequestList.size() != 0) {
+            if(lastIndex != -1) {
+                processPendingRequest(lastIndex);
+            }
+            for(int i=0; i < mPendingRequestList.size(); i++) {
+                mPendingRequestList.get(i).incLifeTime();
+            }
+        }
+    }
+
+    private void processPendingRequest(int index) {
+        ImageItem item;
+        synchronized (mLock) {
+            if(mBuffer == null)
+                return;
+            item = mBuffer[index];
+            if (item != null && item.isValid() && checkImageRequirement(item.getMetadata())) {
+                if(DEBUG && (mBuffer[index].getMetadata().get(CaptureResult.SENSOR_TIMESTAMP)).longValue() !=
+                        mBuffer[index].getImage().getTimestamp()) {
+                    Log.e(TAG,"Not matching image coming through");
+                }
+                mBuffer[index] = null;
+                mPendingRequestList.removeFirst();
+                for(PendingRequest request : mPendingRequestList) {
+                    if(request.getLifeTime() >= REQUEST_LIFESPAN) {
+                        mPendingRequestList.remove(request);
+                    }
+                }
+            } else {
+                return;
+            }
+        }
+        mModule.getPostProcessor().onMatchingZSLPictureAvailable(item);
+    }
+
+    public void add(TotalCaptureResult metadata) {
+        int lastIndex = -1;
+        synchronized (mLock) {
+            if(mBuffer == null)
+                return;
+            long timestamp = -1;
+            try {
+                timestamp = metadata.get(CaptureResult.SENSOR_TIMESTAMP).longValue();
+            } catch(IllegalStateException e) {
+                //This happens when corresponding image to this metadata is closed and discarded.
+                return;
+            }
+            if(timestamp == -1) {
+                return;
+            }
+            if(mBuffer[mMetaHead] == null) {
+                mBuffer[mMetaHead] = new ImageItem();
+            } else {
+                mBuffer[mMetaHead].closeMeta();
+            }
+            if(mBuffer[mMetaHead].getImage() != null) {
+                if(mBuffer[mMetaHead].getImage().getTimestamp() == timestamp) {
+                    mBuffer[mMetaHead].setMetadata(metadata);
+                    lastIndex = mMetaHead;
+                    mMetaHead = (mMetaHead + 1) % mBuffer.length;
+                } else if(mBuffer[mMetaHead].getImage().getTimestamp() > timestamp) {
+                    //Disard
+                } else {
+                    int i = findImage(timestamp, mMetaHead);
+                    if(i == -1) {
+                        mBuffer[mMetaHead].setImage(null);
+                        mBuffer[mMetaHead].setMetadata(metadata);
+                        mMetaHead = (mMetaHead + 1) % mBuffer.length;
+                    } else {
+                        lastIndex = mMetaHead = i;
+                        mBuffer[mMetaHead].setMetadata(metadata);
+                        mMetaHead = (mMetaHead + 1) % mBuffer.length;
+                    }
+                }
+            } else {
+                mBuffer[mMetaHead].setMetadata(metadata);
+                lastIndex = mImageHead;
+                mMetaHead = (mMetaHead + 1) % mBuffer.length;
+            }
+        }
+
+        if(DEBUG_QUEUE) Log.d(TAG, "Meta: " + lastIndex + " " + metadata.get(CaptureResult.SENSOR_TIMESTAMP));
+
+        if(mPendingRequestList.size() != 0) {
+            if(lastIndex != -1) {
+                processPendingRequest(lastIndex);
+            }
+            for(int i=0; i < mPendingRequestList.size(); i++) {
+                mPendingRequestList.get(i).incLifeTime();
+            }
+        }
+    }
+
+    public ImageItem tryToGetMatchingItem() {
+        synchronized (mLock) {
+            int index = mImageHead;
+            ImageItem item;
+            do {
+                item = mBuffer[index];
+                if (item != null && item.isValid() && checkImageRequirement(item.getMetadata())) {
+                    mBuffer[index] = null;
+                    return item;
+                }
+                index--;
+                if (index < 0) index = mBuffer.length - 1;
+            } while (index != mImageHead);
+        }
+        return null;
+    }
+
+    public void addPictureRequest() {
+        if(DEBUG) Log.d(TAG, "RequsetPendingCount: "+mPendingRequestList.size());
+        synchronized (mLock) {
+            mPendingRequestList.addLast(new PendingRequest());
+        }
+    }
+
+    public void onClose() {
+        synchronized (mLock) {
+            for (int i = 0; i < mBuffer.length; i++) {
+                if (mBuffer[i] != null) {
+                    mBuffer[i].closeImage();
+                    mBuffer[i].closeMeta();
+                    mBuffer[i] = null;
+                }
+            }
+            mBuffer = null;
+            mImageHead = 0;
+            mMetaHead = 0;
+            mPendingRequestList.clear();
+        }
+    }
+
+    private boolean checkImageRequirement(TotalCaptureResult captureResult) {
+        if( (captureResult.get(CaptureResult.LENS_STATE) != null &&
+             captureResult.get(CaptureResult.LENS_STATE).intValue() == CaptureResult.LENS_STATE_MOVING)
+                ||
+            (captureResult.get(CaptureResult.CONTROL_AE_STATE) != null &&
+                (captureResult.get(CaptureResult.CONTROL_AE_STATE).intValue() == CaptureResult.CONTROL_AE_STATE_SEARCHING ||
+                 captureResult.get(CaptureResult.CONTROL_AE_STATE).intValue() == CaptureResult.CONTROL_AE_STATE_PRECAPTURE))
+                ||
+            (captureResult.get(CaptureResult.CONTROL_AF_STATE) != null) &&
+                (captureResult.get(CaptureResult.CONTROL_AF_STATE) == CaptureResult.CONTROL_AF_STATE_ACTIVE_SCAN ||
+                 captureResult.get(CaptureResult.CONTROL_AF_STATE) == CaptureResult.CONTROL_AF_STATE_PASSIVE_SCAN)) {
+            return false;
+        }
+
+        if( captureResult.get(CaptureResult.CONTROL_AE_STATE) != null &&
+            captureResult.get(CaptureResult.CONTROL_AF_STATE) != null &&
+            captureResult.get(CaptureResult.CONTROL_AE_STATE).intValue() == CaptureResult.CONTROL_AE_STATE_FLASH_REQUIRED &&
+            captureResult.get(CaptureResult.CONTROL_AF_STATE).intValue() != CaptureResult.FLASH_MODE_OFF) {
+            return true;
+        }
+
+        if( captureResult.get(CaptureResult.CONTROL_AWB_STATE) != null &&
+            captureResult.get(CaptureResult.CONTROL_AWB_STATE).intValue() == CaptureResult.CONTROL_AWB_STATE_SEARCHING ) {
+            return false;
+        }
+
+        return true;
+    }
+
+    class ImageItem {
+        private Image mImage = null;
+        private TotalCaptureResult mMetadata = null;
+
+        public Image getImage() {
+            return mImage;
+        }
+
+        public void setImage(Image image) {
+            if(mImage != null) {
+                mImage.close();
+            }
+            mImage = image;
+        }
+
+        public TotalCaptureResult getMetadata() {
+            return mMetadata;
+        }
+
+        public void setMetadata(TotalCaptureResult metadata) {
+            mMetadata = metadata;
+        }
+
+        public void closeImage() {
+            if(mImage != null) {
+                mImage.close();
+            }
+            mImage = null;
+        }
+
+        public void closeMeta() {
+            mMetadata = null;
+        }
+
+        public boolean isValid() {
+            if(mImage != null && mMetadata != null) {
+                return true;
+            }
+            return false;
+        }
+    }
+}
diff --git a/src/com/android/camera/util/ApiHelper.java b/src/com/android/camera/util/ApiHelper.java
index 4a91779..890780f 100644
--- a/src/com/android/camera/util/ApiHelper.java
+++ b/src/com/android/camera/util/ApiHelper.java
@@ -53,6 +53,9 @@
             Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2;
     public static final boolean HAS_HIDEYBARS = isKitKatOrHigher();
 
+    public static final boolean HAS_RESUME_SUPPORTED =
+            Build.VERSION.SDK_INT > Build.VERSION_CODES.M;
+
     public static int getIntFieldIfExists(Class<?> klass, String fieldName,
             Class<?> obj, int defaultVal) {
         try {
diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java
index 172a545..eb237b8 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -61,12 +61,14 @@
 import com.android.camera.CameraSettings;
 import com.android.camera.ui.RotateTextToast;
 import com.android.camera.util.IntentHelper;
+
 import org.codeaurora.snapcam.R;
 
 import java.io.Closeable;
 import java.io.IOException;
 import java.lang.reflect.Method;
 import java.text.SimpleDateFormat;
+import java.util.Comparator;
 import java.util.Date;
 import java.util.List;
 import java.util.Locale;
@@ -1227,4 +1229,17 @@
         }
         return ".3gp";
     }
+
+    /**
+     * Compares two {@code Size}s based on their areas.
+     */
+    public static class CompareSizesByArea implements Comparator<android.util.Size> {
+
+        @Override
+        public int compare(android.util.Size lhs, android.util.Size rhs) {
+            // We cast here to ensure the multiplications won't overflow
+            return Long.signum((long) lhs.getWidth() * lhs.getHeight() -
+                    (long) rhs.getWidth() * rhs.getHeight());
+        }
+    }
 }
diff --git a/src/org/codeaurora/snapcam/filter/ClearSightImageProcessor.java b/src/org/codeaurora/snapcam/filter/ClearSightImageProcessor.java
index ea37d37..0633b0c 100644
--- a/src/org/codeaurora/snapcam/filter/ClearSightImageProcessor.java
+++ b/src/org/codeaurora/snapcam/filter/ClearSightImageProcessor.java
@@ -34,6 +34,7 @@
 import java.nio.ByteBuffer;
 import java.util.ArrayDeque;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 
 import org.codeaurora.snapcam.filter.ClearSightNativeEngine.CamSystemCalibrationData;
@@ -53,6 +54,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.TotalCaptureResult;
 import android.hardware.camera2.params.InputConfiguration;
+import android.hardware.camera2.params.StreamConfigurationMap;
 import android.media.Image;
 import android.media.Image.Plane;
 import android.media.ImageReader;
@@ -65,6 +67,7 @@
 import android.os.SystemProperties;
 import android.util.Log;
 import android.util.SparseLongArray;
+import android.util.Size;
 import android.view.Surface;
 
 import com.android.camera.CaptureModule;
@@ -75,6 +78,7 @@
 import com.android.camera.PhotoModule.NamedImages;
 import com.android.camera.PhotoModule.NamedImages.NamedEntity;
 import com.android.camera.Storage;
+import com.android.camera.util.CameraUtil;
 
 public class ClearSightImageProcessor {
     private static final String TAG = "ClearSightImageProcessor";
@@ -86,6 +90,13 @@
     private static final long DEFAULT_TIMESTAMP_THRESHOLD_MS = 10;
     private static final int DEFAULT_IMAGES_TO_BURST = 4;
 
+    private static final long MIN_MONO_AREA = 1900000;  // ~1.9 MP
+    private static final Size[] MONO_SIZES = {
+        new Size(1600, 1200),   // 4:3
+        new Size(1920, 1080),   // 16:9
+        new Size(1400, 1400)    // 1:1
+    };
+
     private static final int MSG_START_CAPTURE = 0;
     private static final int MSG_NEW_IMG = 1;
     private static final int MSG_NEW_CAPTURE_RESULT = 2;
@@ -107,6 +118,9 @@
     private ImageReader[] mImageReader = new ImageReader[NUM_CAM];
     private ImageReader[] mEncodeImageReader = new ImageReader[NUM_CAM];
     private ImageWriter[] mImageWriter = new ImageWriter[NUM_CAM];
+    private float mFinalPictureRatio;
+    private Size mFinalPictureSize;
+    private Size mFinalMonoSize;
 
     private ImageProcessHandler mImageProcessHandler;
     private ClearsightRegisterHandler mClearsightRegisterHandler;
@@ -163,7 +177,8 @@
         return mInstance;
     }
 
-    public void init(int width, int height, Context context, OnMediaSavedListener mediaListener) {
+    public void init(StreamConfigurationMap map, int width, int height,
+            Context context, OnMediaSavedListener mediaListener) {
         mImageProcessThread = new HandlerThread("CameraImageProcess");
         mImageProcessThread.start();
         mClearsightRegisterThread = new HandlerThread("ClearsightRegister");
@@ -178,10 +193,16 @@
         mClearsightProcessHandler = new ClearsightProcessHandler(mClearsightProcessThread.getLooper());
         mImageEncodeHandler = new ImageEncodeHandler(mImageEncodeThread.getLooper());
 
-        mImageReader[CAM_TYPE_BAYER] = createImageReader(CAM_TYPE_BAYER, width, height);
-        mImageReader[CAM_TYPE_MONO] = createImageReader(CAM_TYPE_MONO, width, height);
-        mEncodeImageReader[CAM_TYPE_BAYER] = createEncodeImageReader(CAM_TYPE_BAYER, width, height);
-        mEncodeImageReader[CAM_TYPE_MONO] = createEncodeImageReader(CAM_TYPE_MONO, width, height);
+        mFinalPictureSize = new Size(width, height);
+        mFinalPictureRatio = (float)width / (float)height;
+        mFinalMonoSize = getFinalMonoSize();
+        Size maxSize = findMaxOutputSize(map);
+        int maxWidth = maxSize.getWidth();
+        int maxHeight = maxSize.getHeight();
+        mImageReader[CAM_TYPE_BAYER] = createImageReader(CAM_TYPE_BAYER, maxWidth, maxHeight);
+        mImageReader[CAM_TYPE_MONO] = createImageReader(CAM_TYPE_MONO, maxWidth, maxHeight);
+        mEncodeImageReader[CAM_TYPE_BAYER] = createEncodeImageReader(CAM_TYPE_BAYER, maxWidth, maxHeight);
+        mEncodeImageReader[CAM_TYPE_MONO] = createEncodeImageReader(CAM_TYPE_MONO, maxWidth, maxHeight);
 
         mMediaSavedListener = mediaListener;
         CameraManager cm = (CameraManager) context.getSystemService(Context.CAMERA_SERVICE);
@@ -859,13 +880,26 @@
         private void sendReprocessRequest(CaptureRequest.Builder reprocRequest, Image image, final int camType) {
 
             try {
+                reprocRequest.set(CaptureModule.JpegCropEnableKey, (byte)1);
+
                 Rect cropRect = image.getCropRect();
-                if(cropRect != null &&
-                        !cropRect.isEmpty()) {
-                    // has crop rect. apply to jpeg request
-                    reprocRequest.set(CaptureModule.JpegCropEnableKey, (byte)1);
-                    reprocRequest.set(CaptureModule.JpegCropRectKey,
-                           new int[] {cropRect.left, cropRect.top, cropRect.width(), cropRect.height()});
+                if(cropRect == null ||
+                        cropRect.isEmpty()) {
+                    // if no crop rect set, init to default image width + height
+                    cropRect = new Rect(0, 0, image.getWidth(), image.getHeight());
+                }
+
+                cropRect = getFinalCropRect(cropRect);
+                // has crop rect. apply to jpeg request
+                reprocRequest.set(CaptureModule.JpegCropRectKey,
+                       new int[] {cropRect.left, cropRect.top, cropRect.width(), cropRect.height()});
+
+                if(camType == CAM_TYPE_MONO) {
+                    reprocRequest.set(CaptureModule.JpegRoiRectKey,
+                           new int[] {0, 0, mFinalMonoSize.getWidth(), mFinalMonoSize.getHeight()});
+                } else {
+                    reprocRequest.set(CaptureModule.JpegRoiRectKey,
+                            new int[] {0, 0, mFinalPictureSize.getWidth(), mFinalPictureSize.getHeight()});
                 }
 
                 mImageWriter[camType].queueInputImage(image);
@@ -1073,4 +1107,73 @@
 
         return data;
     }
+
+    private Size findMaxOutputSize(StreamConfigurationMap map) {
+        Size[] sizes = map.getOutputSizes(ImageFormat.YUV_420_888);
+        Arrays.sort(sizes, new CameraUtil.CompareSizesByArea());
+        return sizes[sizes.length-1];
+    }
+
+    private Size getFinalMonoSize() {
+        Size finalSize = null;
+        long finalPicArea = mFinalPictureSize.getWidth() * mFinalPictureSize.getHeight();
+
+        // if final pic size is less than 2MP, then use same MONO size
+        if(finalPicArea > MIN_MONO_AREA) {
+            for(Size size:MONO_SIZES) {
+                float monoRatio = (float)size.getWidth() / (float)size.getHeight();
+                if(monoRatio == mFinalPictureRatio) {
+                    finalSize = size;
+                    break;
+                } else if (Math.abs(monoRatio - mFinalPictureRatio) < 0.1f) {
+                    // close enough
+                    int monoWidth = size.getWidth();
+                    int monoHeight = size.getHeight();
+                    if(monoRatio > mFinalPictureRatio) {
+                        // keep width, increase height to match final ratio
+                        // add .5 to round up if necessary
+                        monoHeight = (int)(((float)monoWidth / mFinalPictureRatio) + .5f);
+                    } else if(monoRatio < mFinalPictureRatio) {
+                        // keep height, increase width to match final ratio
+                        // add .5 to round up if necessary
+                        monoWidth = (int)(((float)monoHeight * mFinalPictureRatio) + .5f);
+                    }
+                    finalSize = new Size(monoWidth, monoHeight);
+                }
+            }
+        }
+
+        if(finalSize == null) {
+            // set to mFinalPictureSize if matching size not found
+            // or if final resolution is less than 2 MP
+            finalSize = mFinalPictureSize;
+        }
+
+        return finalSize;
+    }
+
+    private Rect getFinalCropRect(Rect rect) {
+        Rect finalRect = new Rect(rect);
+        float rectRatio = (float) rect.width()/(float) rect.height();
+
+        // if ratios are different, adjust crop rect to fit ratio
+        // if ratios are same, no need to adjust crop
+        if(rectRatio > mFinalPictureRatio) {
+            // ratio indicates need for horizontal crop
+            // add .5 to round up if necessary
+            int newWidth = (int)(((float)rect.height() * mFinalPictureRatio) + .5f);
+            int newXoffset = (rect.width() - newWidth)/2;
+            finalRect.left = newXoffset;
+            finalRect.right = newXoffset + newWidth;
+        } else if(rectRatio < mFinalPictureRatio) {
+            // ratio indicates need for vertical crop
+            // add .5 to round up if necessary
+            int newHeight = (int)(((float)rect.width() / mFinalPictureRatio) + .5f);
+            int newYoffset = (rect.height() - newHeight)/2;
+            finalRect.top = newYoffset;
+            finalRect.bottom = newYoffset + newHeight;
+        }
+
+        return finalRect;
+    }
 }
diff --git a/src/org/codeaurora/snapcam/filter/ClearSightNativeEngine.java b/src/org/codeaurora/snapcam/filter/ClearSightNativeEngine.java
index d6527d5..402ffce 100644
--- a/src/org/codeaurora/snapcam/filter/ClearSightNativeEngine.java
+++ b/src/org/codeaurora/snapcam/filter/ClearSightNativeEngine.java
@@ -174,10 +174,14 @@
             Log.d(TAG, "setRefColorImage");
             Plane[] planes = mRefColorImage.getPlanes();
             SourceImage newSrc = getNewSourceImage();
+            ByteBuffer refY = planes[Y_PLANE].getBuffer();
+            ByteBuffer refVU = planes[VU_PLANE].getBuffer();
+            refY.rewind();
+            refVU.rewind();
             newSrc.mY.rewind();
-            newSrc.mY.put(planes[Y_PLANE].getBuffer());
+            newSrc.mY.put(refY);
             newSrc.mVU.rewind();
-            newSrc.mVU.put(planes[VU_PLANE].getBuffer());
+            newSrc.mVU.put(refVU);
             mSrcColor.add(newSrc);
         }
     }
@@ -194,8 +198,10 @@
             Log.d(TAG, "setRefMonoImage");
             Plane[] planes = mRefMonoImage.getPlanes();
             SourceImage newSrc = getNewSourceImage();
+            ByteBuffer refY = planes[Y_PLANE].getBuffer();
+            refY.rewind();
             newSrc.mY.rewind();
-            newSrc.mY.put(planes[Y_PLANE].getBuffer());
+            newSrc.mY.put(refY);
             mSrcMono.add(newSrc);
         }
     }