OSDN Git Service

Add back countdown timer
authorDoris Liu <tianliu@google.com>
Tue, 6 May 2014 01:43:26 +0000 (18:43 -0700)
committerDoris Liu <tianliu@google.com>
Mon, 19 May 2014 22:24:12 +0000 (15:24 -0700)
Bug: 14222651
Change-Id: I482ec942dd9d79c4ea0432140a96ba2fc5b29c57

40 files changed:
res/anim/count_down_exit.xml [deleted file]
res/drawable-hdpi/ic_timer_10s_indicator.png [new file with mode: 0644]
res/drawable-hdpi/ic_timer_10s_normal.png [new file with mode: 0644]
res/drawable-hdpi/ic_timer_3s_indicator.png [new file with mode: 0644]
res/drawable-hdpi/ic_timer_3s_normal.png [new file with mode: 0644]
res/drawable-hdpi/ic_timer_off_indicator.png [new file with mode: 0644]
res/drawable-hdpi/ic_timer_off_normal.png [new file with mode: 0644]
res/drawable-mdpi/ic_timer_10s_indicator.png [new file with mode: 0644]
res/drawable-mdpi/ic_timer_10s_normal.png [new file with mode: 0644]
res/drawable-mdpi/ic_timer_3s_indicator.png [new file with mode: 0644]
res/drawable-mdpi/ic_timer_3s_normal.png [new file with mode: 0644]
res/drawable-mdpi/ic_timer_off_indicator.png [new file with mode: 0644]
res/drawable-mdpi/ic_timer_off_normal.png [new file with mode: 0644]
res/drawable-xhdpi/ic_timer_10s_indicator.png [new file with mode: 0644]
res/drawable-xhdpi/ic_timer_10s_normal.png [new file with mode: 0644]
res/drawable-xhdpi/ic_timer_3s_indicator.png [new file with mode: 0644]
res/drawable-xhdpi/ic_timer_3s_normal.png [new file with mode: 0644]
res/drawable-xhdpi/ic_timer_off_indicator.png [new file with mode: 0644]
res/drawable-xhdpi/ic_timer_off_normal.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_timer_10s_indicator.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_timer_10s_normal.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_timer_3s_indicator.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_timer_3s_normal.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_timer_off_indicator.png [new file with mode: 0644]
res/drawable-xxhdpi/ic_timer_off_normal.png [new file with mode: 0644]
res/layout-land/indicators.xml
res/layout-land/mode_options.xml
res/layout-port/indicators.xml
res/layout-port/mode_options.xml
res/layout/photo_module.xml
res/values/arrays.xml
src/com/android/camera/ButtonManager.java
src/com/android/camera/PhotoModule.java
src/com/android/camera/PhotoUI.java
src/com/android/camera/app/CameraAppUI.java
src/com/android/camera/settings/SettingsCache.java
src/com/android/camera/settings/SettingsManager.java
src/com/android/camera/ui/BottomBar.java
src/com/android/camera/ui/CountDownView.java [new file with mode: 0644]
src/com/android/camera/widget/IndicatorIconController.java

