Merge "Load Gcam OneCamera in CaptureModule when it's also doing normal shots." into ub-camera-glacier
diff --git a/res/values-ca/strings.xml b/res/values-ca/strings.xml
index 11d97c0..e6eb5f9 100644
--- a/res/values-ca/strings.xml
+++ b/res/values-ca/strings.xml
@@ -333,7 +333,7 @@
     <string name="setting_front_camera_photo" msgid="4131886734622868637">"Foto de càmera frontal"</string>
     <string name="setting_front_camera_video" msgid="2178799452805359752">"Vídeo de càmera frontal"</string>
     <string name="setting_default_camera" msgid="6954076799301004779">"Càmera predeterminada"</string>
-    <string name="setting_google_help_and_feedback" msgid="2079580537079242775">"Ajuda i comentaris"</string>
+    <string name="setting_google_help_and_feedback" msgid="2079580537079242775">"Ajuda i suggeriments"</string>
     <string name="processing_hdr_plus" msgid="9160093263037540304">"Processant HDR+..."</string>
     <string name="open_source_licenses" msgid="2169711954264883060">"Llicències de codi obert"</string>
     <string name="pref_category_general" msgid="6737748849700581019">"Configuració general"</string>
diff --git a/res/values/bool.xml b/res/values/bool.xml
index bcb30de..464842a 100644
--- a/res/values/bool.xml
+++ b/res/values/bool.xml
@@ -15,5 +15,4 @@
 -->
 <resources>
     <bool name="show_action_bar_title">false</bool>
-    <bool name="is_os_version_l">false</bool>
 </resources>
\ No newline at end of file
diff --git a/src/android/util/CameraPerformanceTracker.java b/src/android/util/CameraPerformanceTracker.java
index 4e46058..60a1ab0 100644
--- a/src/android/util/CameraPerformanceTracker.java
+++ b/src/android/util/CameraPerformanceTracker.java
@@ -75,6 +75,7 @@
                 sInstance.mAppResumeTime = currentTime;
                 break;
             case FIRST_PREVIEW_FRAME:
+                Log.d(TAG, "First preview frame received");
                 if (sInstance.mFirstPreviewFrameLatencyColdStart == UNSET) {
                     // Cold start.
                     sInstance.mFirstPreviewFrameLatencyColdStart =
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index 46086a7..e6f81cd 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -1102,8 +1102,13 @@
     }
 
     @Override
+    public void startPreCaptureAnimation(boolean shortFlash) {
+        mCameraAppUI.startPreCaptureAnimation(shortFlash);
+    }
+
+    @Override
     public void startPreCaptureAnimation() {
-        mCameraAppUI.startPreCaptureAnimation();
+        mCameraAppUI.startPreCaptureAnimation(false);
     }
 
     @Override
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index 73b16c0..16a0d69 100644
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -390,7 +390,7 @@
     }
 
     @Override
