diff options
Diffstat (limited to 'src/com')
34 files changed, 810 insertions, 286 deletions
diff --git a/src/com/android/camera/ActivityBase.java b/src/com/android/camera/ActivityBase.java index b2ef481..ea523c8 100644 --- a/src/com/android/camera/ActivityBase.java +++ b/src/com/android/camera/ActivityBase.java @@ -20,13 +20,14 @@ import com.android.camera.ui.PopupManager; import android.app.Activity; import android.app.KeyguardManager; -import android.view.KeyEvent; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.hardware.Camera; import android.media.AudioManager; import android.os.Bundle; import android.util.Log; +import android.view.KeyEvent; /** * Superclass of Camera and VideoCamera activities. @@ -41,6 +42,11 @@ abstract public class ActivityBase extends Activity { @Override public void onCreate(Bundle icicle) { + if (Util.isTabletUI()) { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + } else { + setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); + } super.onCreate(icicle); setVolumeControlStream(AudioManager.STREAM_MUSIC); } @@ -61,11 +67,16 @@ abstract public class ActivityBase extends Activity { // Don't grab the camera if in use by lockscreen. For example, face // unlock may be using the camera. Camera may be already opened in // onCreate. doOnResume should continue if mCameraDevice != null. - if (mCameraDevice == null && !hasWindowFocus() && isKeyguardLocked()) { - if (LOGV) Log.v(TAG, "onRsume. mOnResumePending=true"); + // Suppose camera app is in the foreground. If users turn off and turn + // on the screen very fast, camera app can still have the focus when the + // lock screen shows up. The keyguard takes input focus, so the caemra + // app will lose focus when it is displayed. + if (LOGV) Log.v(TAG, "onResume. hasWindowFocus()=" + hasWindowFocus()); + if (mCameraDevice == null && isKeyguardLocked()) { + if (LOGV) Log.v(TAG, "onResume. mOnResumePending=true"); mOnResumePending = true; } else { - if (LOGV) Log.v(TAG, "onRsume. mOnResumePending=false"); + if (LOGV) Log.v(TAG, "onResume. mOnResumePending=false"); doOnResume(); mOnResumePending = false; } @@ -124,6 +135,12 @@ abstract public class ActivityBase extends Activity { private boolean isKeyguardLocked() { KeyguardManager kgm = (KeyguardManager) getSystemService(Context.KEYGUARD_SERVICE); + if (LOGV) { + if (kgm != null) { + Log.v(TAG, "kgm.isKeyguardLocked()="+kgm.isKeyguardLocked() + + ". kgm.isKeyguardSecure()="+kgm.isKeyguardSecure()); + } + } // isKeyguardSecure excludes the slide lock case. return (kgm != null) && kgm.isKeyguardLocked() && kgm.isKeyguardSecure(); } diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java index 8898510..9c7bbfe 100644 --- a/src/com/android/camera/Camera.java +++ b/src/com/android/camera/Camera.java @@ -33,6 +33,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences.Editor; +import android.content.pm.ActivityInfo; import android.graphics.Bitmap; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Face; @@ -62,6 +63,7 @@ import android.view.OrientationEventListener; import android.view.SurfaceHolder; import android.view.SurfaceView; import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; import android.view.animation.AnimationUtils; import android.widget.TextView; @@ -145,6 +147,7 @@ public class Camera extends ActivityBase implements FocusManager.Listener, private View mPreviewPanel; // The container of PreviewFrameLayout. private PreviewFrameLayout mPreviewFrameLayout; private View mPreviewFrame; // Preview frame area. + private RotateDialogController mRotateDialog; // A popup window that contains a bigger thumbnail and a list of apps to share. private SharePopup mSharePopup; @@ -188,7 +191,7 @@ public class Camera extends ActivityBase implements FocusManager.Listener, * * TODO: consider publishing by moving into MediaStore. */ - private final static String EXTRA_QUICK_CAPTURE = + private static final String EXTRA_QUICK_CAPTURE = "android.intent.extra.quickCapture"; // The display rotation in degrees. This is only valid when mCameraState is @@ -303,7 +306,7 @@ public class Camera extends ActivityBase implements FocusManager.Listener, } case DISMISS_TAP_TO_FOCUS_TOAST: { - View v = findViewById(R.id.tap_to_focus_prompt); + View v = findViewById(R.id.first_use_hint); v.setVisibility(View.GONE); v.setAnimation(AnimationUtils.loadAnimation(Camera.this, R.anim.on_screen_hint_exit)); @@ -388,7 +391,7 @@ public class Camera extends ActivityBase implements FocusManager.Listener, startFaceDetection(); // Show the tap to focus toast if this is the first start. if (mFocusAreaSupported && - mPreferences.getBoolean(CameraSettings.KEY_TAP_TO_FOCUS_PROMPT_SHOWN, true)) { + mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) { // Delay the toast for one second to wait for orientation. mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000); } @@ -698,8 +701,6 @@ public class Camera extends ActivityBase implements FocusManager.Listener, + mPictureDisplayedToJpegCallbackTime + "ms"); if (!mIsImageCaptureIntent) { - enableCameraControls(true); - startPreview(); startFaceDetection(); } @@ -738,12 +739,8 @@ public class Camera extends ActivityBase implements FocusManager.Listener, mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime; Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms"); + setCameraState(IDLE); mFocusManager.onAutoFocus(focused); - // If focus completes and the snapshot is not started, enable the - // controls. - if (mFocusManager.isFocusCompleted()) { - enableCameraControls(true); - } } } @@ -823,7 +820,11 @@ public class Camera extends ActivityBase implements FocusManager.Listener, r.width = width; r.height = height; r.dateTaken = System.currentTimeMillis(); - r.previewWidth = mPreviewFrameLayout.getWidth(); + if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { + r.previewWidth = mPreviewFrameLayout.getHeight(); + } else { + r.previewWidth = mPreviewFrameLayout.getWidth(); + } synchronized (this) { while (mQueue.size() >= QUEUE_LIMIT) { try { @@ -949,6 +950,20 @@ public class Camera extends ActivityBase implements FocusManager.Listener, } } + private void setCameraState(int state) { + mCameraState = state; + switch (state) { + case SNAPSHOT_IN_PROGRESS: + case FOCUSING: + enableCameraControls(false); + break; + case IDLE: + case PREVIEW_STOPPED: + enableCameraControls(true); + break; + } + } + @Override public boolean capture() { // If we are already in the middle of taking a snapshot then ignore. @@ -957,7 +972,6 @@ public class Camera extends ActivityBase implements FocusManager.Listener, } mCaptureStartTime = System.currentTimeMillis(); mPostViewPictureCallbackTime = 0; - enableCameraControls(false); mJpegImageData = null; // Set rotation and gps data. @@ -968,7 +982,7 @@ public class Camera extends ActivityBase implements FocusManager.Listener, mCameraDevice.takePicture(mShutterCallback, mRawPictureCallback, mPostViewPictureCallback, new JpegPictureCallback(loc)); - mCameraState = SNAPSHOT_IN_PROGRESS; + setCameraState(SNAPSHOT_IN_PROGRESS); return true; } @@ -1025,8 +1039,9 @@ public class Camera extends ActivityBase implements FocusManager.Listener, public void onCreate(Bundle icicle) { super.onCreate(icicle); getPreferredCameraId(); - mFocusManager = new FocusManager(mPreferences, - getString(R.string.pref_camera_focusmode_default)); + String[] defaultFocusModes = getResources().getStringArray( + R.array.pref_camera_focusmode_default_array); + mFocusManager = new FocusManager(mPreferences, defaultFocusModes); /* * To reduce startup time, we start the camera open and preview threads. @@ -1046,6 +1061,8 @@ public class Camera extends ActivityBase implements FocusManager.Listener, mThumbnailView.setVisibility(View.VISIBLE); } + mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog); + mPreferences.setLocalId(this, mCameraId); CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); @@ -1216,16 +1233,13 @@ public class Camera extends ActivityBase implements FocusManager.Listener, } } - private void setOrientationIndicator(int degree) { - if (mThumbnailView != null) mThumbnailView.setDegree(degree); - if (mModePicker != null) mModePicker.setDegree(degree); - if (mSharePopup != null) mSharePopup.setOrientation(degree); - if (mIndicatorControlContainer != null) mIndicatorControlContainer.setDegree(degree); - if (mZoomControl != null) mZoomControl.setDegree(degree); - if (mFocusIndicator != null) mFocusIndicator.setOrientation(degree); - if (mFaceView != null) mFaceView.setOrientation(degree); - if (mReviewCancelButton != null) mReviewCancelButton.setOrientation(degree); - if (mReviewDoneButton != null) mReviewDoneButton.setOrientation(degree); + private void setOrientationIndicator(int orientation) { + Rotatable[] indicators = {mThumbnailView, mModePicker, mSharePopup, + mIndicatorControlContainer, mZoomControl, mFocusIndicator, mFaceView, + mReviewCancelButton, mReviewDoneButton, mRotateDialog}; + for (Rotatable indicator : indicators) { + if (indicator != null) indicator.setOrientation(orientation); + } } @Override @@ -1437,7 +1451,6 @@ public class Camera extends ActivityBase implements FocusManager.Listener, if (mOpenCameraFail || mCameraDisabled) return; mPausing = false; - mJpegPictureCallbackTime = 0; mZoomValue = 0; @@ -1556,15 +1569,13 @@ public class Camera extends ActivityBase implements FocusManager.Listener, public void autoFocus() { mFocusStartTime = System.currentTimeMillis(); mCameraDevice.autoFocus(mAutoFocusCallback); - mCameraState = FOCUSING; - enableCameraControls(false); + setCameraState(FOCUSING); } @Override public void cancelAutoFocus() { mCameraDevice.cancelAutoFocus(); - mCameraState = IDLE; - enableCameraControls(true); + setCameraState(IDLE); setCameraParameters(UPDATE_PARAM_PREFERENCE); } @@ -1710,7 +1721,7 @@ public class Camera extends ActivityBase implements FocusManager.Listener, mCameraDevice.setFaceDetectionListener(null); mCameraDevice.setErrorCallback(null); mCameraDevice = null; - mCameraState = PREVIEW_STOPPED; + setCameraState(PREVIEW_STOPPED); mFocusManager.onCameraReleased(); } } @@ -1771,7 +1782,7 @@ public class Camera extends ActivityBase implements FocusManager.Listener, } mZoomState = ZOOM_STOPPED; - mCameraState = IDLE; + setCameraState(IDLE); mFocusManager.onPreviewStarted(); if (mSnapshotOnIdle) { @@ -1785,7 +1796,7 @@ public class Camera extends ActivityBase implements FocusManager.Listener, mCameraDevice.cancelAutoFocus(); // Reset the focus. mCameraDevice.stopPreview(); } - mCameraState = PREVIEW_STOPPED; + setCameraState(PREVIEW_STOPPED); mFocusManager.onPreviewStopped(); } @@ -2026,8 +2037,6 @@ public class Camera extends ActivityBase implements FocusManager.Listener, private void hidePostCaptureAlert() { if (mIsImageCaptureIntent) { - enableCameraControls(true); - int[] pickIds = {R.id.btn_retake, R.id.btn_done}; for (int id : pickIds) { Util.fadeOut(findViewById(id)); @@ -2158,10 +2167,11 @@ public class Camera extends ActivityBase implements FocusManager.Listener, restorePreferences(); } }; - MenuHelper.confirmAction(this, + mRotateDialog.showAlertDialog( getString(R.string.confirm_restore_title), getString(R.string.confirm_restore_message), - runnable); + getString(android.R.string.ok), runnable, + getString(android.R.string.cancel), null); } private void restorePreferences() { @@ -2206,15 +2216,18 @@ public class Camera extends ActivityBase implements FocusManager.Listener, } private void showTapToFocusToast() { + // Set the text of toast + TextView textView = (TextView) findViewById(R.id.toast_text); + textView.setText(R.string.tap_to_focus); // Show the toast. - RotateLayout v = (RotateLayout) findViewById(R.id.tap_to_focus_prompt); + RotateLayout v = (RotateLayout) findViewById(R.id.first_use_hint); v.setOrientation(mOrientationCompensation); v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.on_screen_hint_enter)); v.setVisibility(View.VISIBLE); mHandler.sendEmptyMessageDelayed(DISMISS_TAP_TO_FOCUS_TOAST, 5000); // Clear the preference. Editor editor = mPreferences.edit(); - editor.putBoolean(CameraSettings.KEY_TAP_TO_FOCUS_PROMPT_SHOWN, false); + editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false); editor.apply(); } diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java index 9c3104b..c861366 100644 --- a/src/com/android/camera/CameraSettings.java +++ b/src/com/android/camera/CameraSettings.java @@ -50,7 +50,8 @@ public class CameraSettings { public static final String KEY_EXPOSURE = "pref_camera_exposure_key"; public static final String KEY_VIDEO_EFFECT = "pref_video_effect_key"; public static final String KEY_CAMERA_ID = "pref_camera_id_key"; - public static final String KEY_TAP_TO_FOCUS_PROMPT_SHOWN = "pref_tap_to_focus_prompt_shown_key"; + public static final String KEY_CAMERA_FIRST_USE_HINT_SHOWN = "pref_camera_first_use_hint_shown_key"; + public static final String KEY_VIDEO_FIRST_USE_HINT_SHOWN = "pref_video_first_use_hint_shown_key"; public static final String EXPOSURE_DEFAULT_VALUE = "0"; diff --git a/src/com/android/camera/ComboPreferences.java b/src/com/android/camera/ComboPreferences.java index 1af634c..bea2ffd 100644 --- a/src/com/android/camera/ComboPreferences.java +++ b/src/com/android/camera/ComboPreferences.java @@ -76,7 +76,8 @@ public class ComboPreferences implements SharedPreferences, OnSharedPreferenceCh return key.equals(CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL) || key.equals(CameraSettings.KEY_CAMERA_ID) || key.equals(CameraSettings.KEY_RECORD_LOCATION) - || key.equals(CameraSettings.KEY_TAP_TO_FOCUS_PROMPT_SHOWN) + || key.equals(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN) + || key.equals(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN) || key.equals(CameraSettings.KEY_VIDEO_EFFECT); } diff --git a/src/com/android/camera/EffectsRecorder.java b/src/com/android/camera/EffectsRecorder.java index d5f83ef..bb7c813 100644 --- a/src/com/android/camera/EffectsRecorder.java +++ b/src/com/android/camera/EffectsRecorder.java @@ -93,6 +93,7 @@ public class EffectsRecorder { private long mMaxFileSize = 0; private int mMaxDurationMs = 0; private int mCameraFacing = Camera.CameraInfo.CAMERA_FACING_BACK; + private boolean mAppIsLandscape; private int mEffect = EFFECT_NONE; private int mCurrentEffect = EFFECT_NONE; @@ -375,6 +376,16 @@ public class EffectsRecorder { setRecordingOrientation(); } + /** Passes the native orientation of the Camera app (device dependent) + * to allow for correct output aspect ratio. Defaults to portrait */ + public void setAppToLandscape(boolean landscape) { + if (mState != STATE_CONFIGURE) { + throw new RuntimeException( + "setAppToLandscape called after configuration!"); + } + mAppIsLandscape = landscape; + } + public void setCameraFacing(int facing) { switch (mState) { case STATE_RELEASED: @@ -419,7 +430,12 @@ public class EffectsRecorder { Log.v(TAG, "Effects framework initializing. Recording size " + mProfile.videoFrameWidth + ", " + mProfile.videoFrameHeight); } - + if (!mAppIsLandscape) { + int tmp; + tmp = mProfile.videoFrameWidth; + mProfile.videoFrameWidth = mProfile.videoFrameHeight; + mProfile.videoFrameHeight = tmp; + } mGraphEnv.addReferences( "textureSourceCallback", mSourceReadyCallback, "recordingWidth", mProfile.videoFrameWidth, diff --git a/src/com/android/camera/FocusManager.java b/src/com/android/camera/FocusManager.java index 72ea8b5..bdf4766 100644 --- a/src/com/android/camera/FocusManager.java +++ b/src/com/android/camera/FocusManager.java @@ -67,7 +67,7 @@ public class FocusManager { private List<Area> mFocusArea; // focus area in driver format private List<Area> mMeteringArea; // metering area in driver format private String mFocusMode; - private String mDefaultFocusMode; + private String[] mDefaultFocusModes; private String mOverrideFocusMode; private Parameters mParameters; private ComboPreferences mPreferences; @@ -96,9 +96,9 @@ public class FocusManager { } } - public FocusManager(ComboPreferences preferences, String defaultFocusMode) { + public FocusManager(ComboPreferences preferences, String[] defaultFocusModes) { mPreferences = preferences; - mDefaultFocusMode = defaultFocusMode; + mDefaultFocusModes = defaultFocusModes; mHandler = new MainHandler(); mMatrix = new Matrix(); } @@ -361,6 +361,7 @@ public class FocusManager { // This can only be called after mParameters is initialized. public String getFocusMode() { if (mOverrideFocusMode != null) return mOverrideFocusMode; + List<String> supportedFocusModes = mParameters.getSupportedFocusModes(); if (mFocusAreaSupported && mFocusArea != null) { // Always use autofocus in tap-to-focus. @@ -368,9 +369,20 @@ public class FocusManager { } else { // The default is continuous autofocus. mFocusMode = mPreferences.getString( - CameraSettings.KEY_FOCUS_MODE, mDefaultFocusMode); + CameraSettings.KEY_FOCUS_MODE, null); + + // Try to find a supported focus mode from the default list. + if (mFocusMode == null) { + for (int i = 0; i < mDefaultFocusModes.length; i++) { + String mode = mDefaultFocusModes[i]; + if (isSupported(mode, supportedFocusModes)) { + mFocusMode = mode; + break; + } + } + } } - if (!isSupported(mFocusMode, mParameters.getSupportedFocusModes())) { + if (!isSupported(mFocusMode, supportedFocusModes)) { // For some reasons, the driver does not support the current // focus mode. Fall back to auto. if (isSupported(Parameters.FOCUS_MODE_AUTO, diff --git a/src/com/android/camera/ListPreference.java b/src/com/android/camera/ListPreference.java index 6885a37..32cf692 100644 --- a/src/com/android/camera/ListPreference.java +++ b/src/com/android/camera/ListPreference.java @@ -21,6 +21,7 @@ import android.content.SharedPreferences; import android.content.res.TypedArray; import android.util.AttributeSet; import android.util.Log; +import android.util.TypedValue; import java.util.ArrayList; import java.util.List; @@ -33,7 +34,7 @@ public class ListPreference extends CameraPreference { private final String TAG = "ListPreference"; private final String mKey; private String mValue; - private final String mDefaultValue; + private final CharSequence[] mDefaultValues; private CharSequence[] mEntries; private CharSequence[] mEntryValues; @@ -47,7 +48,20 @@ public class ListPreference extends CameraPreference { mKey = Util.checkNotNull( a.getString(R.styleable.ListPreference_key)); - mDefaultValue = a.getString(R.styleable.ListPreference_defaultValue); + + // We allow the defaultValue attribute to be a string or an array of + // strings. The reason we need multiple default values is that some + // of them may be unsupported on a specific platform (for example, + // continuous auto-focus). In that case the first supported value + // in the array will be used. + int attrDefaultValue = R.styleable.ListPreference_defaultValue; + TypedValue tv = a.peekValue(attrDefaultValue); + if (tv != null && tv.type == TypedValue.TYPE_REFERENCE) { + mDefaultValues = a.getTextArray(attrDefaultValue); + } else { + mDefaultValues = new CharSequence[1]; + mDefaultValues[0] = a.getString(attrDefaultValue); + } setEntries(a.getTextArray(R.styleable.ListPreference_entries)); setEntryValues(a.getTextArray( @@ -77,12 +91,27 @@ public class ListPreference extends CameraPreference { public String getValue() { if (!mLoaded) { - mValue = getSharedPreferences().getString(mKey, mDefaultValue); + mValue = getSharedPreferences().getString(mKey, + findSupportedDefaultValue()); mLoaded = true; } return mValue; } + // Find the first value in mDefaultValues which is supported. + private String findSupportedDefaultValue() { + for (int i = 0; i < mDefaultValues.length; i++) { + for (int j = 0; j < mEntryValues.length; j++) { + // Note that mDefaultValues[i] may be null (if unspecified + // in the xml file). + if (mEntryValues[j].equals(mDefaultValues[i])) { + return mDefaultValues[i].toString(); + } + } + } + return null; + } + public void setValue(String value) { if (findIndexOfValue(value) < 0) throw new IllegalArgumentException(); mValue = value; diff --git a/src/com/android/camera/ModePicker.java b/src/com/android/camera/ModePicker.java index 528bc3d..b79baa5 100644 --- a/src/com/android/camera/ModePicker.java +++ b/src/com/android/camera/ModePicker.java @@ -17,6 +17,7 @@ package com.android.camera; import com.android.camera.ui.PopupManager; +import com.android.camera.ui.Rotatable; import com.android.camera.ui.RotateImageView; import android.content.Context; @@ -36,7 +37,7 @@ import android.widget.RelativeLayout; * a current mode indicator. */ public class ModePicker extends RelativeLayout implements View.OnClickListener, - PopupManager.OnOtherPopupShowedListener { + PopupManager.OnOtherPopupShowedListener, Rotatable { public static final int MODE_CAMERA = 0; public static final int MODE_VIDEO = 1; public static final int MODE_PANORAMA = 2; @@ -188,11 +189,11 @@ public class ModePicker extends RelativeLayout implements View.OnClickListener, return true; } - public void setDegree(int degree) { + public void setOrientation(int orientation) { for (int i = 0; i < MODE_NUM; ++i) { - mModeSelectionIcon[i].setDegree(degree); + mModeSelectionIcon[i].setOrientation(orientation); if (mCurrentModeFrame != null) { - mCurrentModeIcon[i].setDegree(degree); + mCurrentModeIcon[i].setOrientation(orientation); } } } @@ -222,10 +223,13 @@ public class ModePicker extends RelativeLayout implements View.OnClickListener, } private void updateModeState() { - // Grey-out the unselected icons. - for (int i = 0; i < MODE_NUM; ++i) { - highlightView(mModeSelectionIcon[i], (i == mCurrentMode)); + // Grey-out the unselected icons for Phone UI. + if (mCurrentModeFrame != null) { + for (int i = 0; i < MODE_NUM; ++i) { + highlightView(mModeSelectionIcon[i], (i == mCurrentMode)); + } } + // Update the current mode icons on the Phone UI. The selected mode // should be in the center of the current mode icon bar. if (mCurrentModeFrame != null) { diff --git a/src/com/android/camera/PreviewFrameLayout.java b/src/com/android/camera/PreviewFrameLayout.java index 5d31e2b..c16961a 100644 --- a/src/com/android/camera/PreviewFrameLayout.java +++ b/src/com/android/camera/PreviewFrameLayout.java @@ -18,8 +18,10 @@ package com.android.camera; import com.android.camera.R; +import android.app.Activity; import android.content.Context; -import android.graphics.Rect; +import android.content.pm.ActivityInfo; +import android.view.View; import android.util.AttributeSet; import android.widget.RelativeLayout; /** @@ -31,14 +33,21 @@ public class PreviewFrameLayout extends RelativeLayout { public void onSizeChanged(); } - private double mAspectRatio = 4.0 / 3.0; + private double mAspectRatio; public PreviewFrameLayout(Context context, AttributeSet attrs) { super(context, attrs); + setAspectRatio(4.0 / 3.0); } public void setAspectRatio(double ratio) { if (ratio <= 0.0) throw new IllegalArgumentException(); + + if (((Activity) getContext()).getRequestedOrientation() + == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { + ratio = 1 / ratio; + } + if (mAspectRatio != ratio) { mAspectRatio = ratio; requestLayout(); @@ -46,10 +55,10 @@ public class PreviewFrameLayout extends RelativeLayout { } public void showBorder(boolean enabled) { - setActivated(enabled); + findViewById(R.id.preview_border).setVisibility( + enabled ? View.VISIBLE : View.INVISIBLE); } - @Override protected void onMeasure(int widthSpec, int heightSpec) { int previewWidth = MeasureSpec.getSize(widthSpec); diff --git a/src/com/android/camera/RotateDialogController.java b/src/com/android/camera/RotateDialogController.java new file mode 100644 index 0000000..d91ba13 --- /dev/null +++ b/src/com/android/camera/RotateDialogController.java @@ -0,0 +1,157 @@ +/* + * Copyright (C) 2011 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; + +import com.android.camera.ui.Rotatable; +import com.android.camera.ui.RotateLayout; + +import android.app.Activity; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.Animation; +import android.view.animation.AnimationUtils; +import android.widget.Button; +import android.widget.TextView; +import android.widget.ProgressBar; + +public class RotateDialogController implements Rotatable { + + private static final String TAG = "RotateDialogController"; + private static final long ANIM_DURATION = 150; // millis + + private Activity mActivity; + private int mLayoutResourceID; + private View mDialogRootLayout; + private RotateLayout mRotateDialog; + private View mRotateDialogTitleLayout; + private View mRotateDialogButtonLayout; + private TextView mRotateDialogTitle; + private ProgressBar mRotateDialogSpinner; + private TextView mRotateDialogText; + private TextView mRotateDialogButton1; + private TextView mRotateDialogButton2; + + private Animation mFadeInAnim, mFadeOutAnim; + + public RotateDialogController(Activity a, int layoutResource) { + mActivity = a; + mLayoutResourceID = layoutResource; + } + + private void inflateDialogLayout() { + if (mDialogRootLayout == null) { + ViewGroup layoutRoot = (ViewGroup) mActivity.getWindow().getDecorView(); + LayoutInflater inflater = mActivity.getLayoutInflater(); + View v = inflater.inflate(mLayoutResourceID, layoutRoot); + mDialogRootLayout = v.findViewById(R.id.rotate_dialog_root_layout); + mRotateDialog = (RotateLayout) v.findViewById(R.id.rotate_dialog_layout); + mRotateDialogTitleLayout = v.findViewById(R.id.rotate_dialog_title_layout); + mRotateDialogButtonLayout = v.findViewById(R.id.rotate_dialog_button_layout); + mRotateDialogTitle = (TextView) v.findViewById(R.id.rotate_dialog_title); + mRotateDialogSpinner = (ProgressBar) v.findViewById(R.id.rotate_dialog_spinner); + mRotateDialogText = (TextView) v.findViewById(R.id.rotate_dialog_text); + mRotateDialogButton1 = (Button) v.findViewById(R.id.rotate_dialog_button1); + mRotateDialogButton2 = (Button) v.findViewById(R.id.rotate_dialog_button2); + + mFadeInAnim = AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_in); + mFadeOutAnim = AnimationUtils.loadAnimation( + mActivity, android.R.anim.fade_out); + mFadeInAnim.setDuration(ANIM_DURATION); + mFadeOutAnim.setDuration(ANIM_DURATION); + } + } + + @Override + public void setOrientation(int orientation) { + inflateDialogLayout(); + mRotateDialog.setOrientation(orientation); + } + + public void resetRotateDialog() { + inflateDialogLayout(); + mRotateDialogTitleLayout.setVisibility(View.GONE); + mRotateDialogSpinner.setVisibility(View.GONE); + mRotateDialogButton1.setVisibility(View.GONE); + mRotateDialogButton2.setVisibility(View.GONE); + mRotateDialogButtonLayout.setVisibility(View.GONE); + } + + private void fadeOutDialog() { + mDialogRootLayout.startAnimation(mFadeOutAnim); + mDialogRootLayout.setVisibility(View.GONE); + } + + private void fadeInDialog() { + mDialogRootLayout.startAnimation(mFadeInAnim); + mDialogRootLayout.setVisibility(View.VISIBLE); + } + + public void dismissDialog() { + if (mDialogRootLayout != null && mDialogRootLayout.getVisibility() != View.GONE) { + fadeOutDialog(); + } + } + + public void showAlertDialog(String title, String msg, String button1Text, + final Runnable r1, String button2Text, final Runnable r2) { + resetRotateDialog(); + + mRotateDialogTitle.setText(title); + mRotateDialogTitleLayout.setVisibility(View.VISIBLE); + + mRotateDialogText.setText(msg); + + if (button1Text != null) { + mRotateDialogButton1.setText(button1Text); + mRotateDialogButton1.setVisibility(View.VISIBLE); + mRotateDialogButton1.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (r1 != null) r1.run(); + dismissDialog(); + } + }); + mRotateDialogButtonLayout.setVisibility(View.VISIBLE); + } + if (button2Text != null) { + mRotateDialogButton2.setText(button2Text); + mRotateDialogButton2.setVisibility(View.VISIBLE); + mRotateDialogButton2.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if (r2 != null) r2.run(); + dismissDialog(); + } + }); + mRotateDialogButtonLayout.setVisibility(View.VISIBLE); + } + + fadeInDialog(); + } + + public void showWaitingDialog(String msg) { + resetRotateDialog(); + + mRotateDialogText.setText(msg); + mRotateDialogSpinner.setVisibility(View.VISIBLE); + + fadeInDialog(); + } + +} diff --git a/src/com/android/camera/Storage.java b/src/com/android/camera/Storage.java index 564c088..38a6d48 100644 --- a/src/com/android/camera/Storage.java +++ b/src/com/android/camera/Storage.java @@ -84,10 +84,16 @@ public class Storage { values.put(ImageColumns.LONGITUDE, location.getLongitude()); } - Uri uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values); - if (uri == null) { - Log.e(TAG, "Failed to write MediaStore"); - return null; + Uri uri = null; + try { + uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values); + } catch (Throwable th) { + // This can happen when the external volume is already mounted, but + // MediaScanner has not notify MediaProvider to add that volume. + // The picture is still safe and MediaScanner will find it and + // insert it into MediaProvider. The only problem is that the user + // cannot click the thumbnail to review the picture. + Log.e(TAG, "Failed to write MediaStore" + th); } return uri; } diff --git a/src/com/android/camera/Util.java b/src/com/android/camera/Util.java index 21bad6f..ba8a4f7 100644 --- a/src/com/android/camera/Util.java +++ b/src/com/android/camera/Util.java @@ -90,7 +90,7 @@ public class Util { } public static void initialize(Context context) { - sIsTabletUI = (context.getResources().getConfiguration().screenWidthDp >= 1024); + sIsTabletUI = (context.getResources().getConfiguration().smallestScreenWidthDp >= 600); DisplayMetrics metrics = new DisplayMetrics(); WindowManager wm = (WindowManager) diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java index 6de5a81..0b946c4 100755 --- a/src/com/android/camera/VideoCamera.java +++ b/src/com/android/camera/VideoCamera.java @@ -32,6 +32,8 @@ import android.content.ContentValues; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; +import android.content.SharedPreferences.Editor; +import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.hardware.Camera.CameraInfo; @@ -64,6 +66,7 @@ import android.view.SurfaceView; import android.view.View; import android.view.Window; import android.view.WindowManager; +import android.view.animation.AnimationUtils; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; @@ -94,6 +97,8 @@ public class VideoCamera extends ActivityBase private static final int CLEAR_SCREEN_DELAY = 4; private static final int UPDATE_RECORD_TIME = 5; private static final int ENABLE_SHUTTER_BUTTON = 6; + private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7; + private static final int DISMISS_TAP_TO_SNAPSHOT_TOAST = 8; private static final int SCREEN_DELAY = 2 * 60 * 1000; @@ -111,7 +116,7 @@ public class VideoCamera extends ActivityBase CamcorderProfile.QUALITY_TIME_LAPSE_720P, CamcorderProfile.QUALITY_TIME_LAPSE_480P, CamcorderProfile.QUALITY_TIME_LAPSE_CIF, - 1007, /* TODO: replace it with QUALITY_TIME_LAPSE_QVGA if public. */ + CamcorderProfile.QUALITY_TIME_LAPSE_QVGA, CamcorderProfile.QUALITY_TIME_LAPSE_QCIF}; private static final int[] VIDEO_QUALITY = { @@ -119,7 +124,7 @@ public class VideoCamera extends ActivityBase CamcorderProfile.QUALITY_720P, CamcorderProfile.QUALITY_480P, CamcorderProfile.QUALITY_CIF, - 7, /* TODO: replace it with CamcorderProfile.QUALITY_QVGA */ + CamcorderProfile.QUALITY_QVGA, CamcorderProfile.QUALITY_QCIF}; /** @@ -297,6 +302,19 @@ public class VideoCamera extends ActivityBase break; } + case SHOW_TAP_TO_SNAPSHOT_TOAST: { + showTapToSnapshotToast(); + break; + } + + case DISMISS_TAP_TO_SNAPSHOT_TOAST: { + View v = findViewById(R.id.first_use_hint); + v.setVisibility(View.GONE); + v.setAnimation(AnimationUtils.loadAnimation(VideoCamera.this, + R.anim.on_screen_hint_exit)); + break; + } + default: Log.v(TAG, "Unhandled message: " + msg.what); break; @@ -539,22 +557,27 @@ public class VideoCamera extends ActivityBase setOrientationIndicator(mOrientationCompensation); } } + + // Show the toast after getting the first orientation changed. + if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) { + mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST); + showTapToSnapshotToast(); + } } } - private void setOrientationIndicator(int degree) { - if (mThumbnailView != null) mThumbnailView.setDegree(degree); - if (mModePicker != null) mModePicker.setDegree(degree); - if (mSharePopup != null) mSharePopup.setOrientation(degree); - if (mBgLearningMessageRotater != null) mBgLearningMessageRotater.setOrientation(degree); - if (mIndicatorControlContainer != null) mIndicatorControlContainer.setDegree(degree); - if (mReviewDoneButton != null) mReviewDoneButton.setOrientation(degree); - if (mReviewPlayButton != null) mReviewPlayButton.setOrientation(degree); - if (mReviewCancelButton!= null) mReviewCancelButton.setOrientation(degree); + private void setOrientationIndicator(int orientation) { + Rotatable[] indicators = {mThumbnailView, mModePicker, mSharePopup, + mBgLearningMessageRotater, mIndicatorControlContainer, + mReviewDoneButton, mReviewPlayButton, mReviewCancelButton}; + for (Rotatable indicator : indicators) { + if (indicator != null) indicator.setOrientation(orientation); + } + // We change the orientation of the linearlayout only for phone UI because when in portrait // the width is not enough. if (mLabelsLinearLayout != null) { - if (((degree / 90) & 1) == 1) { + if (((orientation / 90) & 1) == 1) { mLabelsLinearLayout.setOrientation(mLabelsLinearLayout.VERTICAL); } else { mLabelsLinearLayout.setOrientation(mLabelsLinearLayout.HORIZONTAL); @@ -1238,6 +1261,10 @@ public class VideoCamera extends ActivityBase // If the mCameraDevice is null, then this activity is going to finish if (mCameraDevice == null) return; + boolean inLandscape = + (getRequestedOrientation() == + ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId]; mEffectsDisplayResult = false; @@ -1245,6 +1272,7 @@ public class VideoCamera extends ActivityBase // TODO: Confirm none of the foll need to go to initializeEffectsRecording() // and none of these change even when the preview is not refreshed. + mEffectsRecorder.setAppToLandscape(inLandscape); mEffectsRecorder.setCamera(mCameraDevice); mEffectsRecorder.setCameraFacing(info.facing); mEffectsRecorder.setProfile(mProfile); @@ -2290,6 +2318,12 @@ public class VideoCamera extends ActivityBase private void initializeVideoSnapshot() { if (mParameters.isVideoSnapshotSupported() && !mIsVideoCaptureIntent) { findViewById(R.id.camera_preview).setOnTouchListener(this); + // Show the tap to focus toast if this is the first start. + if (mPreferences.getBoolean( + CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) { + // Delay the toast for one second to wait for orientation. + mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000); + } } } @@ -2403,4 +2437,20 @@ public class VideoCamera extends ActivityBase mVideoFileDescriptor = null; } } + + private void showTapToSnapshotToast() { + // Set the text of toast + TextView textView = (TextView) findViewById(R.id.toast_text); + textView.setText(R.string.video_snapshot_hint); + // Show the toast. + RotateLayout v = (RotateLayout) findViewById(R.id.first_use_hint); + v.setOrientation(mOrientationCompensation); + v.startAnimation(AnimationUtils.loadAnimation(this, R.anim.on_screen_hint_enter)); + v.setVisibility(View.VISIBLE); + mHandler.sendEmptyMessageDelayed(DISMISS_TAP_TO_SNAPSHOT_TOAST, 5000); + // Clear the preference. + Editor editor = mPreferences.edit(); + editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false); + editor.apply(); + } } diff --git a/src/com/android/camera/panorama/MosaicRenderer.java b/src/com/android/camera/panorama/MosaicRenderer.java index 1ff307d..f055c0e 100644 --- a/src/com/android/camera/panorama/MosaicRenderer.java +++ b/src/com/android/camera/panorama/MosaicRenderer.java @@ -43,8 +43,9 @@ public class MosaicRenderer * * @param width width of the drawing surface in pixels. * @param height height of the drawing surface in pixels. + * @param isLandscapeOrientation is the orientation of the activity layout in landscape. */ - public static native void reset(int width, int height); + public static native void reset(int width, int height, boolean isLandscapeOrientation); /** * Calling this function will render the SurfaceTexture to a new 2D texture diff --git a/src/com/android/camera/panorama/MosaicRendererSurfaceView.java b/src/com/android/camera/panorama/MosaicRendererSurfaceView.java index 08d4eff..b2acfde 100644 --- a/src/com/android/camera/panorama/MosaicRendererSurfaceView.java +++ b/src/com/android/camera/panorama/MosaicRendererSurfaceView.java @@ -16,7 +16,9 @@ package com.android.camera.panorama; +import android.app.Activity; import android.content.Context; +import android.content.pm.ActivityInfo; import android.graphics.PixelFormat; import android.opengl.GLSurfaceView; import android.os.ConditionVariable; @@ -33,25 +35,36 @@ public class MosaicRendererSurfaceView extends GLSurfaceView { private static final boolean DEBUG = false; private MosaicRendererSurfaceViewRenderer mRenderer; private ConditionVariable mPreviewFrameReadyForProcessing; + private boolean mIsLandscapeOrientation = true; public MosaicRendererSurfaceView(Context context) { super(context); - init(false, 0, 0); - setZOrderMediaOverlay(true); + initialize(context, false, 0, 0); } public MosaicRendererSurfaceView(Context context, AttributeSet attrs) { super(context, attrs); - init(false, 0, 0); - setZOrderMediaOverlay(true); + initialize(context, false, 0, 0); } - public MosaicRendererSurfaceView(Context context, boolean translucent, int depth, int stencil) { + public MosaicRendererSurfaceView(Context context, boolean translucent, + int depth, int stencil) { super(context); + initialize(context, translucent, depth, stencil); + } + + private void initialize(Context context, boolean translucent, int depth, int stencil) { + getDisplayOrientation(context); init(translucent, depth, stencil); setZOrderMediaOverlay(true); } + private void getDisplayOrientation(Context context) { + Activity activity = (PanoramaActivity) context; + mIsLandscapeOrientation = (activity.getRequestedOrientation() + == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE ); + } + private void init(boolean translucent, int depth, int stencil) { /* By default, GLSurfaceView() creates a RGB_565 opaque surface. @@ -78,7 +91,7 @@ public class MosaicRendererSurfaceView extends GLSurfaceView { new ConfigChooser(5, 6, 5, 0, depth, stencil)); /* Set the renderer responsible for frame rendering */ - mRenderer = new MosaicRendererSurfaceViewRenderer(); + mRenderer = new MosaicRendererSurfaceViewRenderer(mIsLandscapeOrientation); setRenderer(mRenderer); setRenderMode(RENDERMODE_WHEN_DIRTY); mPreviewFrameReadyForProcessing = new ConditionVariable(); diff --git a/src/com/android/camera/panorama/MosaicRendererSurfaceViewRenderer.java b/src/com/android/camera/panorama/MosaicRendererSurfaceViewRenderer.java index b2b2f56..3089972 100755 --- a/src/com/android/camera/panorama/MosaicRendererSurfaceViewRenderer.java +++ b/src/com/android/camera/panorama/MosaicRendererSurfaceViewRenderer.java @@ -25,9 +25,14 @@ import javax.microedition.khronos.opengles.GL10; public class MosaicRendererSurfaceViewRenderer implements GLSurfaceView.Renderer { private static final String TAG = "MosaicRendererSurfaceViewRenderer"; + private boolean mIsLandscapeOrientation; private MosaicSurfaceCreateListener mSurfaceCreateListener; + public MosaicRendererSurfaceViewRenderer(boolean isLandscapeOrientation) { + mIsLandscapeOrientation = isLandscapeOrientation; + } + /** A callback to be called when the surface is created */ public interface MosaicSurfaceCreateListener { public void onMosaicSurfaceCreated(final int surface); @@ -41,7 +46,7 @@ public class MosaicRendererSurfaceViewRenderer implements GLSurfaceView.Renderer @Override public void onSurfaceChanged(GL10 gl, int width, int height) { - MosaicRenderer.reset(width, height); + MosaicRenderer.reset(width, height, mIsLandscapeOrientation); Log.i(TAG, "Renderer: onSurfaceChanged"); if (mSurfaceCreateListener != null) { mSurfaceCreateListener.onMosaicSurfaceChanged(); diff --git a/src/com/android/camera/panorama/PanoramaActivity.java b/src/com/android/camera/panorama/PanoramaActivity.java index 7a7cf78..a65d263 100755 --- a/src/com/android/camera/panorama/PanoramaActivity.java +++ b/src/com/android/camera/panorama/PanoramaActivity.java @@ -25,20 +25,21 @@ import com.android.camera.MenuHelper; import com.android.camera.ModePicker; import com.android.camera.OnClickAttr; import com.android.camera.R; +import com.android.camera.RotateDialogController; import com.android.camera.ShutterButton; import com.android.camera.SoundPlayer; import com.android.camera.Storage; import com.android.camera.Thumbnail; import com.android.camera.Util; +import com.android.camera.ui.Rotatable; import com.android.camera.ui.RotateImageView; +import com.android.camera.ui.RotateLayout; import com.android.camera.ui.SharePopup; -import android.app.AlertDialog; -import android.app.ProgressDialog; import android.content.ContentResolver; import android.content.Context; -import android.content.DialogInterface; import android.content.res.AssetFileDescriptor; +import android.content.pm.ActivityInfo; import android.content.res.Resources; import android.graphics.Bitmap; import android.graphics.BitmapFactory; @@ -47,7 +48,6 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.graphics.YuvImage; -import android.hardware.Camera; import android.hardware.Camera.Parameters; import android.hardware.Camera.Size; import android.hardware.Sensor; @@ -63,13 +63,13 @@ import android.view.Gravity; import android.view.Menu; import android.view.OrientationEventListener; import android.view.View; +import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; import java.io.ByteArrayOutputStream; -import java.io.FileNotFoundException; import java.io.File; import java.util.List; @@ -88,6 +88,9 @@ public class PanoramaActivity extends ActivityBase implements private static final int MSG_RESET_TO_PREVIEW_WITH_THUMBNAIL = 2; private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 3; private static final int MSG_RESET_TO_PREVIEW = 4; + private static final int MSG_CLEAR_SCREEN_DELAY = 5; + + private static final int SCREEN_DELAY = 2 * 60 * 1000; private static final String TAG = "PanoramaActivity"; private static final int PREVIEW_STOPPED = 0; @@ -109,7 +112,7 @@ public class PanoramaActivity extends ActivityBase implements private View mCaptureLayout; private View mReviewLayout; private ImageView mReview; - private TextView mCaptureIndicator; + private RotateLayout mCaptureIndicator; private PanoProgressBar mPanoProgressBar; private PanoProgressBar mSavingProgressBar; private View mFastIndicationBorder; @@ -121,10 +124,9 @@ public class PanoramaActivity extends ActivityBase implements private Object mWaitObject = new Object(); private String mPreparePreviewString; - private AlertDialog mAlertDialog; - private ProgressDialog mProgressDialog; private String mDialogTitle; - private String mDialogOk; + private String mDialogOkString; + private String mDialogPanoramaFailedString; private int mIndicatorColor; private int mIndicatorColorFast; @@ -177,6 +179,8 @@ public class PanoramaActivity extends ActivityBase implements private int mDeviceOrientation; private int mOrientationCompensation; + private RotateDialogController mRotateDialog; + private class MosaicJpeg { public MosaicJpeg(byte[] data, int width, int height) { this.data = data; @@ -251,8 +255,6 @@ public class PanoramaActivity extends ActivityBase implements super.onCreate(icicle); Window window = getWindow(); - window.setFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON, - WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); Util.enterLightsOutMode(window); Util.initializeScreenBrightness(window, getContentResolver()); @@ -271,7 +273,9 @@ public class PanoramaActivity extends ActivityBase implements mPreparePreviewString = getResources().getString(R.string.pano_dialog_prepare_preview); mDialogTitle = getResources().getString(R.string.pano_dialog_title); - mDialogOk = getResources().getString(R.string.dialog_ok); + mDialogOkString = getResources().getString(R.string.dialog_ok); + mDialogPanoramaFailedString = + getResources().getString(R.string.pano_dialog_panorama_failed); mMainHandler = new Handler() { @Override @@ -297,36 +301,28 @@ public class PanoramaActivity extends ActivityBase implements if (mPausing) { resetToPreview(); } else { - mAlertDialog.show(); + mRotateDialog.showAlertDialog( + mDialogTitle, mDialogPanoramaFailedString, + mDialogOkString, new Runnable() { + @Override + public void run() { + resetToPreview(); + }}, + null, null); } break; case MSG_RESET_TO_PREVIEW: onBackgroundThreadFinished(); resetToPreview(); + break; + case MSG_CLEAR_SCREEN_DELAY: + getWindow().clearFlags(WindowManager.LayoutParams. + FLAG_KEEP_SCREEN_ON); + break; } clearMosaicFrameProcessorIfNeeded(); } }; - - mAlertDialog = (new AlertDialog.Builder(this)) - .setTitle(mDialogTitle) - .setMessage(R.string.pano_dialog_panorama_failed) - .create(); - mAlertDialog.setCancelable(false); - mAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE, mDialogOk, - new DialogInterface.OnClickListener() { - @Override - public void onClick(DialogInterface dialog, int which) { - dialog.dismiss(); - resetToPreview(); - } - }); - } - - @Override - public void onStart() { - super.onStart(); - updateThumbnailButton(); } private void setupCamera() { @@ -577,6 +573,7 @@ public class PanoramaActivity extends ActivityBase implements mPanoProgressBar.setIndicatorWidth(20); mPanoProgressBar.setMaxProgress(DEFAULT_SWEEP_ANGLE); mPanoProgressBar.setVisibility(View.VISIBLE); + keepScreenOn(); } private void stopCapture(boolean aborted) { @@ -592,7 +589,7 @@ public class PanoramaActivity extends ActivityBase implements mSurfaceTexture.setOnFrameAvailableListener(null); if (!aborted && !mThreadRunning) { - showDialog(mPreparePreviewString); + mRotateDialog.showWaitingDialog(mPreparePreviewString); runBackgroundThread(new Thread() { @Override public void run() { @@ -612,6 +609,7 @@ public class PanoramaActivity extends ActivityBase implements } // do we have to wait for the thread to complete before enabling this? if (mModePicker != null) mModePicker.setEnabled(true); + keepScreenOnAwhile(); } private void showTooFastIndication() { @@ -682,7 +680,7 @@ public class PanoramaActivity extends ActivityBase implements mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication)); - mCaptureIndicator = (TextView) findViewById(R.id.pano_capture_indicator); + mCaptureIndicator = (RotateLayout) findViewById(R.id.pano_capture_indicator); mThumbnailView = (RotateImageView) findViewById(R.id.thumbnail); mThumbnailView.enableFilter(false); @@ -702,6 +700,24 @@ public class PanoramaActivity extends ActivityBase implements mShutterButton.setOnShutterButtonListener(this); mPanoLayout = findViewById(R.id.pano_layout); + + mRotateDialog = new RotateDialogController(this, R.layout.rotate_dialog); + + if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { + Rotatable[] rotateLayout = { + (Rotatable) findViewById(R.id.pano_pan_progress_bar_layout), + (Rotatable) findViewById(R.id.pano_capture_too_fast_textview_layout), + (Rotatable) findViewById(R.id.pano_review_saving_indication_layout), + (Rotatable) findViewById(R.id.pano_saving_progress_bar_layout), + (Rotatable) findViewById(R.id.pano_review_cancel_button_layout), + (Rotatable) mRotateDialog, + (Rotatable) mCaptureIndicator, + (Rotatable) mModePicker, + (Rotatable) mThumbnailView}; + for (Rotatable r : rotateLayout) { + r.setOrientation(270); + } + } } @Override @@ -755,6 +771,14 @@ public class PanoramaActivity extends ActivityBase implements t.start(); } + private void initThumbnailButton() { + // Load the thumbnail from the disk. + if (mThumbnail == null) { + mThumbnail = Thumbnail.loadFrom(new File(getFilesDir(), Thumbnail.LAST_THUMB_FILENAME)); + } + updateThumbnailButton(); + } + private void updateThumbnailButton() { // Update last image if URI is invalid and the storage is ready. ContentResolver contentResolver = getContentResolver(); @@ -801,12 +825,6 @@ public class PanoramaActivity extends ActivityBase implements reportProgress(); } - private void showDialog(String str) { - mProgressDialog = new ProgressDialog(this); - mProgressDialog.setMessage(str); - mProgressDialog.show(); - } - private void runBackgroundThread(Thread thread) { mThreadRunning = true; thread.start(); @@ -814,10 +832,7 @@ public class PanoramaActivity extends ActivityBase implements private void onBackgroundThreadFinished() { mThreadRunning = false; - if (mProgressDialog != null) { - mProgressDialog.dismiss(); - mProgressDialog = null; - } + mRotateDialog.dismissDialog(); } private void cancelHighResComputation() { @@ -951,6 +966,7 @@ public class PanoramaActivity extends ActivityBase implements mMosaicView.onPause(); clearMosaicFrameProcessorIfNeeded(); mOrientationEventListener.disable(); + resetScreenOn(); System.gc(); } @@ -968,11 +984,25 @@ public class PanoramaActivity extends ActivityBase implements // has to be decided by camera device. initMosaicFrameProcessorIfNeeded(); mMosaicView.onResume(); + + initThumbnailButton(); + keepScreenOnAwhile(); } + /** + * Generate the final mosaic image. + * + * @param highRes flag to indicate whether we want to get a high-res version. + * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation + * process is cancelled; and a MosaicJpeg with its isValid flag set to false if there + * is an error in generating the final mosaic. + */ public MosaicJpeg generateFinalMosaic(boolean highRes) { - if (mMosaicFrameProcessor.createMosaic(highRes) == Mosaic.MOSAIC_RET_CANCELLED) { + int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes); + if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) { return null; + } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) { + return new MosaicJpeg(); } byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21(); @@ -1021,9 +1051,10 @@ public class PanoramaActivity extends ActivityBase implements // the screen). if (mCameraState != PREVIEW_STOPPED) stopCameraPreview(); - int orientation = Util.getDisplayOrientation(Util.getDisplayRotation(this), - CameraHolder.instance().getBackCameraId()); - mCameraDevice.setDisplayOrientation(orientation); + // Set the display orientation to 0, so that the underlying mosaic library + // can always get undistorted mPreviewWidth x mPreviewHeight image data + // from SurfaceTexture. + mCameraDevice.setDisplayOrientation(0); setPreviewTexture(mSurfaceTexture); @@ -1044,4 +1075,26 @@ public class PanoramaActivity extends ActivityBase implements } mCameraState = PREVIEW_STOPPED; } + + @Override + public void onUserInteraction() { + super.onUserInteraction(); + if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile(); + } + + private void resetScreenOn() { + mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + + private void keepScreenOnAwhile() { + mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY); + } + + private void keepScreenOn() { + mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } } diff --git a/src/com/android/camera/ui/AbstractIndicatorButton.java b/src/com/android/camera/ui/AbstractIndicatorButton.java index a661586..0ff7b19 100644 --- a/src/com/android/camera/ui/AbstractIndicatorButton.java +++ b/src/com/android/camera/ui/AbstractIndicatorButton.java @@ -106,10 +106,10 @@ public abstract class AbstractIndicatorButton extends RotateImageView implements } @Override - public void setDegree(int degree) { - super.setDegree(degree); + public void setOrientation(int orientation) { + super.setOrientation(orientation); if (mPopup != null) { - mPopup.setOrientation(degree); + mPopup.setOrientation(orientation); } } diff --git a/src/com/android/camera/ui/ControlPanelLayout.java b/src/com/android/camera/ui/ControlPanelLayout.java index f85d955..24efb8b 100644 --- a/src/com/android/camera/ui/ControlPanelLayout.java +++ b/src/com/android/camera/ui/ControlPanelLayout.java @@ -16,7 +16,9 @@ package com.android.camera.ui; +import android.app.Activity; import android.content.Context; +import android.content.pm.ActivityInfo; import android.util.AttributeSet; import android.util.Log; import android.widget.RelativeLayout; @@ -25,8 +27,7 @@ import android.widget.RelativeLayout; * A layout which handles the the width of the control panel, which contains * the shutter button, thumbnail, front/back camera picker, and mode picker. * The purpose of this is to have a consistent width of control panel in camera, - * camcorder, and panorama modes. The control panel can also be GONE and the - * preview can expand to full-screen in panorama. + * camcorder, and panorama modes. */ public class ControlPanelLayout extends RelativeLayout { private static final String TAG = "ControlPanelLayout"; @@ -39,29 +40,43 @@ public class ControlPanelLayout extends RelativeLayout { protected void onMeasure(int widthSpec, int heightSpec) { int widthSpecSize = MeasureSpec.getSize(widthSpec); int heightSpecSize = MeasureSpec.getSize(heightSpec); - int widthMode = MeasureSpec.getMode(widthSpec); - int measuredWidth = 0; + int measuredSize = 0; + int mode, longSideSize, shortSideSize, specSize; - if (widthSpecSize > 0 && heightSpecSize > 0 && widthMode == MeasureSpec.AT_MOST) { + boolean isLandscape = (((Activity) getContext()).getRequestedOrientation() + == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE); + + if (isLandscape) { + mode = MeasureSpec.getMode(widthSpec); + longSideSize = widthSpecSize; + shortSideSize = heightSpecSize; + specSize = widthSpecSize; + } else { + mode = MeasureSpec.getMode(heightSpec); + longSideSize = heightSpecSize; + shortSideSize = widthSpecSize; + specSize = heightSpecSize; + } + + if (widthSpecSize > 0 && heightSpecSize > 0 && mode == MeasureSpec.AT_MOST) { // Calculate how big 4:3 preview occupies. Then deduct it from the // width of the parent. - measuredWidth = (int) (widthSpecSize - heightSpecSize / 3.0 * 4.0 - 16); + measuredSize = (int) (longSideSize - shortSideSize / 3.0 * 4.0); } else { - Log.e(TAG, "layout_width of ControlPanelLayout should be wrap_content"); + Log.e(TAG, "layout_xxx of ControlPanelLayout should be wrap_content"); } - // Make sure the width is bigger than the minimum width. - int minWidth = getSuggestedMinimumWidth(); - if (minWidth > measuredWidth) { - measuredWidth = minWidth; + // The width cannot be bigger than the constraint. + if (mode == MeasureSpec.AT_MOST && measuredSize > specSize) { + measuredSize = specSize; } - // The width cannot be bigger than the constraint. - if (widthMode == MeasureSpec.AT_MOST && measuredWidth > widthSpecSize) { - measuredWidth = widthSpecSize; + if (isLandscape) { + widthSpec = MeasureSpec.makeMeasureSpec(measuredSize, MeasureSpec.EXACTLY); + } else { + heightSpec = MeasureSpec.makeMeasureSpec(measuredSize, MeasureSpec.EXACTLY); } - super.onMeasure(MeasureSpec.makeMeasureSpec(measuredWidth, MeasureSpec.EXACTLY), - heightSpec); + super.onMeasure(widthSpec, heightSpec); } } diff --git a/src/com/android/camera/ui/EffectSettingPopup.java b/src/com/android/camera/ui/EffectSettingPopup.java index c4a4d49..a0f9be0 100644 --- a/src/com/android/camera/ui/EffectSettingPopup.java +++ b/src/com/android/camera/ui/EffectSettingPopup.java @@ -180,11 +180,19 @@ public class EffectSettingPopup extends AbstractSettingPopup implements @Override public void onItemClick(AdapterView<?> parent, View view, int index, long id) { + String value; if (parent == mSillyFacesGrid) { - String value = (String) mSillyFacesItem.get(index).get("value"); - mPreference.setValue(value); + value = (String) mSillyFacesItem.get(index).get("value"); } else if (parent == mBackgroundGrid) { - String value = (String) mBackgroundItem.get(index).get("value"); + value = (String) mBackgroundItem.get(index).get("value"); + } else { + return; + } + + // Tapping the selected effect will deselect it (clear effects). + if (value.equals(mPreference.getValue())) { + mPreference.setValue(mNoEffect); + } else { mPreference.setValue(value); } reloadPreference(); diff --git a/src/com/android/camera/ui/FaceView.java b/src/com/android/camera/ui/FaceView.java index 9018886..ed59682 100644 --- a/src/com/android/camera/ui/FaceView.java +++ b/src/com/android/camera/ui/FaceView.java @@ -29,7 +29,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.View; -public class FaceView extends View implements FocusIndicator { +public class FaceView extends View implements FocusIndicator, Rotatable { private final String TAG = "FaceView"; private final boolean LOGV = false; // The value for android.hardware.Camera.setDisplayOrientation. diff --git a/src/com/android/camera/ui/IndicatorControl.java b/src/com/android/camera/ui/IndicatorControl.java index 86b0cc9..cac38b8 100644 --- a/src/com/android/camera/ui/IndicatorControl.java +++ b/src/com/android/camera/ui/IndicatorControl.java @@ -35,7 +35,7 @@ import java.util.ArrayList; * A view that contains camera setting indicators. */ public abstract class IndicatorControl extends RelativeLayout implements - IndicatorButton.Listener, OtherSettingsPopup.Listener { + IndicatorButton.Listener, OtherSettingsPopup.Listener, Rotatable { private static final String TAG = "IndicatorControl"; public static final int MODE_CAMERA = 0; public static final int MODE_VIDEO = 1; @@ -45,7 +45,7 @@ public abstract class IndicatorControl extends RelativeLayout implements protected CameraPicker mCameraPicker; private PreferenceGroup mPreferenceGroup; - private int mDegree = 0; + private int mOrientation = 0; protected int mCurrentMode = MODE_CAMERA; @@ -61,15 +61,13 @@ public abstract class IndicatorControl extends RelativeLayout implements super(context, attrs); } - public void setDegree(int degree) { - mDegree = degree; + public void setOrientation(int orientation) { + mOrientation = orientation; int count = getChildCount(); for (int i = 0 ; i < count ; ++i) { View view = getChildAt(i); - if (view instanceof RotateImageView) { - ((RotateImageView) view).setDegree(degree); - } else if (view instanceof ZoomControl) { - ((ZoomControl) view).setDegree(degree); + if (view instanceof Rotatable) { + ((Rotatable) view).setOrientation(orientation); } } } diff --git a/src/com/android/camera/ui/IndicatorControlBar.java b/src/com/android/camera/ui/IndicatorControlBar.java index 6c2151d..8ab61fd 100644 --- a/src/com/android/camera/ui/IndicatorControlBar.java +++ b/src/com/android/camera/ui/IndicatorControlBar.java @@ -87,20 +87,19 @@ public class IndicatorControlBar extends IndicatorControl implements int count = getChildCount(); if (count == 0) return; - int width = right - left; + int height = bottom - top; - // First indicator will be CameraPicker if exists. - if (mCameraPicker != null) { - mCameraPicker.layout(0, padding, width, padding + width); - } + mSecondLevelIcon.layout(0, 0, height, height); // Layout the zoom control if required. - int offset = padding + width; // the padding and the icon height + int offset = padding + height; // the padding and the icon height if (mZoomControl != null) { - mZoomControl.layout(0, offset, width, bottom - top - offset); + mZoomControl.layout(offset, 0, right - left - offset, height); } - mSecondLevelIcon.layout(0, bottom - top - offset, width, bottom - top); + if (mCameraPicker != null) { + mCameraPicker.layout(right - left - offset, 0, right - left, height); + } } @Override diff --git a/src/com/android/camera/ui/IndicatorControlBarContainer.java b/src/com/android/camera/ui/IndicatorControlBarContainer.java index 6b60ace..3c907a8 100644 --- a/src/com/android/camera/ui/IndicatorControlBarContainer.java +++ b/src/com/android/camera/ui/IndicatorControlBarContainer.java @@ -43,14 +43,14 @@ public class IndicatorControlBarContainer extends IndicatorControlContainer { public IndicatorControlBarContainer(Context context, AttributeSet attrs) { super(context, attrs); mFadeIn = AnimationUtils.loadAnimation( - context, R.anim.grow_fade_in_from_top); + context, R.anim.first_level_fade_in); mFadeOut = AnimationUtils.loadAnimation( - context, R.anim.shrink_fade_out_from_bottom); + context, R.anim.first_level_fade_out); mFadeOut.setAnimationListener(mAnimationListener); mSecondLevelFadeIn = AnimationUtils.loadAnimation( - context, R.anim.grow_fade_in_from_bottom); + context, R.anim.second_level_fade_in); mSecondLevelFadeOut = AnimationUtils.loadAnimation( - context, R.anim.shrink_fade_out_from_top); + context, R.anim.second_level_fade_out); mSecondLevelFadeOut.setAnimationListener(mAnimationListener); } @@ -75,9 +75,9 @@ public class IndicatorControlBarContainer extends IndicatorControlContainer { secondLevelKeys, secondLevelOtherSettingKeys); } - public void setDegree(int degree) { - mIndicatorControlBar.setDegree(degree); - mSecondLevelIndicatorControlBar.setDegree(degree); + public void setOrientation(int orientation) { + mIndicatorControlBar.setOrientation(orientation); + mSecondLevelIndicatorControlBar.setOrientation(orientation); } @Override diff --git a/src/com/android/camera/ui/IndicatorControlWheel.java b/src/com/android/camera/ui/IndicatorControlWheel.java index af36e11..3b8bea4 100644 --- a/src/com/android/camera/ui/IndicatorControlWheel.java +++ b/src/com/android/camera/ui/IndicatorControlWheel.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.Paint; +import android.graphics.Path; import android.graphics.RectF; import android.os.Handler; import android.os.SystemClock; @@ -65,6 +66,7 @@ public class IndicatorControlWheel extends IndicatorControl implements private static final int TIME_LAPSE_ARC_WIDTH = 6; private final int HIGHLIGHT_COLOR; + private final int HIGHLIGHT_FAN_COLOR; private final int TIME_LAPSE_ARC_COLOR; // The center of the shutter button. @@ -115,6 +117,7 @@ public class IndicatorControlWheel extends IndicatorControl implements super(context, attrs); Resources resources = context.getResources(); HIGHLIGHT_COLOR = resources.getColor(R.color.review_control_pressed_color); + HIGHLIGHT_FAN_COLOR = resources.getColor(R.color.review_control_pressed_fan_color); TIME_LAPSE_ARC_COLOR = resources.getColor(R.color.time_lapse_arc); setWillNotDraw(false); @@ -135,9 +138,7 @@ public class IndicatorControlWheel extends IndicatorControl implements } } - @Override - public void onClick(View view) { - if (view == mZoomIcon) return; + private void changeIndicatorsLevel() { mPressedIndex = -1; dismissSettingPopup(); mInAnimation = true; @@ -145,6 +146,12 @@ public class IndicatorControlWheel extends IndicatorControl implements requestLayout(); } + @Override + public void onClick(View view) { + if (view == mZoomIcon) return; + changeIndicatorsLevel(); + } + public void initialize(Context context, PreferenceGroup group, boolean isZoomSupported, String[] keys, String[] otherSettingKeys) { mShutterButtonRadius = IndicatorControlWheelContainer.SHUTTER_BUTTON_RADIUS; @@ -291,7 +298,6 @@ public class IndicatorControlWheel extends IndicatorControl implements double increment = Math.toRadians(expectedAngle) - mChildRadians[mSecondLevelStartIndex]; for (int i = 0 ; i < getChildCount(); ++i) mChildRadians[i] += increment; - requestLayout(); } @Override @@ -309,7 +315,7 @@ public class IndicatorControlWheel extends IndicatorControl implements // The icons are spreaded on the left side of the shutter button. for (int i = 0; i < getChildCount(); ++i) { View view = getChildAt(i); - if (!view.isEnabled()) continue; + // We still need to show the disabled indicators in the second level. double radian = mChildRadians[i]; double startVisibleRadians = mInAnimation ? mStartVisibleRadians[1] @@ -317,8 +323,9 @@ public class IndicatorControlWheel extends IndicatorControl implements double endVisibleRadians = mInAnimation ? mEndVisibleRadians[1] : mEndVisibleRadians[mCurrentLevel]; - if ((radian < (startVisibleRadians - HIGHLIGHT_RADIANS / 2)) || - (radian > (endVisibleRadians + HIGHLIGHT_RADIANS / 2))) { + if ((!view.isEnabled() && (mCurrentLevel == 0)) + || (radian < (startVisibleRadians - HIGHLIGHT_RADIANS / 2)) + || (radian > (endVisibleRadians + HIGHLIGHT_RADIANS / 2))) { view.setVisibility(View.GONE); continue; } @@ -415,19 +422,35 @@ public class IndicatorControlWheel extends IndicatorControl implements @Override protected void onDraw(Canvas canvas) { - // Draw highlight. - float delta = mStrokeWidth * 0.5f; - float radius = (float) (mWheelRadius + mStrokeWidth * 0.5 + EDGE_STROKE_WIDTH); - mBackgroundRect.set(mCenterX - radius, mCenterY - radius, mCenterX + radius, - mCenterY + radius); - int selectedIndex = getSelectedIndicatorIndex(); // Draw the highlight arc if an indicator is selected or being pressed. - if (selectedIndex >= 0) { + if (selectedIndex >= 0) { int degree = (int) Math.toDegrees(mChildRadians[selectedIndex]); + float innerR = (float) mShutterButtonRadius; + float outerR = (float) (mShutterButtonRadius + mStrokeWidth + + EDGE_STROKE_WIDTH * 0.5); + + // Construct the path of the fan-shaped semi-transparent area. + Path fanPath = new Path(); + mBackgroundRect.set(mCenterX - innerR, mCenterY - innerR, + mCenterX + innerR, mCenterY + innerR); + fanPath.arcTo(mBackgroundRect, -degree + HIGHLIGHT_DEGREES / 2, + -HIGHLIGHT_DEGREES); + mBackgroundRect.set(mCenterX - outerR, mCenterY - outerR, + mCenterX + outerR, mCenterY + outerR); + fanPath.arcTo(mBackgroundRect, -degree - HIGHLIGHT_DEGREES / 2, + HIGHLIGHT_DEGREES); + fanPath.close(); + mBackgroundPaint.setStrokeWidth(HIGHLIGHT_WIDTH); - mBackgroundPaint.setStrokeCap(Paint.Cap.ROUND); + mBackgroundPaint.setStrokeCap(Paint.Cap.SQUARE); + mBackgroundPaint.setStyle(Paint.Style.FILL_AND_STROKE); + mBackgroundPaint.setColor(HIGHLIGHT_FAN_COLOR); + canvas.drawPath(fanPath, mBackgroundPaint); + + // Draw the highlight edge + mBackgroundPaint.setStyle(Paint.Style.STROKE); mBackgroundPaint.setColor(HIGHLIGHT_COLOR); canvas.drawArc(mBackgroundRect, -degree - HIGHLIGHT_DEGREES / 2, HIGHLIGHT_DEGREES, false, mBackgroundPaint); @@ -492,4 +515,10 @@ public class IndicatorControlWheel extends IndicatorControl implements invalidate(); } } + + public void dismissSecondLevelIndicator() { + if (mCurrentLevel == 1) { + changeIndicatorsLevel(); + } + } } diff --git a/src/com/android/camera/ui/IndicatorControlWheelContainer.java b/src/com/android/camera/ui/IndicatorControlWheelContainer.java index 14539da..a10136b 100644 --- a/src/com/android/camera/ui/IndicatorControlWheelContainer.java +++ b/src/com/android/camera/ui/IndicatorControlWheelContainer.java @@ -191,9 +191,9 @@ public class IndicatorControlWheelContainer extends IndicatorControlContainer { } @Override - public void setDegree(int degree) { - mIndicatorControlWheel.setDegree(degree); - mZoomControlWheel.setDegree(degree); + public void setOrientation(int orientation) { + mIndicatorControlWheel.setOrientation(orientation); + mZoomControlWheel.setOrientation(orientation); } public void startTimeLapseAnimation(int timeLapseInterval, long startTime) { @@ -223,6 +223,6 @@ public class IndicatorControlWheelContainer extends IndicatorControlContainer { @Override public void dismissSecondLevelIndicator() { - // TODO: back to first-level indicator set. + mIndicatorControlWheel.dismissSecondLevelIndicator(); } } diff --git a/src/com/android/camera/ui/OneRowGridView.java b/src/com/android/camera/ui/OneRowGridView.java new file mode 100644 index 0000000..5e37d35 --- /dev/null +++ b/src/com/android/camera/ui/OneRowGridView.java @@ -0,0 +1,43 @@ +/* + * Copyright (C) 2011 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 com.android.camera.R; +import com.android.camera.Util; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.GridView; + +public class OneRowGridView extends GridView { + public OneRowGridView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Once we know the number of children in this view, we have to set + // the correct width and height for containing the icons in one row. + int n = getChildCount(); + if (n == 0) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + } else { + setMeasuredDimension((n * getChildAt(0).getMeasuredWidth()), + getMeasuredHeight()); + } + } +} diff --git a/src/com/android/camera/ui/RightAlignedHorizontalScrollView.java b/src/com/android/camera/ui/RightAlignedHorizontalScrollView.java new file mode 100644 index 0000000..cd4c5f5 --- /dev/null +++ b/src/com/android/camera/ui/RightAlignedHorizontalScrollView.java @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2011 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 android.content.Context; +import android.view.View; +import android.util.AttributeSet; +import android.widget.HorizontalScrollView; + +public class RightAlignedHorizontalScrollView extends HorizontalScrollView { + + public RightAlignedHorizontalScrollView(Context context) { + super(context); + } + + public RightAlignedHorizontalScrollView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onLayout(boolean changed, int l, int t, int r, int b) { + super.onLayout(changed, l, t, r, b); + if (changed) { + // Get the width of the child, i.e. the LinearLayout, and scroll to + // the rightmost position. + View child = getChildAt(0); + if (child != null) scrollTo(child.getWidth(), 0); + } + } +} diff --git a/src/com/android/camera/ui/RotateImageView.java b/src/com/android/camera/ui/RotateImageView.java index 390d705..f47a26b 100644 --- a/src/com/android/camera/ui/RotateImageView.java +++ b/src/com/android/camera/ui/RotateImageView.java @@ -16,8 +16,6 @@ package com.android.camera.ui; -import com.android.camera.ui.Rotatable; - import android.content.Context; import android.graphics.Bitmap; import android.graphics.Canvas; @@ -66,12 +64,8 @@ public class RotateImageView extends ColorFilterImageView implements Rotatable { return mTargetDegree; } - public void setOrientation(int orientation) { - setDegree(orientation); - } - // Rotate the view counter-clockwise - public void setDegree(int degree) { + public void setOrientation(int degree) { // make sure in the range of [0, 359] degree = degree >= 0 ? degree % 360 : degree % 360 + 360; if (degree == mTargetDegree) return; diff --git a/src/com/android/camera/ui/RotateLayout.java b/src/com/android/camera/ui/RotateLayout.java index 24815f8..e66e71a 100644 --- a/src/com/android/camera/ui/RotateLayout.java +++ b/src/com/android/camera/ui/RotateLayout.java @@ -16,8 +16,6 @@ package com.android.camera.ui; -import com.android.camera.ui.Rotatable; - import android.content.Context; import android.util.AttributeSet; import android.view.View; diff --git a/src/com/android/camera/ui/SecondLevelIndicatorControlBar.java b/src/com/android/camera/ui/SecondLevelIndicatorControlBar.java index c9037d9..1fb9a80 100644 --- a/src/com/android/camera/ui/SecondLevelIndicatorControlBar.java +++ b/src/com/android/camera/ui/SecondLevelIndicatorControlBar.java @@ -39,7 +39,7 @@ public class SecondLevelIndicatorControlBar extends IndicatorControl implements private View mDivider; // the divider line private View mIndicatorHighlight; // the side highlight bar private View mPopupedIndicator; - int mDegree = 0; + int mOrientation = 0; int mSelectedIndex = -1; // There are some views in the ViewGroup before adding the indicator buttons, // such as Close icon, divider line and the hightlight bar, we need to @@ -64,7 +64,7 @@ public class SecondLevelIndicatorControlBar extends IndicatorControl implements setPreferenceGroup(group); mNonIndicatorButtonCount = getChildCount(); addControls(keys, otherSettingKeys); - if (mDegree != 0) setDegree(mDegree); + if (mOrientation != 0) setOrientation(mOrientation); } public void onClick(View view) { @@ -73,20 +73,20 @@ public class SecondLevelIndicatorControlBar extends IndicatorControl implements OnIndicatorEventListener.EVENT_LEAVE_SECOND_LEVEL_INDICATOR_BAR); } - private int getTouchViewIndex(int y, int height) { + private int getTouchViewIndex(int x, int width) { // If the current touch location is on close icon and above. - if (y < mCloseIcon.getBottom()) return indexOfChild(mCloseIcon); + if (x > mCloseIcon.getLeft()) return indexOfChild(mCloseIcon); // Calculate if the touch event is on the indicator buttons. int count = getChildCount(); if (count == mNonIndicatorButtonCount) return -1; // The baseline will be the first indicator button's top minus spacing. View firstIndicatorButton = getChildAt(mNonIndicatorButtonCount); - int baselineY = firstIndicatorButton.getTop() - (ICON_SPACING / 2); - if (y < baselineY) return -1; - int iconHeight = firstIndicatorButton.getMeasuredHeight(); - int buttonRange = iconHeight + ICON_SPACING; - return (mNonIndicatorButtonCount + (y - baselineY) / buttonRange); + int baselineX = firstIndicatorButton.getRight() + (ICON_SPACING / 2); + if (x > baselineX) return -1; + int iconWidth = firstIndicatorButton.getMeasuredWidth(); + int buttonRange = iconWidth + ICON_SPACING; + return (mNonIndicatorButtonCount + ((baselineX - x) / buttonRange)); } @Override @@ -98,12 +98,12 @@ public class SecondLevelIndicatorControlBar extends IndicatorControl implements double x = (double) event.getX(); double y = (double) event.getY(); - int height = getHeight(); - if (height == 0) return false; // the event is sent before onMeasure() - if (x > getWidth()) x = getWidth(); - if (y >= height) y = height - 1; + int width = getWidth(); + if (width == 0) return false; // the event is sent before onMeasure() + if (x > width) x = width; + if (y >= getHeight()) y = getHeight() - 1; - int index = getTouchViewIndex((int) y, height); + int index = getTouchViewIndex((int) x, width); if (index == -1) return true; View b = getChildAt(index); b.dispatchTouchEvent(event); @@ -151,9 +151,9 @@ public class SecondLevelIndicatorControlBar extends IndicatorControl implements } @Override - public void setDegree(int degree) { - mDegree = degree; - super.setDegree(degree); + public void setOrientation(int orientation) { + mOrientation = orientation; + super.setOrientation(orientation); } @Override @@ -163,26 +163,26 @@ public class SecondLevelIndicatorControlBar extends IndicatorControl implements if (count == 0) return; int width = right - left; int height = bottom - top; - int iconHeight = mCloseIcon.getMeasuredHeight(); - int padding = getPaddingTop(); - - // The first icon is close button. - int offsetY = padding; - mCloseIcon.layout(0, padding, width, (padding + iconHeight)); - - // And layout the divider line. - offsetY += (iconHeight + padding); - mDivider.layout(padding, offsetY, - (width - padding), (offsetY + mDivider.getMeasuredHeight())); + int iconWidth = mCloseIcon.getMeasuredWidth(); + int padding = getPaddingLeft(); // Layout from the last icon up. - int startY = height - iconHeight - padding; - int decrement = iconHeight + ICON_SPACING; + int offsetX = padding; + int increment = iconWidth + ICON_SPACING; for (int i = count - 1; i >= mNonIndicatorButtonCount; --i) { - getChildAt(i).layout(0, startY, width, startY + iconHeight); - startY -= decrement; + getChildAt(i).layout(offsetX, 0, offsetX + iconWidth, height); + offsetX += increment; } + // And layout the divider line. + offsetX = width - iconWidth - 2 * padding; + mDivider.layout(offsetX, padding, (offsetX + mDivider.getMeasuredWidth()), + (height - padding)); + + offsetX = width - iconWidth - padding; + // The first icon is close button. + mCloseIcon.layout(offsetX, 0, (offsetX + iconWidth), height); + // Hightlight the selected indicator if exists. if (mPopupedIndicator == null) { mIndicatorHighlight.setVisibility(View.GONE); @@ -190,9 +190,9 @@ public class SecondLevelIndicatorControlBar extends IndicatorControl implements mIndicatorHighlight.setVisibility(View.VISIBLE); // Keep the top and bottom of the hightlight the same as // the 'active' indicator button. - mIndicatorHighlight.layout(0, mPopupedIndicator.getTop(), - mIndicatorHighlight.getLayoutParams().width, - mPopupedIndicator.getBottom()); + mIndicatorHighlight.layout(mPopupedIndicator.getLeft(), 0, + mPopupedIndicator.getRight(), + mIndicatorHighlight.getLayoutParams().height); } } diff --git a/src/com/android/camera/ui/SharePopup.java b/src/com/android/camera/ui/SharePopup.java index de78f66..134b7c0 100644 --- a/src/com/android/camera/ui/SharePopup.java +++ b/src/com/android/camera/ui/SharePopup.java @@ -23,6 +23,7 @@ import android.app.Activity; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; @@ -38,7 +39,7 @@ import android.view.ViewGroup.LayoutParams; import android.view.WindowManager; import android.widget.AdapterView; import android.widget.ImageView; -import android.widget.ListView; +import android.widget.GridView; import android.widget.PopupWindow; import android.widget.RelativeLayout; import android.widget.SimpleAdapter; @@ -50,7 +51,7 @@ import java.util.Map; // A popup window that contains a big thumbnail and a list of apps to share. public class SharePopup extends PopupWindow implements View.OnClickListener, - View.OnTouchListener, AdapterView.OnItemClickListener { + View.OnTouchListener, AdapterView.OnItemClickListener, Rotatable { private static final String TAG = "SharePopup"; private static final String ADAPTER_COLUMN_ICON = "icon"; private Context mContext; @@ -65,7 +66,7 @@ public class SharePopup extends PopupWindow implements View.OnClickListener, // A view that contains a list of application icons and the share view. private View mRootView; // The list of the application icons. - private ListView mShareList; + private GridView mShareList; // A rotated view that contains the thumbnail. private RotateLayout mThumbnailRotateLayout; private RotateLayout mGotoGalleryRotate; @@ -114,8 +115,7 @@ public class SharePopup extends PopupWindow implements View.OnClickListener, // This is required because popup window is full screen. sharePopup.setOnTouchListener(this); mThumbnailRotateLayout = (RotateLayout) sharePopup.findViewById(R.id.thumbnail_rotate_layout); - mShareList = (ListView) sharePopup.findViewById(R.id.share_list); - mShareList.setDivider(null); + mShareList = (GridView) sharePopup.findViewById(R.id.share_list); mThumbnail = (ImageView) sharePopup.findViewById(R.id.thumbnail); mThumbnail.setImageBitmap(bitmap); mShareView = (ViewGroup) sharePopup.findViewById(R.id.share_view); @@ -279,10 +279,19 @@ public class SharePopup extends PopupWindow implements View.OnClickListener, items.add(map); mComponent.add(component); } + + // On phone UI, we have to know how many icons in the grid view before + // the view is measured. + if (((Activity) mContext).getRequestedOrientation() + == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT) { + mShareList.setNumColumns(items.size()); + } + SimpleAdapter listItemAdapter = new MySimpleAdapter(mContext, items, R.layout.share_icon, new String[] {ADAPTER_COLUMN_ICON}, new int[] {R.id.icon}); + listItemAdapter.setViewBinder(mViewBinder); mShareList.setAdapter(listItemAdapter); mShareList.setOnItemClickListener(this); diff --git a/src/com/android/camera/ui/ZoomControl.java b/src/com/android/camera/ui/ZoomControl.java index 1809d18..f2971cd 100644 --- a/src/com/android/camera/ui/ZoomControl.java +++ b/src/com/android/camera/ui/ZoomControl.java @@ -29,7 +29,7 @@ import android.widget.RelativeLayout; * A view that contains camera zoom control which could adjust the zoom in/out * if the camera supports zooming. */ -public abstract class ZoomControl extends RelativeLayout { +public abstract class ZoomControl extends RelativeLayout implements Rotatable { // The states of zoom button. public static final int ZOOM_IN = 0; public static final int ZOOM_OUT = 1; @@ -42,7 +42,7 @@ public abstract class ZoomControl extends RelativeLayout { protected ImageView mZoomOut; protected ImageView mZoomSlider; protected int mSliderPosition = 0; - protected int mDegree; + protected int mOrientation; private Handler mHandler; public interface OnZoomChangedListener { @@ -208,13 +208,13 @@ public abstract class ZoomControl extends RelativeLayout { return true; } - public void setDegree(int degree) { - mDegree = degree; + public void setOrientation(int orientation) { + mOrientation = orientation; int count = getChildCount(); for (int i = 0 ; i < count ; ++i) { View view = getChildAt(i); if (view instanceof RotateImageView) { - ((RotateImageView) view).setDegree(degree); + ((RotateImageView) view).setOrientation(orientation); } } } diff --git a/src/com/android/camera/ui/ZoomControlBar.java b/src/com/android/camera/ui/ZoomControlBar.java index 08042d4..4e572cf 100644 --- a/src/com/android/camera/ui/ZoomControlBar.java +++ b/src/com/android/camera/ui/ZoomControlBar.java @@ -36,9 +36,9 @@ public class ZoomControlBar extends ZoomControl { private View mBar; private boolean mStartChanging; private int mSliderLength; - private int mHeight; - private int mIconHeight; - private int mTotalIconHeight; + private int mWidth; + private int mIconWidth; + private int mTotalIconWidth; public ZoomControlBar(Context context, AttributeSet attrs) { super(context, attrs); @@ -53,16 +53,16 @@ public class ZoomControlBar extends ZoomControl { mBar.setActivated(activated); } - private int getSliderPosition(int y) { + private int getSliderPosition(int x) { // Calculate the absolute offset of the slider in the zoom control bar. // For left-hand users, as the device is rotated for 180 degree for // landscape mode, the zoom-in bottom should be on the top, so the // position should be reversed. int pos; // the relative position in the zoom slider bar - if (mDegree == 180) { - pos = y - mTotalIconHeight; + if (mOrientation == 90) { + pos = mWidth - mTotalIconWidth - x; } else { - pos = mHeight - mTotalIconHeight - y; + pos = x - mTotalIconWidth; } if (pos < 0) pos = 0; if (pos > mSliderLength) pos = mSliderLength; @@ -71,15 +71,15 @@ public class ZoomControlBar extends ZoomControl { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mHeight = h; - mIconHeight = mZoomIn.getMeasuredHeight(); - mTotalIconHeight = mIconHeight + ICON_SPACING; - mSliderLength = mHeight - (2 * mTotalIconHeight); + mWidth = w; + mIconWidth = mZoomIn.getMeasuredWidth(); + mTotalIconWidth = mIconWidth + ICON_SPACING; + mSliderLength = mWidth - (2 * mTotalIconWidth); } @Override public boolean dispatchTouchEvent(MotionEvent event) { - if (!isEnabled() || (mHeight == 0)) return false; + if (!isEnabled() || (mWidth == 0)) return false; int action = event.getAction(); switch (action) { @@ -94,7 +94,7 @@ public class ZoomControlBar extends ZoomControl { setActivated(true); mStartChanging = false; case MotionEvent.ACTION_MOVE: - int pos = getSliderPosition((int) event.getY()); + int pos = getSliderPosition((int) event.getX()); if (!mStartChanging) { // Make sure the movement is large enough before we start // changing the zoom. @@ -114,18 +114,18 @@ public class ZoomControlBar extends ZoomControl { } @Override - public void setDegree(int degree) { + public void setOrientation(int orientation) { // layout for the left-hand camera control - if ((degree == 180) || (mDegree == 180)) requestLayout(); - super.setDegree(degree); + if ((orientation == 90) || (mOrientation == 90)) requestLayout(); + super.setOrientation(orientation); } @Override protected void onLayout( boolean changed, int left, int top, int right, int bottom) { if (mZoomMax == 0) return; - int width = right - left; - mBar.layout(0, mTotalIconHeight, width, mHeight - mTotalIconHeight); + int height = bottom - top; + mBar.layout(mTotalIconWidth, 0, mWidth - mTotalIconWidth, height); // For left-hand users, as the device is rotated for 180 degree, // the zoom-in button should be on the top. int pos; // slider position @@ -135,18 +135,18 @@ public class ZoomControlBar extends ZoomControl { } else { sliderPosition = (int) ((double) mSliderLength * mZoomIndex / mZoomMax); } - if (mDegree == 180) { - mZoomOut.layout(0, 0, width, mIconHeight); - mZoomIn.layout(0, mHeight - mIconHeight, width, mHeight); - pos = mBar.getTop() + sliderPosition; + if (mOrientation == 90) { + mZoomIn.layout(0, 0, mIconWidth, height); + mZoomOut.layout(mWidth - mIconWidth, 0, mWidth, height); + pos = mBar.getRight() - sliderPosition; } else { - mZoomIn.layout(0, 0, width, mIconHeight); - mZoomOut.layout(0, mHeight - mIconHeight, width, mHeight); - pos = mBar.getBottom() - sliderPosition; + mZoomOut.layout(0, 0, mIconWidth, height); + mZoomIn.layout(mWidth - mIconWidth, 0, mWidth, height); + pos = mBar.getLeft() + sliderPosition; } - int sliderHeight = mZoomSlider.getMeasuredHeight(); - mZoomSlider.layout(0, (pos - sliderHeight / 2), - width, (pos + sliderHeight / 2)); + int sliderWidth = mZoomSlider.getMeasuredWidth(); + mZoomSlider.layout((pos - sliderWidth / 2), 0, + (pos + sliderWidth / 2), height); } @Override |