diff --git a/res/anim/count_down_exit.xml b/res/anim/count_down_exit.xml
deleted file mode 100644 (file)
index 0091c5b..0000000
+++ /dev/null
@@ -1,29 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (c) 2013, The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android">
-        <alpha
-            android:fromAlpha="1.0"
-            android:toAlpha="0.0"
-            android:duration="1000" />
-        <scale
-            android:fromXScale="1.0"
-            android:fromYScale="1.0"
-            android:toXScale="3.0"
-            android:toYScale="3.0"
-            android:pivotX="50%"
-            android:pivotY="50%"
-            android:duration="800" />
-</set>
\ No newline at end of file
diff --git a/res/drawable-hdpi/ic_timer_10s_indicator.png b/res/drawable-hdpi/ic_timer_10s_indicator.png
new file mode 100644 (file)
index 0000000..8bde7de
Binary files /dev/null and b/res/drawable-hdpi/ic_timer_10s_indicator.png differ
diff --git a/res/drawable-hdpi/ic_timer_10s_normal.png b/res/drawable-hdpi/ic_timer_10s_normal.png
new file mode 100644 (file)
index 0000000..8478ac8
Binary files /dev/null and b/res/drawable-hdpi/ic_timer_10s_normal.png differ
diff --git a/res/drawable-hdpi/ic_timer_3s_indicator.png b/res/drawable-hdpi/ic_timer_3s_indicator.png
new file mode 100644 (file)
index 0000000..f367d9a
Binary files /dev/null and b/res/drawable-hdpi/ic_timer_3s_indicator.png differ
diff --git a/res/drawable-hdpi/ic_timer_3s_normal.png b/res/drawable-hdpi/ic_timer_3s_normal.png
new file mode 100644 (file)
index 0000000..e4b696d
Binary files /dev/null and b/res/drawable-hdpi/ic_timer_3s_normal.png differ
diff --git a/res/drawable-hdpi/ic_timer_off_indicator.png b/res/drawable-hdpi/ic_timer_off_indicator.png
new file mode 100644 (file)
index 0000000..e3f08f6
Binary files /dev/null and b/res/drawable-hdpi/ic_timer_off_indicator.png differ
diff --git a/res/drawable-hdpi/ic_timer_off_normal.png b/res/drawable-hdpi/ic_timer_off_normal.png
new file mode 100644 (file)
index 0000000..9f17862
Binary files /dev/null and b/res/drawable-hdpi/ic_timer_off_normal.png differ
diff --git a/res/drawable-mdpi/ic_timer_10s_indicator.png b/res/drawable-mdpi/ic_timer_10s_indicator.png
new file mode 100644 (file)
index 0000000..2bbf6f3
Binary files /dev/null and b/res/drawable-mdpi/ic_timer_10s_indicator.png differ
diff --git a/res/drawable-mdpi/ic_timer_10s_normal.png b/res/drawable-mdpi/ic_timer_10s_normal.png
new file mode 100644 (file)
index 0000000..3d06ee4
Binary files /dev/null and b/res/drawable-mdpi/ic_timer_10s_normal.png differ
diff --git a/res/drawable-mdpi/ic_timer_3s_indicator.png b/res/drawable-mdpi/ic_timer_3s_indicator.png
new file mode 100644 (file)
index 0000000..db5c4ce
Binary files /dev/null and b/res/drawable-mdpi/ic_timer_3s_indicator.png differ
diff --git a/res/drawable-mdpi/ic_timer_3s_normal.png b/res/drawable-mdpi/ic_timer_3s_normal.png
new file mode 100644 (file)
index 0000000..d9e083e
Binary files /dev/null and b/res/drawable-mdpi/ic_timer_3s_normal.png differ
diff --git a/res/drawable-mdpi/ic_timer_off_indicator.png b/res/drawable-mdpi/ic_timer_off_indicator.png
new file mode 100644 (file)
index 0000000..9780cc5
Binary files /dev/null and b/res/drawable-mdpi/ic_timer_off_indicator.png differ
diff --git a/res/drawable-mdpi/ic_timer_off_normal.png b/res/drawable-mdpi/ic_timer_off_normal.png
new file mode 100644 (file)
index 0000000..ad66d90
Binary files /dev/null and b/res/drawable-mdpi/ic_timer_off_normal.png differ
diff --git a/res/drawable-xhdpi/ic_timer_10s_indicator.png b/res/drawable-xhdpi/ic_timer_10s_indicator.png
new file mode 100644 (file)
index 0000000..0040e4b
Binary files /dev/null and b/res/drawable-xhdpi/ic_timer_10s_indicator.png differ
diff --git a/res/drawable-xhdpi/ic_timer_10s_normal.png b/res/drawable-xhdpi/ic_timer_10s_normal.png
new file mode 100644 (file)
index 0000000..d8104e0
Binary files /dev/null and b/res/drawable-xhdpi/ic_timer_10s_normal.png differ
diff --git a/res/drawable-xhdpi/ic_timer_3s_indicator.png b/res/drawable-xhdpi/ic_timer_3s_indicator.png
new file mode 100644 (file)
index 0000000..745c09f
Binary files /dev/null and b/res/drawable-xhdpi/ic_timer_3s_indicator.png differ
diff --git a/res/drawable-xhdpi/ic_timer_3s_normal.png b/res/drawable-xhdpi/ic_timer_3s_normal.png
new file mode 100644 (file)
index 0000000..c3874ac
Binary files /dev/null and b/res/drawable-xhdpi/ic_timer_3s_normal.png differ
diff --git a/res/drawable-xhdpi/ic_timer_off_indicator.png b/res/drawable-xhdpi/ic_timer_off_indicator.png
new file mode 100644 (file)
index 0000000..7e6272d
Binary files /dev/null and b/res/drawable-xhdpi/ic_timer_off_indicator.png differ
diff --git a/res/drawable-xhdpi/ic_timer_off_normal.png b/res/drawable-xhdpi/ic_timer_off_normal.png
new file mode 100644 (file)
index 0000000..b6554f1
Binary files /dev/null and b/res/drawable-xhdpi/ic_timer_off_normal.png differ
diff --git a/res/drawable-xxhdpi/ic_timer_10s_indicator.png b/res/drawable-xxhdpi/ic_timer_10s_indicator.png
new file mode 100644 (file)
index 0000000..3994565
Binary files /dev/null and b/res/drawable-xxhdpi/ic_timer_10s_indicator.png differ
diff --git a/res/drawable-xxhdpi/ic_timer_10s_normal.png b/res/drawable-xxhdpi/ic_timer_10s_normal.png
new file mode 100644 (file)
index 0000000..61fffec
Binary files /dev/null and b/res/drawable-xxhdpi/ic_timer_10s_normal.png differ
diff --git a/res/drawable-xxhdpi/ic_timer_3s_indicator.png b/res/drawable-xxhdpi/ic_timer_3s_indicator.png
new file mode 100644 (file)
index 0000000..7b0c5d3
Binary files /dev/null and b/res/drawable-xxhdpi/ic_timer_3s_indicator.png differ
diff --git a/res/drawable-xxhdpi/ic_timer_3s_normal.png b/res/drawable-xxhdpi/ic_timer_3s_normal.png
new file mode 100644 (file)
index 0000000..a4a7bf9
Binary files /dev/null and b/res/drawable-xxhdpi/ic_timer_3s_normal.png differ
diff --git a/res/drawable-xxhdpi/ic_timer_off_indicator.png b/res/drawable-xxhdpi/ic_timer_off_indicator.png
new file mode 100644 (file)
index 0000000..21acf8c
Binary files /dev/null and b/res/drawable-xxhdpi/ic_timer_off_indicator.png differ
diff --git a/res/drawable-xxhdpi/ic_timer_off_normal.png b/res/drawable-xxhdpi/ic_timer_off_normal.png
new file mode 100644 (file)
index 0000000..935b7ce
Binary files /dev/null and b/res/drawable-xxhdpi/ic_timer_off_normal.png differ
index e2632fa..fa83e8f 100644 (file)
@@ -48,6 +48,9 @@
         android:id="@+id/hdr_indicator"
         style="@style/IndicatorIcon" />
     <ImageView