-    public void onRemainingSecondsChanged(int remainingSeconds){
+    public void onRemainingSecondsChanged(int remainingSeconds) {
         if (remainingSeconds == 1) {
             mCountdownSoundPlayer.play(R.raw.beep_twice, 0.6f);
         } else if (remainingSeconds == 2 || remainingSeconds == 3) {
@@ -408,6 +408,17 @@
     }
 
     @Override
+    public void onQuickExpose() {
+        mMainHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                // Starts the short version of the capture animation UI.
+                mAppController.startPreCaptureAnimation(true);
+            }
+        });
+    }
+
+    @Override
     public void onPreviewAreaChanged(RectF previewArea) {
         mPreviewArea = previewArea;
         mUI.onPreviewAreaChanged(previewArea);
diff --git a/src/com/android/camera/app/AppController.java b/src/com/android/camera/app/AppController.java
index f273914..4516bfb 100644
--- a/src/com/android/camera/app/AppController.java
+++ b/src/com/android/camera/app/AppController.java
@@ -271,7 +271,14 @@
     /********************** Capture animation **********************/
 
     /**
-     * Starts the pre-capture animation.
+     * Starts the pre-capture animation with optional shorter flash.
+     *
+     * @param shortFlash true for shorter flash (faster cameras).
+     */
+    public void startPreCaptureAnimation(boolean shortFlash);
+
+    /**
+     * Starts normal pre-capture animation.
      */
     public void startPreCaptureAnimation();
 
diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java
index c123d52..8dc3400 100644
--- a/src/com/android/camera/app/CameraAppUI.java
+++ b/src/com/android/camera/app/CameraAppUI.java
@@ -1472,9 +1472,11 @@
 
     /**
      * Starts the pre-capture animation.
+     *
+     * @param shortFlash show shortest possible flash instead of regular long version.
      */
-    public void startPreCaptureAnimation() {
-        mCaptureOverlay.startFlashAnimation();
+    public void startPreCaptureAnimation(boolean shortFlash) {
+        mCaptureOverlay.startFlashAnimation(shortFlash);
     }
 
     /**
diff --git a/src/com/android/camera/one/OneCamera.java b/src/com/android/camera/one/OneCamera.java
index 1ae63fc..ab0f95e 100644
--- a/src/com/android/camera/one/OneCamera.java
+++ b/src/com/android/camera/one/OneCamera.java
@@ -143,6 +143,13 @@
      */
     public static interface PictureCallback {
         /**
+         * Called near the the when an image is being exposed for cameras which
+         * are exposing a single frame, so that a UI can be presented for the
+         * capture.
+         */
+        public void onQuickExpose();
+
+        /**
          * Called when a thumbnail image is provided before the final image is
          * finished.
          */
diff --git a/src/com/android/camera/one/v2/ImageCaptureManager.java b/src/com/android/camera/one/v2/ImageCaptureManager.java
index edfce80..464cb1e 100644
--- a/src/com/android/camera/one/v2/ImageCaptureManager.java
+++ b/src/com/android/camera/one/v2/ImageCaptureManager.java
@@ -102,7 +102,7 @@
      * Callback for saving an image.
      */
     public interface ImageCaptureListener {
-        /**
+         /**
          * Called with the {@link Image} and associated
          * {@link TotalCaptureResult}. A typical implementation would save this
          * to disk.
@@ -234,6 +234,23 @@
     private static final long DEBUG_MAX_IMAGE_CALLBACK_DUR = 25;
 
     /**
+     * If spacing between onCaptureCompleted() callbacks is lower than this
+     * value, camera operations at the Java level have stalled, and are now
+     * catching up. In milliseconds.
+     */
+    private static final long DEBUG_INTERFRAME_STALL_WARNING = 5;
+
+    /**
+     * Last called to onCaptureCompleted() in SystemClock.uptimeMillis().
+     */
+    private long mDebugLastOnCaptureCompletedMillis = 0;
+
+    /**
+     * Number of frames in a row exceeding DEBUG_INTERFRAME_STALL_WARNING.
+     */
+    private long mDebugStalledFrameCount = 0;
+
+    /**
      * Stores the ring-buffer of captured images.<br>
      * Note that this takes care of thread-safe reference counting of images to
      * ensure that they are never leaked by the app.
@@ -403,6 +420,16 @@
             final TotalCaptureResult result) {
         final long timestamp = result.get(TotalCaptureResult.SENSOR_TIMESTAMP);
 
+        // Detect camera thread stall.
+        long now = SystemClock.uptimeMillis();
+        if (now - mDebugLastOnCaptureCompletedMillis < DEBUG_INTERFRAME_STALL_WARNING) {
+            Log.e(TAG, "Camera thread has stalled for " + ++mDebugStalledFrameCount +
+                    " frames at # " + result.getFrameNumber() + ".");
+        } else {
+            mDebugStalledFrameCount = 0;
+        }
+        mDebugLastOnCaptureCompletedMillis = now;
+
         // Find the CapturedImage in the ring-buffer and attach the
         // TotalCaptureResult to it.
         // See documentation for swapLeast() for details.
diff --git a/src/com/android/camera/one/v2/OneCameraImpl.java b/src/com/android/camera/one/v2/OneCameraImpl.java
index 1abdadb..3f7e461 100644
--- a/src/com/android/camera/one/v2/OneCameraImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraImpl.java
@@ -125,6 +125,8 @@
     private boolean mTakePictureWhenLensIsStopped = false;
     /** Takes a (delayed) picture with appropriate parameters. */
     private Runnable mTakePictureRunnable;
+    /** Keep PictureCallback for last requested capture. */
+    private PictureCallback mLastPictureCallback = null;
     /** Last time takePicture() was called in uptimeMillis. */
     private long mTakePictureStartMillis;
     /** Runnable that returns to CONTROL_AF_MODE = AF_CONTINUOUS_PICTURE. */
@@ -153,6 +155,14 @@
      */
     private final CameraCaptureSession.CaptureListener mAutoFocusStateListener = new
             CameraCaptureSession.CaptureListener() {
+                @Override
+                public void onCaptureStarted(CameraCaptureSession session, CaptureRequest request,
+                                             long timestamp) {
+                    if (request.getTag() == RequestTag.CAPTURE && mLastPictureCallback != null) {
+                        mLastPictureCallback.onQuickExpose();
+                    }
+                }
+
                 // AF state information is sometimes available 1 frame before
                 // onCaptureCompleted(), so we take advantage of that.
                 @Override
@@ -269,6 +279,7 @@
                 takePictureNow(params, session);
             }
         };
+        mLastPictureCallback = params.callback;
         mTakePictureStartMillis = SystemClock.uptimeMillis();
 
         // This class implements a very simple version of AF, which
diff --git a/src/com/android/camera/one/v2/OneCameraZslImpl.java b/src/com/android/camera/one/v2/OneCameraZslImpl.java
index 4d5fdef..874ace4 100644
--- a/src/com/android/camera/one/v2/OneCameraZslImpl.java
+++ b/src/com/android/camera/one/v2/OneCameraZslImpl.java
@@ -34,6 +34,7 @@
 import android.media.CameraProfile;
 import android.media.Image;
 import android.media.ImageReader;
+import android.media.MediaActionSound;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Handler;
@@ -182,6 +183,8 @@
     private MeteringRectangle[] mAFRegions = ZERO_WEIGHT_3A_REGION;
     private MeteringRectangle[] mAERegions = ZERO_WEIGHT_3A_REGION;
 
+    private MediaActionSound mMediaActionSound = new MediaActionSound();
+
     /**
      * Ready state (typically displayed by the UI shutter-button) depends on two
      * things:<br>
@@ -246,7 +249,6 @@
             mReadyStateManager.setInput(
                     ReadyStateRequirement.CAPTURE_NOT_IN_PROGRESS, true);
 
-            // TODO Add callback to CaptureModule here to flash the screen.
             mSession.startEmpty();
             savePicture(image, mParams, mSession);
             mParams.callback.onPictureTaken(mSession);
@@ -319,6 +321,7 @@
                 sCaptureImageFormat, MAX_CAPTURE_IMAGES);
 
         mCaptureImageReader.setOnImageAvailableListener(mCaptureManager, mCameraHandler);
+        mMediaActionSound.load(MediaActionSound.SHUTTER_CLICK);
     }
 
     /**
@@ -345,6 +348,14 @@
                 largestSupportedSize.getHeight());
     }
 
+
+    private void onShutterInvokeUI(final PhotoCaptureParameters params) {
+        // Tell CaptureModule shutter has occurred so it can flash the screen.
+        params.callback.onQuickExpose();
+        // Play shutter click sound.
+        mMediaActionSound.play(MediaActionSound.SHUTTER_CLICK);
+    }
+
     /**
      * Take a picture.
      */
@@ -439,6 +450,7 @@
                     new ImageCaptureTask(params, session), zslConstraints);
             if (capturedPreviousFrame) {
                 Log.v(TAG, "Saving previous frame");
+                onShutterInvokeUI(params);
             } else {
                 Log.v(TAG, "No good image Available.  Capturing next available good image.");
                 // If there was no good frame available in the ring buffer
@@ -473,6 +485,8 @@
                                                     CaptureResult.CONTROL_AE_STATE_PRECAPTURE))) {
                                         mCaptureManager.removeMetadataChangeListener(key, this);
                                         sendSingleRequest(params);
+                                        // TODO: Delay this until onCaptureStarted().
+                                        onShutterInvokeUI(params);
                                     }
                                 }
                             });
