diff options
40 files changed, 458 insertions, 52 deletions
diff --git a/res/anim/count_down_exit.xml b/res/anim/count_down_exit.xml deleted file mode 100644 index 0091c5bd7..000000000 --- a/res/anim/count_down_exit.xml +++ /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 Binary files differnew file mode 100644 index 000000000..8bde7dea5 --- /dev/null +++ b/res/drawable-hdpi/ic_timer_10s_indicator.png diff --git a/res/drawable-hdpi/ic_timer_10s_normal.png b/res/drawable-hdpi/ic_timer_10s_normal.png Binary files differnew file mode 100644 index 000000000..8478ac860 --- /dev/null +++ b/res/drawable-hdpi/ic_timer_10s_normal.png diff --git a/res/drawable-hdpi/ic_timer_3s_indicator.png b/res/drawable-hdpi/ic_timer_3s_indicator.png Binary files differnew file mode 100644 index 000000000..f367d9a00 --- /dev/null +++ b/res/drawable-hdpi/ic_timer_3s_indicator.png diff --git a/res/drawable-hdpi/ic_timer_3s_normal.png b/res/drawable-hdpi/ic_timer_3s_normal.png Binary files differnew file mode 100644 index 000000000..e4b696d02 --- /dev/null +++ b/res/drawable-hdpi/ic_timer_3s_normal.png diff --git a/res/drawable-hdpi/ic_timer_off_indicator.png b/res/drawable-hdpi/ic_timer_off_indicator.png Binary files differnew file mode 100644 index 000000000..e3f08f694 --- /dev/null +++ b/res/drawable-hdpi/ic_timer_off_indicator.png diff --git a/res/drawable-hdpi/ic_timer_off_normal.png b/res/drawable-hdpi/ic_timer_off_normal.png Binary files differnew file mode 100644 index 000000000..9f1786236 --- /dev/null +++ b/res/drawable-hdpi/ic_timer_off_normal.png diff --git a/res/drawable-mdpi/ic_timer_10s_indicator.png b/res/drawable-mdpi/ic_timer_10s_indicator.png Binary files differnew file mode 100644 index 000000000..2bbf6f3dc --- /dev/null +++ b/res/drawable-mdpi/ic_timer_10s_indicator.png diff --git a/res/drawable-mdpi/ic_timer_10s_normal.png b/res/drawable-mdpi/ic_timer_10s_normal.png Binary files differnew file mode 100644 index 000000000..3d06ee482 --- /dev/null +++ b/res/drawable-mdpi/ic_timer_10s_normal.png diff --git a/res/drawable-mdpi/ic_timer_3s_indicator.png b/res/drawable-mdpi/ic_timer_3s_indicator.png Binary files differnew file mode 100644 index 000000000..db5c4ce94 --- /dev/null +++ b/res/drawable-mdpi/ic_timer_3s_indicator.png diff --git a/res/drawable-mdpi/ic_timer_3s_normal.png b/res/drawable-mdpi/ic_timer_3s_normal.png Binary files differnew file mode 100644 index 000000000..d9e083e79 --- /dev/null +++ b/res/drawable-mdpi/ic_timer_3s_normal.png diff --git a/res/drawable-mdpi/ic_timer_off_indicator.png b/res/drawable-mdpi/ic_timer_off_indicator.png Binary files differnew file mode 100644 index 000000000..9780cc5bb --- /dev/null +++ b/res/drawable-mdpi/ic_timer_off_indicator.png diff --git a/res/drawable-mdpi/ic_timer_off_normal.png b/res/drawable-mdpi/ic_timer_off_normal.png Binary files differnew file mode 100644 index 000000000..ad66d9099 --- /dev/null +++ b/res/drawable-mdpi/ic_timer_off_normal.png diff --git a/res/drawable-xhdpi/ic_timer_10s_indicator.png b/res/drawable-xhdpi/ic_timer_10s_indicator.png Binary files differnew file mode 100644 index 000000000..0040e4b05 --- /dev/null +++ b/res/drawable-xhdpi/ic_timer_10s_indicator.png diff --git a/res/drawable-xhdpi/ic_timer_10s_normal.png b/res/drawable-xhdpi/ic_timer_10s_normal.png Binary files differnew file mode 100644 index 000000000..d8104e053 --- /dev/null +++ b/res/drawable-xhdpi/ic_timer_10s_normal.png diff --git a/res/drawable-xhdpi/ic_timer_3s_indicator.png b/res/drawable-xhdpi/ic_timer_3s_indicator.png Binary files differnew file mode 100644 index 000000000..745c09f7b --- /dev/null +++ b/res/drawable-xhdpi/ic_timer_3s_indicator.png diff --git a/res/drawable-xhdpi/ic_timer_3s_normal.png b/res/drawable-xhdpi/ic_timer_3s_normal.png Binary files differnew file mode 100644 index 000000000..c3874ac62 --- /dev/null +++ b/res/drawable-xhdpi/ic_timer_3s_normal.png diff --git a/res/drawable-xhdpi/ic_timer_off_indicator.png b/res/drawable-xhdpi/ic_timer_off_indicator.png Binary files differnew file mode 100644 index 000000000..7e6272d43 --- /dev/null +++ b/res/drawable-xhdpi/ic_timer_off_indicator.png diff --git a/res/drawable-xhdpi/ic_timer_off_normal.png b/res/drawable-xhdpi/ic_timer_off_normal.png Binary files differnew file mode 100644 index 000000000..b6554f1fd --- /dev/null +++ b/res/drawable-xhdpi/ic_timer_off_normal.png diff --git a/res/drawable-xxhdpi/ic_timer_10s_indicator.png b/res/drawable-xxhdpi/ic_timer_10s_indicator.png Binary files differnew file mode 100644 index 000000000..399456576 --- /dev/null +++ b/res/drawable-xxhdpi/ic_timer_10s_indicator.png diff --git a/res/drawable-xxhdpi/ic_timer_10s_normal.png b/res/drawable-xxhdpi/ic_timer_10s_normal.png Binary files differnew file mode 100644 index 000000000..61fffec85 --- /dev/null +++ b/res/drawable-xxhdpi/ic_timer_10s_normal.png diff --git a/res/drawable-xxhdpi/ic_timer_3s_indicator.png b/res/drawable-xxhdpi/ic_timer_3s_indicator.png Binary files differnew file mode 100644 index 000000000..7b0c5d3fd --- /dev/null +++ b/res/drawable-xxhdpi/ic_timer_3s_indicator.png diff --git a/res/drawable-xxhdpi/ic_timer_3s_normal.png b/res/drawable-xxhdpi/ic_timer_3s_normal.png Binary files differnew file mode 100644 index 000000000..a4a7bf9c0 --- /dev/null +++ b/res/drawable-xxhdpi/ic_timer_3s_normal.png diff --git a/res/drawable-xxhdpi/ic_timer_off_indicator.png b/res/drawable-xxhdpi/ic_timer_off_indicator.png Binary files differnew file mode 100644 index 000000000..21acf8ca4 --- /dev/null +++ b/res/drawable-xxhdpi/ic_timer_off_indicator.png diff --git a/res/drawable-xxhdpi/ic_timer_off_normal.png b/res/drawable-xxhdpi/ic_timer_off_normal.png Binary files differnew file mode 100644 index 000000000..935b7cef1 --- /dev/null +++ b/res/drawable-xxhdpi/ic_timer_off_normal.png diff --git a/res/layout-land/indicators.xml b/res/layout-land/indicators.xml index e2632fa5a..fa83e8f42 100644 --- a/res/layout-land/indicators.xml +++ b/res/layout-land/indicators.xml @@ -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 diff --git a/res/layout-land/mode_options.xml b/res/layout-land/mode_options.xml index d2ffcde9a..d8a69ceac 100644 --- a/res/layout-land/mode_options.xml +++ b/res/layout-land/mode_options.xml @@ -118,5 +118,10 @@ 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> diff --git a/res/layout-port/indicators.xml b/res/layout-port/indicators.xml index 68e35fdce..d98c591e5 100644 --- a/res/layout-port/indicators.xml +++ b/res/layout-port/indicators.xml @@ -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 diff --git a/res/layout-port/mode_options.xml b/res/layout-port/mode_options.xml index 4d2dd99e6..566b8edbe 100644 --- a/res/layout-port/mode_options.xml +++ b/res/layout-port/mode_options.xml @@ -118,5 +118,10 @@ 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> diff --git a/res/layout/photo_module.xml b/res/layout/photo_module.xml index 2cb002f24..e62d551af 100644 --- a/res/layout/photo_module.xml +++ b/res/layout/photo_module.xml @@ -32,4 +32,18 @@ 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> diff --git a/res/values/arrays.xml b/res/values/arrays.xml index ed7e19906..9b30aff56 100644 --- a/res/values/arrays.xml +++ b/res/values/arrays.xml @@ -457,6 +457,12 @@ <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> @@ -474,6 +480,24 @@ <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> diff --git a/src/com/android/camera/ButtonManager.java b/src/com/android/camera/ButtonManager.java index c5a6f2edc..a67e42bf3 100644 --- a/src/com/android/camera/ButtonManager.java +++ b/src/com/android/camera/ButtonManager.java @@ -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() { diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index 33cb36e8e..9bce0ce80 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -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; + } + } } diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java index f32256892..f1981a63b 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -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; - } - } diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java index 19d113471..35236dbdc 100644 --- a/src/com/android/camera/app/CameraAppUI.java +++ b/src/com/android/camera/app/CameraAppUI.java @@ -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); diff --git a/src/com/android/camera/settings/SettingsCache.java b/src/com/android/camera/settings/SettingsCache.java index e89bbd02f..bdd925340 100644 --- a/src/com/android/camera/settings/SettingsCache.java +++ b/src/com/android/camera/settings/SettingsCache.java @@ -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); } diff --git a/src/com/android/camera/settings/SettingsManager.java b/src/com/android/camera/settings/SettingsManager.java index 288bc8622..7e8127446 100644 --- a/src/com/android/camera/settings/SettingsManager.java +++ b/src/com/android/camera/settings/SettingsManager.java @@ -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; diff --git a/src/com/android/camera/ui/BottomBar.java b/src/com/android/camera/ui/BottomBar.java index 9561cf6a5..224093cb2 100644 --- a/src/com/android/camera/ui/BottomBar.java +++ b/src/com/android/camera/ui/BottomBar.java @@ -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 index 000000000..8bedb6376 --- /dev/null +++ b/src/com/android/camera/ui/CountDownView.java @@ -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 diff --git a/src/com/android/camera/widget/IndicatorIconController.java b/src/com/android/camera/widget/IndicatorIconController.java index 610d9c71d..5392628fd 100644 --- a/src/com/android/camera/widget/IndicatorIconController.java +++ b/src/com/android/camera/widget/IndicatorIconController.java @@ -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. } } } + } |