+        android:id="@+id/countdown_timer_indicator"
+        style="@style/IndicatorIcon" />
+    <ImageView
         android:id="@+id/flash_indicator"
         style="@style/IndicatorIcon" />
     <ImageView
index d2ffcde..d8a69ce 100644 (file)
             android:background="@null"
             android:src="@drawable/ic_exposure"
             android:contentDescription="@string/manual_exposure_compensation_desc" />
+        <com.android.camera.MultiToggleImageButton
+            android:id="@+id/countdown_toggle_button"
+            style="@style/ModeOption"
+            camera:imageIds="@array/countdown_duration_icons"
+            camera:contentDescriptionIds="@array/countdown_duration_descriptions" />
     </com.android.camera.ui.TopRightWeightedLayout>
 </com.android.camera.widget.ModeOptions>
index 68e35fd..d98c591 100644 (file)
@@ -48,6 +48,9 @@
         android:id="@+id/hdr_indicator"
         style="@style/IndicatorIcon" />
     <ImageView
+        android:id="@+id/countdown_timer_indicator"
+        style="@style/IndicatorIcon" />
+    <ImageView
         android:id="@+id/flash_indicator"
         style="@style/IndicatorIcon" />
     <ImageView
index 4d2dd99..566b8ed 100644 (file)
             style="@style/ModeOption"
             camera:imageIds="@array/camera_id_icons"
             camera:contentDescriptionIds="@array/camera_id_descriptions" />
+        <com.android.camera.MultiToggleImageButton
+            android:id="@+id/countdown_toggle_button"
+            style="@style/ModeOption"
+            camera:imageIds="@array/countdown_duration_icons"
+            camera:contentDescriptionIds="@array/countdown_duration_descriptions" />
     </com.android.camera.ui.TopRightWeightedLayout>
 </com.android.camera.widget.ModeOptions>
index 2cb002f..e62d551 100644 (file)
         android:clickable="true"
         android:background="@android:color/black"
         android:scaleType="fitCenter"/>
+    <com.android.camera.ui.CountDownView
+         android:id="@+id/count_down_view"
+         android:visibility="invisible"
+         android:layout_width="match_parent"
+         android:layout_height="match_parent" >
+        <TextView android:id="@+id/remaining_seconds"
+              android:layout_width="200dp"
+              android:layout_height="200dp"
+              android:textSize="160dp"
+              android:textColor="@android:color/white"
+              android:layout_gravity="top|left"
+              android:gravity="center"/>
+    </com.android.camera.ui.CountDownView>
+
 </FrameLayout>
index ed7e199..9b30aff 100644 (file)
       <item>@string/grid_lines_on_desc</item>
     </array>
 
+    <array name="countdown_duration_descriptions" translatable="false">
+        <item>@string/countdown_timer_off</item>
+        <item>@string/countdown_timer_duration_3s</item>
+        <item>@string/countdown_timer_duration_10s</item>
+    </array>
+
     <string-array name="pref_camera_pano_orientation_entryvalues">
       <item>@string/pano_orientation_horizontal</item>
       <item>@string/pano_orientation_vertical</item>
       <item>@string/settings_close_desc</item>
     </array>
 
+    <array name="countdown_duration_icons" translatable="false">
+        <item>@drawable/ic_timer_off_normal</item>
+        <item>@drawable/ic_timer_3s_normal</item>
+        <item>@drawable/ic_timer_10s_normal</item>
+    </array>
+
+    <array name="pref_camera_countdown_indicators" translatable="false">
+        <item>@drawable/ic_timer_off_indicator</item>
+        <item>@drawable/ic_timer_3s_indicator</item>
+        <item>@drawable/ic_timer_10s_indicator</item>
+    </array>
+
+    <string-array name="pref_countdown_duration" translatable="false">
+        <item>0</item>
+        <item>3</item>
+        <item>10</item>
+    </string-array>
+
 
     <!--Index of camera modes. -->
     <integer name="camera_mode_photo">0</integer>
index c5a6f2e..a67e42b 100644 (file)
@@ -53,6 +53,7 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
     public static final int BUTTON_REVIEW = 9;
     public static final int BUTTON_GRID_LINES = 11;
     public static final int BUTTON_EXPOSURE_COMPENSATION = 12;
+    public static final int BUTTON_COUNTDOWN = 13;
 
     /** For two state MultiToggleImageButtons, the off index. */
     public static final int OFF = 0;
@@ -67,6 +68,7 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
     private MultiToggleImageButton mButtonFlash;
     private MultiToggleImageButton mButtonHdr;
     private MultiToggleImageButton mButtonGridlines;
+    private MultiToggleImageButton mButtonCountdown;
 
     /** Intent UI buttons. */
     private ImageButton mButtonCancel;
@@ -172,6 +174,8 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
         mModeOptionsPano = (RadioOptions) root.findViewById(R.id.mode_options_pano);
         mModeOptionsButtons = root.findViewById(R.id.mode_options_buttons);
         mModeOptions = (ModeOptions) root.findViewById(R.id.mode_options);
