Add shutter animation when taking pictures

Additionally to playing shutter sounds, add support for showing
a shutter animation.

Can be toggled in settings, enabled by default.

Change-Id: If40a90a93a7582aaa103a9646307a80099e89a71
Signed-off-by: Alexander Martinz <amartinz@shiftphones.com>
diff --git a/res/drawable/ic_custom_shutter_animation.xml b/res/drawable/ic_custom_shutter_animation.xml
new file mode 100644
index 0000000..881f0e6
--- /dev/null
+++ b/res/drawable/ic_custom_shutter_animation.xml
@@ -0,0 +1,10 @@
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M9.4,10.5l4.77,-8.26C13.47,2.09 12.75,2 12,2c-2.4,0 -4.6,0.85 -6.32,2.25l3.66,6.35 0.06,-0.1zM21.54,9c-0.92,-2.92 -3.15,-5.26 -6,-6.34L11.88,9h9.66zM21.8,10h-7.49l0.29,0.5 4.76,8.25C21,16.97 22,14.61 22,12c0,-0.69 -0.07,-1.35 -0.2,-2zM8.54,12l-3.9,-6.75C3.01,7.03 2,9.39 2,12c0,0.69 0.07,1.35 0.2,2h7.49l-1.15,-2zM2.46,15c0.92,2.92 3.15,5.26 6,6.34L12.12,15L2.46,15zM13.73,15l-3.9,6.76c0.7,0.15 1.42,0.24 2.17,0.24 2.4,0 4.6,-0.85 6.32,-2.25l-3.66,-6.35 -0.93,1.6z" />
+</vector>
diff --git a/res/values-de/qcomstrings.xml b/res/values-de/qcomstrings.xml
index 4710427..9aee98e 100644
--- a/res/values-de/qcomstrings.xml
+++ b/res/values-de/qcomstrings.xml
@@ -311,6 +311,9 @@
     <string name="pref_camera_scenemode_entry_promode">Pro-Modus</string>
     <string name="bestpicture_done">FERTIG</string>
     <string name="bestpicture_at_least_one_picture">Es muss mindestens ein Bild ausgewählt werden.</string>
+    <string name="pref_camera2_shutter_animation_entry_on">An</string>
+    <string name="pref_camera2_shutter_animation_entry_off">Aus</string>
+    <string name="pref_camera2_shutter_animation_title">Auslöse-Animation</string>
     <string name="pref_camera2_shutter_sound_entry_on">An</string>
     <string name="pref_camera2_shutter_sound_entry_off">Aus</string>
     <string name="pref_camera2_shutter_sound_title">Auslöser-Ton</string>
diff --git a/res/values/camera2arrays.xml b/res/values/camera2arrays.xml
index f0368be..f102d13 100755
--- a/res/values/camera2arrays.xml
+++ b/res/values/camera2arrays.xml
@@ -898,6 +898,16 @@
         <item>@string/pref_camera2_timer_value_10sec</item>
     </string-array>
 
+    <string-array name="pref_camera2_shutter_animation_entries" translatable="true">
+        <item>@string/pref_camera2_shutter_animation_entry_on</item>
+        <item>@string/pref_camera2_shutter_animation_entry_off</item>
+    </string-array>
+
+    <string-array name="pref_camera2_shutter_animation_entryvalues" translatable="false">
+        <item>@string/pref_camera2_shutter_animation_value_on</item>
+        <item>@string/pref_camera2_shutter_animation_value_off</item>
+    </string-array>
+
     <string-array name="pref_camera2_shutter_sound_entries" translatable="true">
         <item>@string/pref_camera2_shutter_sound_entry_on</item>
         <item>@string/pref_camera2_shutter_sound_entry_off</item>
diff --git a/res/values/qcomstrings.xml b/res/values/qcomstrings.xml
index 1f70121..417727c 100755
--- a/res/values/qcomstrings.xml
+++ b/res/values/qcomstrings.xml
@@ -984,6 +984,14 @@
     <string name="bestpicture_done">DONE</string>
     <string name="bestpicture_at_least_one_picture">At least one picture has to be chosen.</string>
 
+    <string name="pref_camera2_shutter_animation_default" translatable="false">on</string>
+    <string name="pref_camera2_shutter_animation_value_on" translatable="false">on</string>
+    <string name="pref_camera2_shutter_animation_value_off" translatable="false">off</string>
+
+    <string name="pref_camera2_shutter_animation_entry_on">On</string>
+    <string name="pref_camera2_shutter_animation_entry_off">Off</string>
+    <string name="pref_camera2_shutter_animation_title">Shutter animation</string>
+
     <string name="pref_camera2_shutter_sound_default" translatable="false">on</string>
     <string name="pref_camera2_shutter_sound_value_on" translatable="false">on</string>
     <string name="pref_camera2_shutter_sound_value_off" translatable="false">off</string>