diff --git a/src/com/android/camera/ui/BottomBar.java b/src/com/android/camera/ui/BottomBar.java
index cfc6a14..551cc09 100644
--- a/src/com/android/camera/ui/BottomBar.java
+++ b/src/com/android/camera/ui/BottomBar.java
@@ -32,6 +32,7 @@
 import com.android.camera.CaptureLayoutHelper;
 import com.android.camera.ShutterButton;
 import com.android.camera.debug.Log;
+import com.android.camera.util.ApiHelper;
 import com.android.camera.util.CameraUtil;
 import com.android.camera2.R;
 
@@ -74,7 +75,6 @@
     private final float mCircleRadius;
     private CaptureLayoutHelper mCaptureLayoutHelper = null;
 
-    private final boolean mIsOsVersionL;
     // for Android L, these backgrounds are RippleDrawables (ISA LayerDrawable)
     // pre-L, they're plain old LayerDrawables
     private final LayerDrawable[] mShutterButtonBackgrounds;
@@ -96,8 +96,6 @@
         mBackgroundAlphaDefault = getResources()
                 .getInteger(R.integer.bottom_bar_background_alpha);
 
-        mIsOsVersionL = context.getResources().getBoolean(R.bool.is_os_version_l);
-
         // preload all the drawable BGs
         TypedArray ar = context.getResources()
                 .obtainTypedArray(R.array.shutter_button_backgrounds);