+
+        mButtonCountdown = (MultiToggleImageButton) root.findViewById(R.id.countdown_toggle_button);
     }
 
     @Override
@@ -218,6 +222,11 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
                 updateExposureButtons();
                 break;
             }
+            case SettingsManager.SETTING_COUNTDOWN_DURATION: {
+                index = mSettingsManager.getStringValueIndex(id);
+                button = getButtonOrError(BUTTON_COUNTDOWN);
+                break;
+            }
             default: {
                 // Do nothing.
             }
@@ -277,6 +286,11 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
                     throw new IllegalStateException("Grid lines button could not be found.");
                 }
                 return mButtonGridlines;
+            case BUTTON_COUNTDOWN:
+                if (mButtonCountdown == null) {
+                    throw new IllegalStateException("Countdown button could not be found.");
+                }
+                return mButtonCountdown;
             default:
                 throw new IllegalArgumentException("button not known by id=" + buttonId);
         }
@@ -345,6 +359,9 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
             case BUTTON_GRID_LINES:
                 initializeGridLinesButton(button, cb, R.array.grid_lines_icons);
                 break;
+            case BUTTON_COUNTDOWN:
+                initializeCountdownButton(button, cb, R.array.countdown_duration_icons);
+                break;
             default:
                 throw new IllegalArgumentException("button not known by id=" + buttonId);
         }