diff --git a/res/xml/capture_preferences.xml b/res/xml/capture_preferences.xml
index 5c47e7b..89b12cd 100755
--- a/res/xml/capture_preferences.xml
+++ b/res/xml/capture_preferences.xml
@@ -294,6 +294,13 @@
         camera:title="@string/pref_selfie_flash_title" />
 
     <ListPreference
+        camera:defaultValue="@string/pref_camera2_shutter_animation_default"
+        camera:entries="@array/pref_camera2_shutter_animation_entries"
+        camera:entryValues="@array/pref_camera2_shutter_animation_entryvalues"
+        camera:key="pref_camera2_shutter_animation_key"
+        camera:title="@string/pref_camera2_shutter_animation_title" />
+
+    <ListPreference
         camera:defaultValue="@string/pref_camera2_shutter_sound_default"
         camera:entries="@array/pref_camera2_shutter_sound_entries"
         camera:entryValues="@array/pref_camera2_shutter_sound_entryvalues"
diff --git a/res/xml/setting_menu_preferences.xml b/res/xml/setting_menu_preferences.xml
index de286ff..f392936 100755
--- a/res/xml/setting_menu_preferences.xml
+++ b/res/xml/setting_menu_preferences.xml
@@ -122,6 +122,12 @@
 
         <SwitchPreference
             android:defaultValue="true"
+            android:icon="@drawable/ic_custom_shutter_animation"
+            android:key="pref_camera2_shutter_animation_key"
+            android:title="@string/pref_camera2_shutter_animation_title" />
+
+        <SwitchPreference
+            android:defaultValue="true"
             android:icon="@drawable/ic_custom_shutter_sound"
             android:key="pref_camera2_shutter_sound_key"
             android:title="@string/pref_camera2_shutter_sound_title" />
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index 787f24e..077eee8 100755
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -2070,7 +2070,7 @@
 
     private boolean takeZSLPicture(int cameraId) {
         if(mPostProcessor.isZSLEnabled() && mPostProcessor.takeZSLPicture()) {
-            checkAndPlayShutterSound(getMainCameraId());
+            checkAndPlayShutterEffects(getMainCameraId());
             mUI.enableShutter(true);
             return true;
         }
@@ -2435,7 +2435,7 @@
         VendorTagUtil.setCdsMode(captureBuilder, 2); // CDS 0-OFF, 1-ON, 2-AUTO
         applySettingsForCapture(captureBuilder, id);
         applySettingsForLockExposure(captureBuilder, id);
-        checkAndPlayShutterSound(id);
+        checkAndPlayShutterEffects(id);
         if(mPaused || !mCamerasOpened) {
             //for avoid occurring crash when click back before capture finished.
             //CameraDevice was already closed
@@ -2447,7 +2447,7 @@
 
     private void captureStillPictureForFilter(CaptureRequest.Builder captureBuilder, int id) throws CameraAccessException{
         applySettingsForLockExposure(captureBuilder, id);
-        checkAndPlayShutterSound(id);
+        checkAndPlayShutterEffects(id);
         if(mPaused || !mCamerasOpened) {
             //for avoid occurring crash when click back before capture finished.
             //CameraDevice was already closed
@@ -2480,7 +2480,7 @@
                 }
                 Log.d(TAG, "captureStillPictureForLongshot onCaptureCompleted: " + mNumFramesArrived.get() + " " + mShotNum);
                 if (mLongshotActive) {
-                    checkAndPlayShutterSound(getMainCameraId());
+                    checkAndPlayShutterEffects(getMainCameraId());
                 }
                 mLongshoting = false;
             }
@@ -2567,7 +2567,7 @@
     }
 
     private void captureStillPictureForCommon(CaptureRequest.Builder captureBuilder, int id) throws CameraAccessException{
-        checkAndPlayShutterSound(id);
+        checkAndPlayShutterEffects(id);
         if(mLongshoting) mLongshoting = false;
         if(isMpoOn()) {
             mCaptureStartTime = System.currentTimeMillis();
@@ -2640,7 +2640,7 @@
                 warningToast("Camera is not ready yet to take a video snapshot.");
                 return;
             }
-            checkAndPlayShutterSound(id);
+            checkAndPlayShutterEffects(id);
             CaptureRequest.Builder captureBuilder =
                     mCameraDevice[id].createCaptureRequest(CameraDevice.TEMPLATE_VIDEO_SNAPSHOT);
 
@@ -6803,12 +6803,17 @@
         }
     }
 