@@ -129,13 +127,9 @@
         if (mAnimatedCircleDrawable != null) {
             mAnimatedCircleDrawable.setColor(color);
             mAnimatedCircleDrawable.setAlpha(alpha);
-            invalidateDrawable(mAnimatedCircleDrawable);
-            invalidate();
         } else if (mColorDrawable != null) {
             mColorDrawable.setColor(color);
             mColorDrawable.setAlpha(alpha);
-            invalidateDrawable(mColorDrawable);
-            invalidate();
         }
 
         if (mIntentReviewLayout != null) {
@@ -153,7 +147,7 @@
     private void setCancelBackgroundColor(int alpha, int color) {
         LayerDrawable layerDrawable = (LayerDrawable) mCancelButton.getBackground();
         ColorDrawable colorDrawable = (ColorDrawable) layerDrawable.getDrawable(0);
-        if (!mIsOsVersionL) {
+        if (!ApiHelper.isLOrHigher()) {
             colorDrawable.setColor(color);
         }
         colorDrawable.setAlpha(alpha);
@@ -164,7 +158,7 @@
     }
 
     private void setCaptureButtonDown() {
-        if (!mIsOsVersionL) {
+        if (!ApiHelper.isLOrHigher()) {
             setPaintColor(mBackgroundAlpha, mBackgroundPressedColor);
         }
     }
@@ -346,7 +340,7 @@
     }
 
     private void setBackgroundPressedColor(int color) {
-        if (mIsOsVersionL) {
+        if (ApiHelper.isLOrHigher()) {
             // not supported (setting a color on a RippleDrawable is hard =[ )
         } else {
             mBackgroundPressedColor = color;
diff --git a/src/com/android/camera/ui/CaptureAnimationOverlay.java b/src/com/android/camera/ui/CaptureAnimationOverlay.java
index 9a7126d..19a21f1 100644
--- a/src/com/android/camera/ui/CaptureAnimationOverlay.java
+++ b/src/com/android/camera/ui/CaptureAnimationOverlay.java
@@ -44,13 +44,14 @@
     implements PreviewStatusListener.PreviewAreaChangedListener {
     private final static Log.Tag TAG = new Log.Tag("CaptureAnimOverlay");
 
-    private final static int FLASH_ALPHA_BEFORE_SHRINK = 180;
-    private final static int FLASH_ALPHA_AFTER_SHRINK = 50;
     private final static int FLASH_COLOR = Color.WHITE;
 
     private static final float FLASH_MAX_ALPHA = 0.85f;
     private static final long FLASH_FULL_DURATION_MS = 65;
     private static final long FLASH_DECREASE_DURATION_MS = 150;
+    private static final float SHORT_FLASH_MAX_ALPHA = 0.75f;
+    private static final long SHORT_FLASH_FULL_DURATION_MS = 34;
+    private static final long SHORT_FLASH_DECREASE_DURATION_MS = 100;
 
     private RectF mPreviewArea = new RectF();
 
@@ -97,22 +98,37 @@
 
     /**
      * Start flash animation.
+     *
+     * @param shortFlash show shortest possible flash instead of regular long version.
      */
-    public void startFlashAnimation() {
+    public void startFlashAnimation(boolean shortFlash) {
         if (mFlashAnimation != null && mFlashAnimation.isRunning()) {
             mFlashAnimation.cancel();
         }
+        float maxAlpha;
 
-        ValueAnimator flashAnim1 = ValueAnimator.ofFloat(FLASH_MAX_ALPHA, FLASH_MAX_ALPHA);
-        ValueAnimator flashAnim2 = ValueAnimator.ofFloat(FLASH_MAX_ALPHA, .0f);
-        flashAnim1.setDuration(FLASH_FULL_DURATION_MS);
-        flashAnim2.setDuration(FLASH_DECREASE_DURATION_MS);
+        if (shortFlash) {
+            maxAlpha = SHORT_FLASH_MAX_ALPHA;
+        } else {
+            maxAlpha = FLASH_MAX_ALPHA;
+        }
+
+        ValueAnimator flashAnim1 = ValueAnimator.ofFloat(maxAlpha, maxAlpha);
+        ValueAnimator flashAnim2 = ValueAnimator.ofFloat(maxAlpha, .0f);
+
+        if (shortFlash) {
+            flashAnim1.setDuration(SHORT_FLASH_FULL_DURATION_MS);
+            flashAnim2.setDuration(SHORT_FLASH_DECREASE_DURATION_MS);
+        } else {
+            flashAnim1.setDuration(FLASH_FULL_DURATION_MS);
+            flashAnim2.setDuration(FLASH_DECREASE_DURATION_MS);
+        }
+
         flashAnim1.addUpdateListener(mFlashAnimUpdateListener);
         flashAnim2.addUpdateListener(mFlashAnimUpdateListener);
         flashAnim1.setInterpolator(mFlashAnimInterpolator);
         flashAnim2.setInterpolator(mFlashAnimInterpolator);
 
-
         mFlashAnimation = new AnimatorSet();
         mFlashAnimation.play(flashAnim1).before(flashAnim2);
         mFlashAnimation.addListener(mFlashAnimListener);