@@ -690,6 +707,31 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
     }
 
     /**
+     * Initialize a countdown timer button.
+     */
+    private void initializeCountdownButton(MultiToggleImageButton button,
+            final ButtonCallback cb, int resIdImages) {
+        if (resIdImages > 0) {
+            button.overrideImageIds(resIdImages);
+        }
+
+        int index = mSettingsManager.getStringValueIndex(
+                SettingsManager.SETTING_COUNTDOWN_DURATION);
+        button.setState(index >= 0 ? index : 0, false);
+
+        button.setOnStateChangeListener(new MultiToggleImageButton.OnStateChangeListener() {
+            @Override
+            public void stateChanged(View view, int state) {
+                mSettingsManager.setStringValueIndex(
+                        SettingsManager.SETTING_COUNTDOWN_DURATION, state);
+                if(cb != null) {
+                    cb.onStateChanged(state);
+                }
+            }
+        });
+    }
+
+    /**
      * Update the visual state of the manual exposure buttons
      */
     public void updateExposureButtons() {
index 33cb36e..9bce0ce 100644 (file)
@@ -32,7 +32,9 @@ import android.hardware.SensorEvent;
 import android.hardware.SensorEventListener;
 import android.hardware.SensorManager;
 import android.location.Location;
+import android.media.AudioManager;
 import android.media.CameraProfile;
+import android.media.SoundPool;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -73,6 +75,7 @@ import com.android.camera.remote.RemoteCameraModule;
 import com.android.camera.settings.ResolutionUtil;
 import com.android.camera.settings.SettingsManager;
 import com.android.camera.settings.SettingsUtil;
+import com.android.camera.ui.CountDownView;
 import com.android.camera.util.ApiHelper;
 import com.android.camera.util.CameraUtil;
 import com.android.camera.util.GcamHelper;
@@ -102,7 +105,8 @@ public class PhotoModule
         FocusOverlayManager.Listener,
         SensorEventListener,
         SettingsManager.OnSettingChangedListener,
-        RemoteCameraModule {
+        RemoteCameraModule,
+        CountDownView.OnCountDownStatusListener {
 
     private static final Log.Tag TAG = new Log.Tag("PhotoModule");
 
@@ -243,6 +247,7 @@ public class PhotoModule
     private FocusOverlayManager mFocusManager;
 
     private final int mGcamModeIndex;
+    private final CountdownSoundPlayer mCountdownSoundPlayer = new CountdownSoundPlayer();
 
     private String mSceneMode;
 
@@ -432,6 +437,25 @@ public class PhotoModule
         mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
         mLocationManager = mActivity.getLocationManager();
         mSensorManager = (SensorManager) (mActivity.getSystemService(Context.SENSOR_SERVICE));
+        mUI.setCountdownFinishedListener(this);
+
+        // TODO: Make this a part of app controller API.
+        View cancelButton = mActivity.findViewById(R.id.shutter_cancel_button);
+        cancelButton.setOnClickListener(new View.OnClickListener() {
+            @Override
+            public void onClick(View view) {
+                cancelCountDown();
+            }
+        });
+    }
+
+    private void cancelCountDown() {
+        if (mUI.isCountingDown()) {
+            // Cancel on-going countdown.
+            mUI.cancelCountDown();
+        }
+        mAppController.getCameraAppUI().transitionToCapture();
+        mAppController.getCameraAppUI().showModeOptions();
     }
 
     @Override
@@ -618,6 +642,7 @@ public class PhotoModule
         if (mPaused) {
             return;
         }
+        cancelCountDown();
         SettingsManager settingsManager = mActivity.getSettingsManager();
 
         Log.i(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
@@ -751,6 +776,8 @@ public class PhotoModule
                 mCameraCapabilities.getExposureCompensationStep();
         }
 
+        bottomBarSpec.enableSelfTimer = true;
+
         if (isImageCaptureIntent()) {
             bottomBarSpec.showCancel = true;
             bottomBarSpec.cancelCallback = mCancelCallback;
@@ -1437,6 +1464,20 @@ public class PhotoModule
         }
         Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
 
+        int countDownDuration = Integer.parseInt(mActivity.getSettingsManager()
+                .get(SettingsManager.SETTING_COUNTDOWN_DURATION));
+        if (countDownDuration > 0) {
+            // Start count down.
+            mAppController.getCameraAppUI().transitionToCancel();
+            mAppController.getCameraAppUI().hideModeOptions();
+            mUI.startCountdown(countDownDuration);
+            return;
+        } else {
+            focusAndCapture();
+        }
+    }
+
+    private void focusAndCapture() {
         if (mSceneMode == CameraUtil.SCENE_MODE_HDR) {
             mUI.setSwipingEnabled(false);
         }
@@ -1456,6 +1497,21 @@ public class PhotoModule
         mFocusManager.focusAndCapture();
     }
 
+    @Override
+    public void onRemainingSecondsChanged(int remainingSeconds) {
+        mCountdownSoundPlayer.onRemainingSecondsChanged(remainingSeconds);
+    }
+
+    @Override
+    public void onCountDownFinished() {
+        mAppController.getCameraAppUI().transitionToCapture();
+        mAppController.getCameraAppUI().showModeOptions();
+        if (mPaused) {
+            return;
+        }
+        focusAndCapture();
+    }
+
     private void onResumeTasks() {
         if (mPaused) {
             return;
@@ -1526,6 +1582,7 @@ public class PhotoModule
     @Override
     public void resume() {
         mPaused = false;
+        mCountdownSoundPlayer.loadSounds();
         if (mFocusManager != null) {
             // If camera is not open when resume is called, focus manager will
             // not
@@ -1533,11 +1590,7 @@ public class PhotoModule
             // preview area size change later in the initialization.
             mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
         }
-
-        if (mUI.getPreviewAreaSizeChangedListener() != null) {
-            mAppController.addPreviewAreaSizeChangedListener(
-                    mUI.getPreviewAreaSizeChangedListener());
-        }
+        mAppController.addPreviewAreaSizeChangedListener(mUI);
 
         // Add delay on resume from lock screen only, in order to to speed up
         // the onResume --> onPause --> onResume cycle from lock screen.
@@ -1584,9 +1637,10 @@ public class PhotoModule
         // and startPreview hasn't been called, then this is a no-op.
         // (e.g. onResume -> onPause -> onResume).
         stopPreview();
+        cancelCountDown();
+        mCountdownSoundPlayer.release();
 
         mNamedImages = null;
-
         // If we are in an image capture intent and has taken
         // a picture, we just clear it in onPause.
         mJpegImageData = null;
@@ -1604,10 +1658,7 @@ public class PhotoModule
         }
         getServices().getMemoryManager().removeListener(this);
         mAppController.removePreviewAreaSizeChangedListener(mFocusManager);
-        if (mUI.getPreviewAreaSizeChangedListener() != null) {
-            mAppController.removePreviewAreaSizeChangedListener(
-                    mUI.getPreviewAreaSizeChangedListener());
-        }
+        mAppController.removePreviewAreaSizeChangedListener(mUI);
 
         SettingsManager settingsManager = mActivity.getSettingsManager();
         settingsManager.removeListener(this);
@@ -2258,4 +2309,38 @@ public class PhotoModule
     public void onRemoteShutterPress() {
         capture();
     }
+
+    /**
+     * This class manages the loading/releasing/playing of the sounds needed for
+     * countdown timer.
+     */
+    private class CountdownSoundPlayer {
+        private SoundPool mSoundPool;
+        private int mBeepOnce;
+        private int mBeepTwice;
+
+        void loadSounds() {
+            // Load the beeps.
+            mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
+            mBeepOnce = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_once, 1);
+            mBeepTwice = mSoundPool.load(mAppController.getAndroidContext(), R.raw.beep_twice, 1);
+        }
+
+        void onRemainingSecondsChanged(int newVal) {
+            if (mSoundPool == null) {
+                Log.e(TAG, "Cannot play sound - they have not been loaded.");
+                return;
+            }
+            if (newVal == 1) {
+                mSoundPool.play(mBeepTwice, 1.0f, 1.0f, 0, 0, 1.0f);
+            } else if (newVal == 2 || newVal == 3) {
+                mSoundPool.play(mBeepOnce, 1.0f, 1.0f, 0, 0, 1.0f);
+            }
+        }
+
+        void release() {
+            mSoundPool.release();
+            mSoundPool = null;
+        }
+    }
 }
index f322568..f1981a6 100644 (file)
@@ -20,6 +20,7 @@ import android.app.Dialog;
 import android.content.DialogInterface;
 import android.graphics.Bitmap;
 import android.graphics.Matrix;
+import android.graphics.RectF;
 import android.graphics.SurfaceTexture;
 import android.hardware.Camera;
 import android.hardware.Camera.Face;
@@ -33,6 +34,7 @@ import android.view.ViewGroup;
 import com.android.camera.FocusOverlayManager.FocusUI;
 import com.android.camera.cameradevice.CameraManager;
 import com.android.camera.debug.Log;
+import com.android.camera.ui.CountDownView;
 import com.android.camera.ui.FaceView;
 import com.android.camera.ui.PreviewOverlay;
 import com.android.camera.ui.PreviewStatusListener;
@@ -45,7 +47,7 @@ import com.android.camera2.R;
 import java.util.List;
 
 public class PhotoUI implements PreviewStatusListener,
-    CameraManager.CameraFaceDetectionCallback {
+    CameraManager.CameraFaceDetectionCallback, PreviewStatusListener.PreviewAreaChangedListener {
 
     private static final Log.Tag TAG = new Log.Tag("PhotoUI");
     private static final int DOWN_SAMPLE_FACTOR = 4;
@@ -86,6 +88,7 @@ public class PhotoUI implements PreviewStatusListener,
         }
     };
     private Runnable mRunnableForNextFrame = null;
+    private CountDownView mCountdownView;
 
     @Override
     public GestureDetector.OnGestureListener getGestureListener() {
@@ -130,6 +133,44 @@ public class PhotoUI implements PreviewStatusListener,
         mRunnableForNextFrame = runnable;
     }
 
+    /**
+     * Starts the countdown timer.
+     *
+     * @param sec seconds to countdown
+     */
+    public void startCountdown(int sec) {
+        mCountdownView.startCountDown(sec);
+    }
+
+    /**
+     * Sets a listener that gets notified when the countdown is finished.
+     */
+    public void setCountdownFinishedListener(CountDownView.OnCountDownStatusListener listener) {
+        mCountdownView.setCountDownStatusListener(listener);
+    }
+
+    /**
+     * Returns whether the countdown is on-going.
+     */
+    public boolean isCountingDown() {
+        return mCountdownView.isCountingDown();
+    }
+
+    /**
+     * Cancels the on-going countdown, if any.
+     */
+    public void cancelCountDown() {
+        mCountdownView.cancelCountDown();
+    }
+
+    @Override
+    public void onPreviewAreaChanged(RectF previewArea) {
+        if (mFaceView != null) {
+            mFaceView.onPreviewAreaChanged(previewArea);
+        }
+        mCountdownView.onPreviewAreaChanged(previewArea);
+    }
+
     private class DecodeTask extends AsyncTask<Void, Void, Bitmap> {
         private final byte [] mData;
         private final int mOrientation;
@@ -184,6 +225,7 @@ public class PhotoUI implements PreviewStatusListener,
         initIndicators();
         mFocusUI = (FocusUI) mRootView.findViewById(R.id.focus_overlay);
         mPreviewOverlay = (PreviewOverlay) mRootView.findViewById(R.id.preview_overlay);
+        mCountdownView = (CountDownView) mRootView.findViewById(R.id.count_down_view);
     }
 
     public FocusUI getFocusUI() {
@@ -471,12 +513,4 @@ public class PhotoUI implements PreviewStatusListener,
         }
     }
 