-    public void checkAndPlayShutterSound(int id) {
+    public void checkAndPlayShutterEffects(int id) {
         if (id == getMainCameraId()) {
             String value = mSettingsManager.getValue(SettingsManager.KEY_SHUTTER_SOUND);
-            if (value != null && value.equals("on") && mSoundPlayer != null) {
+            if (mSoundPlayer != null && "on".equals(value)) {
                 mSoundPlayer.play(SoundClips.SHUTTER_CLICK);
             }
+
+            value = mSettingsManager.getValue(SettingsManager.KEY_SHUTTER_ANIMATION);
+            if ("on".equals(value)) {
+                mUI.triggerShutterEffect();
+            }
         }
     }
 
diff --git a/src/com/android/camera/CaptureUI.java b/src/com/android/camera/CaptureUI.java
index 9cef708..611217b 100755
--- a/src/com/android/camera/CaptureUI.java
+++ b/src/com/android/camera/CaptureUI.java
@@ -20,6 +20,9 @@
 package com.android.camera;
 
 import android.animation.Animator;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
 import android.app.Activity;
 import android.app.AlertDialog;
 import android.content.Context;
@@ -254,6 +257,32 @@
         }
     }
 
+    public void triggerShutterEffect() {
+        final AnimatorSet shutterSet = new AnimatorSet();
+
+        final ObjectAnimator showAnimator = ObjectAnimator.ofFloat(mPreviewCover, View.ALPHA, 0.0f, 1.0f);
+        showAnimator.setDuration(150L);
+        showAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationStart(Animator animation) {
+                super.onAnimationStart(animation);
+                showPreviewCover();
+            }
+        });
+
+        final ObjectAnimator hideAnimator = ObjectAnimator.ofFloat(mPreviewCover, View.ALPHA, 1.0f, 0.0f);
+        hideAnimator.setDuration(100L);
+        hideAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override public void onAnimationEnd(Animator animation) {
+                super.onAnimationEnd(animation);
+                hidePreviewCover();
+            }
+        });
+
+        shutterSet.playSequentially(showAnimator, hideAnimator);
+        mPreviewCover.post(shutterSet::start);
+    }
+
     public void initThumbnail() {
         if (mThumbnail == null)
             mThumbnail = (ImageView) mRootView.findViewById(R.id.preview_thumb);
diff --git a/src/com/android/camera/SettingsManager.java b/src/com/android/camera/SettingsManager.java
index a2a7f10..d00de3d 100755
--- a/src/com/android/camera/SettingsManager.java
+++ b/src/com/android/camera/SettingsManager.java
@@ -168,6 +168,7 @@
     public static final String KEY_FACE_DETECTION = "pref_camera2_facedetection_key";
     public static final String KEY_VIDEO_HIGH_FRAME_RATE = "pref_camera2_hfr_key";
     public static final String KEY_SELFIE_FLASH = "pref_selfie_flash_key";
+    public static final String KEY_SHUTTER_ANIMATION = "pref_camera2_shutter_animation_key";
     public static final String KEY_SHUTTER_SOUND = "pref_camera2_shutter_sound_key";
     public static final String KEY_DEVELOPER_MENU = "pref_camera2_developer_menu_key";
     public static final String KEY_RESTORE_DEFAULT = "pref_camera2_restore_default_key";
diff --git a/src/com/android/camera/imageprocessor/PostProcessor.java b/src/com/android/camera/imageprocessor/PostProcessor.java
index 6d66e33..e7de95c 100755
--- a/src/com/android/camera/imageprocessor/PostProcessor.java
+++ b/src/com/android/camera/imageprocessor/PostProcessor.java
@@ -527,7 +527,7 @@
 
     private void reprocessImage(Image image, TotalCaptureResult metadata) {
         if(mController.isLongShotActive()) {
-            mController.checkAndPlayShutterSound(mController.getMainCameraId());
+            mController.checkAndPlayShutterEffects(mController.getMainCameraId());
         }
         synchronized (lock) {
             if(mCameraDevice == null || mCaptureSession == null || mImageReader == null) {
@@ -1107,7 +1107,7 @@
                                         mActivity.getContentResolver(), "jpeg");
                             }
                             if (mFilterIndex == FILTER_UBIFOCUS && numImage > 0) {
-                                mController.checkAndPlayShutterSound(mController.getMainCameraId());
+                                mController.checkAndPlayShutterEffects(mController.getMainCameraId());
                             }
                             mFilter.addImage(yBuf, vuBuf, numImage, null);
                             mImages[numImage] = image;