-    /**
-     * Returns a {@link com.android.camera.ui.PreviewStatusListener.PreviewAreaChangedListener}
-     * that should be registered to listen to preview area change.
-     */
-    public PreviewAreaChangedListener getPreviewAreaSizeChangedListener() {
-        return mFaceView;
-    }
-
 }
index 19d1134..35236db 100644 (file)
@@ -441,6 +441,11 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
         public int minExposureCompensation;
         public int maxExposureCompensation;
         public float exposureCompensationStep;
+
+        /**
+         * Whether or not timer should show.
+         */
+        public boolean enableSelfTimer = false;
     }
 
 
@@ -1733,6 +1738,12 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
             }
         }
 
+        if (bottomBarSpec.enableSelfTimer) {
+            buttonManager.initializeButton(ButtonManager.BUTTON_COUNTDOWN, null);
+        } else {
+            buttonManager.hideButton(ButtonManager.BUTTON_COUNTDOWN);
+        }
+
         if (bottomBarSpec.enablePanoOrientation
                 && PhotoSphereHelper.getPanoramaOrientationOptionArrayId() > 0) {
             buttonManager.initializePanoOrientationButtons(bottomBarSpec.panoOrientationCallback);
index e89bbd0..bdd9253 100644 (file)
@@ -137,6 +137,8 @@ public class SettingsCache {
                 SettingsManager.SETTING_EXPOSURE_COMPENSATION_ENABLED);
         mKeyMap.put(SettingsManager.KEY_USER_SELECTED_ASPECT_RATIO,
                 SettingsManager.SETTING_USER_SELECTED_ASPECT_RATIO);
+        mKeyMap.put(SettingsManager.KEY_COUNTDOWN_DURATION,
+                SettingsManager.SETTING_COUNTDOWN_DURATION);
     }
 
     /**
@@ -225,6 +227,8 @@ public class SettingsCache {
                 return SettingsManager.getManualExposureCompensationSetting(mContext);
             case SettingsManager.SETTING_USER_SELECTED_ASPECT_RATIO:
                 return SettingsManager.getUserSelectedAspectRatioSetting(mContext);
+            case SettingsManager.SETTING_COUNTDOWN_DURATION:
+                return SettingsManager.getCountDownDurationSetting(mContext);
             default:
                 return mExtraSettings.settingFromId(id, mContext);
         }
index 288bc86..7e81274 100644 (file)
@@ -394,6 +394,7 @@ public class SettingsManager {
     public static final int SETTING_SHOULD_SHOW_REFOCUS_VIEWER_CLING = 31;
     public static final int SETTING_EXPOSURE_COMPENSATION_ENABLED = 32;
     public static final int SETTING_USER_SELECTED_ASPECT_RATIO = 33;
+    public static final int SETTING_COUNTDOWN_DURATION = 34;
 
     // Shared preference keys.
     public static final String KEY_RECORD_LOCATION = "pref_camera_recordlocation_key";
@@ -433,6 +434,7 @@ public class SettingsManager {
     public static final String KEY_EXPOSURE_COMPENSATION_ENABLED =
             "pref_camera_exposure_compensation_key";
     public static final String KEY_USER_SELECTED_ASPECT_RATIO = "pref_user_selected_aspect_ratio";
+    public static final String KEY_COUNTDOWN_DURATION = "pref_camera_countdown_duration_key";
 
     public static final int WHITE_BALANCE_DEFAULT_INDEX = 2;
 
@@ -968,6 +970,13 @@ public class SettingsManager {
                 KEY_EXPOSURE_COMPENSATION_ENABLED, values, FLUSH_OFF);
     }
 
+    public static Setting getCountDownDurationSetting(Context context) {
+        String defaultValue = Integer.toString(0);
+        String[] values = context.getResources().getStringArray(R.array.pref_countdown_duration);
+        return new Setting(SOURCE_DEFAULT, TYPE_STRING, defaultValue,
+                KEY_COUNTDOWN_DURATION, values, FLUSH_OFF);
+    }
+
     public static Setting getPictureSizeBackSetting(Context context) {
         String defaultValue = null;
         String[] values = null;
index 9561cf6..224093c 100644 (file)
@@ -88,7 +88,6 @@ public class BottomBar extends FrameLayout {
     private float mCenterX;
     private float mCenterY;
     private final RectF mRect = new RectF();
-    private final RectF mPreviewArea = new RectF();
     private CaptureLayoutHelper mCaptureLayoutHelper = null;
 
     public BottomBar(Context context, AttributeSet attrs) {
@@ -163,10 +162,10 @@ public class BottomBar extends FrameLayout {
                     mCancelLayout.setBackgroundColor(mBackgroundPressedColor);
                 } else if (MotionEvent.ACTION_UP == event.getActionMasked() ||
                         MotionEvent.ACTION_CANCEL == event.getActionMasked()) {
-                    mCancelLayout.setBackgroundColor(mBackgroundColor);
+                    mCancelLayout.setBackgroundColor(mCircleColor);
                 } else if (MotionEvent.ACTION_MOVE == event.getActionMasked()) {
                     if (!mRect.contains(event.getX(), event.getY())) {
-                        mCancelLayout.setBackgroundColor(mBackgroundColor);
+                        mCancelLayout.setBackgroundColor(mCircleColor);
                     }
                 }
                 return false;
@@ -205,6 +204,7 @@ public class BottomBar extends FrameLayout {
     public void transitionToCancel() {
         mCaptureLayout.setVisibility(View.INVISIBLE);
         mIntentReviewLayout.setVisibility(View.INVISIBLE);
+        mCancelLayout.setBackgroundColor(mCircleColor);
         mCancelLayout.setVisibility(View.VISIBLE);
         mMode = MODE_CANCEL;
     }
diff --git a/src/com/android/camera/ui/CountDownView.java b/src/com/android/camera/ui/CountDownView.java
new file mode 100644 (file)
index 0000000..8bedb63
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.camera.ui;
+
+import java.util.Locale;
+
+import android.content.Context;
+import android.graphics.RectF;
+import android.os.Handler;
+import android.os.Message;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import com.android.camera.debug.Log;
+import com.android.camera2.R;
+
+/**
+ * This class manages the looks of the countdown.
+ */
+public class CountDownView extends FrameLayout {
+
+    private static final Log.Tag TAG = new Log.Tag("CountDownView");
+    private static final int SET_TIMER_TEXT = 1;
+    private static final long ANIMATION_DURATION_MS = 800;
+    private TextView mRemainingSecondsView;
+    private int mRemainingSecs = 0;
+    private OnCountDownStatusListener mListener;
+    private final Handler mHandler = new MainHandler();
+    private final RectF mPreviewArea = new RectF();
+
+    /**
+     * Listener that gets notified when the countdown status has
+     * been updated (i.e. remaining seconds changed, or finished).
+     */
+    public interface OnCountDownStatusListener {
+        /**
+         * Gets notified when the remaining seconds for the countdown
+         * has changed.
+         *
+         * @param remainingSeconds seconds remained for countdown
+         */
+        public void onRemainingSecondsChanged(int remainingSeconds);
+
+        /**
+         * Gets called when countdown is finished.
+         */
+        public void onCountDownFinished();
+    }
+
+    public CountDownView(Context context, AttributeSet attrs) {
+        super(context, attrs);
+    }
+
+    /**
+     * Returns whether countdown is on-going.
+     */
+    public boolean isCountingDown() {
+        return mRemainingSecs > 0;
+    };
+
+    /**
+     * Responds to preview area change by centering th countdown UI in the new
+     * preview area.
+     */
+    public void onPreviewAreaChanged(RectF previewArea) {
+        mPreviewArea.set(previewArea);
+    }
+
+    private void remainingSecondsChanged(int newVal) {
+        mRemainingSecs = newVal;
+        if (mListener != null) {
+            mListener.onRemainingSecondsChanged(mRemainingSecs);
+        }
+
+        if (newVal == 0) {
+            // Countdown has finished.
+            setVisibility(View.INVISIBLE);
+            if (mListener != null) {
+                mListener.onCountDownFinished();
+            }
+        } else {
+            Locale locale = getResources().getConfiguration().locale;
+            String localizedValue = String.format(locale, "%d", newVal);
+            mRemainingSecondsView.setText(localizedValue);
+            // Fade-out animation.
+            startFadeOutAnimation();
+            // Schedule the next remainingSecondsChanged() call in 1 second
+            mHandler.sendEmptyMessageDelayed(SET_TIMER_TEXT, 1000);
+        }
+    }
+
+    private void startFadeOutAnimation() {
+        int textWidth = mRemainingSecondsView.getMeasuredWidth();
+        int textHeight = mRemainingSecondsView.getMeasuredHeight();
+        mRemainingSecondsView.setScaleX(1f);
+        mRemainingSecondsView.setScaleY(1f);
+        mRemainingSecondsView.setTranslationX(mPreviewArea.centerX() - textWidth / 2);
+        mRemainingSecondsView.setTranslationY(mPreviewArea.centerY() - textHeight / 2);
+        mRemainingSecondsView.setPivotX(textWidth / 2);
+        mRemainingSecondsView.setPivotY(textHeight / 2);
+        mRemainingSecondsView.setAlpha(1f);
+        float endScale = 2.5f;
+        mRemainingSecondsView.animate().scaleX(endScale).scaleY(endScale)
+                .alpha(0f).setDuration(ANIMATION_DURATION_MS).start();
+    }
+
+    @Override
+    protected void onFinishInflate() {
+        super.onFinishInflate();
+        mRemainingSecondsView = (TextView) findViewById(R.id.remaining_seconds);
+    }
+
+    /**
+     * Sets a listener that gets notified when the status of countdown has changed.
+     */
+    public void setCountDownStatusListener(OnCountDownStatusListener listener) {
+        mListener = listener;
+    }
+
+    /**
+     * Starts showing countdown in the UI.
+     *
+     * @param sec duration of the countdown, in seconds
+     */
+    public void startCountDown(int sec) {
+        if (sec <= 0) {
+            Log.w(TAG, "Invalid input for countdown timer: " + sec + " seconds");
+            return;
+        }
+        if (isCountingDown()) {
+            cancelCountDown();
+        }
+        setVisibility(View.VISIBLE);
+        remainingSecondsChanged(sec);
+    }
+
+    /**
+     * Cancels the on-going countdown in the UI, if any.
+     */
+    public void cancelCountDown() {
+        if (mRemainingSecs > 0) {
+            mRemainingSecs = 0;
+            mHandler.removeMessages(SET_TIMER_TEXT);
+            setVisibility(View.INVISIBLE);
+        }
+    }
+
+    private class MainHandler extends Handler {
+        @Override
+        public void handleMessage(Message message) {
+            if (message.what == SET_TIMER_TEXT) {
+                remainingSecondsChanged(mRemainingSecs -1);
+            }
+        }
+    }
+}
\ No newline at end of file
index 610d9c7..5392628 100644 (file)
@@ -45,6 +45,7 @@ public class IndicatorIconController
     private ImageView mFlashIndicator;
     private ImageView mHdrIndicator;
     private ImageView mPanoIndicator;
+    private ImageView mCountdownTimerIndicator;
 
     private ImageView mExposureIndicatorN2;
     private ImageView mExposureIndicatorN1;
@@ -56,6 +57,7 @@ public class IndicatorIconController
     private TypedArray mHdrPlusIndicatorIcons;
     private TypedArray mHdrIndicatorIcons;
     private TypedArray mPanoIndicatorIcons;
+    private TypedArray mCountdownTimerIndicatorIcons;
 
     private AppController mController;
 
@@ -82,6 +84,10 @@ public class IndicatorIconController
                 context.getResources().obtainTypedArray(panoIndicatorArrayId);
         }
 
+        mCountdownTimerIndicator = (ImageView) root.findViewById(R.id.countdown_timer_indicator);
+        mCountdownTimerIndicatorIcons = context.getResources().obtainTypedArray(
+                R.array.pref_camera_countdown_indicators);
+
         mExposureIndicatorN2 = (ImageView) root.findViewById(R.id.exposure_n2_indicator);
         mExposureIndicatorN1 = (ImageView) root.findViewById(R.id.exposure_n1_indicator);
         mExposureIndicatorP1 = (ImageView) root.findViewById(R.id.exposure_p1_indicator);
@@ -139,6 +145,7 @@ public class IndicatorIconController
         syncHdrIndicator();
         syncPanoIndicator();
         syncExposureIndicator();
+        syncCountdownTimerIndicator();
     }
 
     /**
@@ -264,6 +271,19 @@ public class IndicatorIconController
         }
     }
 
+    private void syncCountdownTimerIndicator() {
+        ButtonManager buttonManager = mController.getButtonManager();
+
+        if (buttonManager.isEnabled(ButtonManager.BUTTON_COUNTDOWN)
+                && buttonManager.isVisible(ButtonManager.BUTTON_COUNTDOWN)) {
+                setIndicatorState(mController.getSettingsManager(),
+                        SettingsManager.SETTING_COUNTDOWN_DURATION,
+                        mCountdownTimerIndicator, mCountdownTimerIndicatorIcons, false);
+        } else {
+            changeVisibility(mCountdownTimerIndicator, View.GONE);
+        }
+    }
+
     /**
      * Sets the image resource and visibility of the indicator
      * based on the indicator's corresponding setting state.
@@ -326,9 +346,13 @@ public class IndicatorIconController
                 syncExposureIndicator();
                 break;
             }
+            case SettingsManager.SETTING_COUNTDOWN_DURATION:
+                syncCountdownTimerIndicator();
+                break;
             default: {
                 // Do nothing.
             }
         }
     }
+
 }