diff options
Diffstat (limited to 'src')
18 files changed, 965 insertions, 973 deletions
diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java index 64f3681de..7c7ed389e 100644 --- a/src/com/android/camera/CameraSettings.java +++ b/src/com/android/camera/CameraSettings.java @@ -235,8 +235,8 @@ public class CameraSettings { private void buildExposureCompensation( PreferenceGroup group, IconListPreference exposure) { - int max = Math.min(3, mParameters.getMaxExposureCompensation()); - int min = Math.max(-3, mParameters.getMinExposureCompensation()); + int max = mParameters.getMaxExposureCompensation(); + int min = mParameters.getMinExposureCompensation(); if (max == 0 && min == 0) { removePreference(group, exposure.getKey()); return; @@ -244,8 +244,8 @@ public class CameraSettings { float step = mParameters.getExposureCompensationStep(); // show only integer values for exposure compensation - int maxValue = (int) FloatMath.floor(max * step); - int minValue = (int) FloatMath.ceil(min * step); + int maxValue = Math.min(3, (int) FloatMath.floor(max * step)); + int minValue = Math.max(-3, (int) FloatMath.ceil(min * step)); CharSequence entries[] = new CharSequence[maxValue - minValue + 1]; CharSequence entryValues[] = new CharSequence[maxValue - minValue + 1]; int[] icons = new int[maxValue - minValue + 1]; diff --git a/src/com/android/camera/Exif.java b/src/com/android/camera/Exif.java index 605556599..ee39d675e 100644 --- a/src/com/android/camera/Exif.java +++ b/src/com/android/camera/Exif.java @@ -18,9 +18,7 @@ package com.android.camera; import android.util.Log; -import com.android.gallery3d.exif.ExifInvalidFormatException; -import com.android.gallery3d.exif.ExifParser; -import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.exif.ExifInterface; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -31,44 +29,23 @@ public class Exif { // Returns the degrees in clockwise. Values are 0, 90, 180, or 270. public static int getOrientation(byte[] jpeg) { - if (jpeg == null) return 0; + if (jpeg == null) { + return 0; + } + ExifInterface exif = new ExifInterface(); InputStream is = new ByteArrayInputStream(jpeg); - try { - ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0); - int event = parser.next(); - while(event != ExifParser.EVENT_END) { - if (event == ExifParser.EVENT_NEW_TAG) { - ExifTag tag = parser.getTag(); - if (tag.getTagId() == ExifTag.TAG_ORIENTATION && - tag.hasValue()) { - int orient = (int) tag.getValueAt(0); - switch (orient) { - case ExifTag.Orientation.TOP_LEFT: - return 0; - case ExifTag.Orientation.BOTTOM_LEFT: - return 180; - case ExifTag.Orientation.RIGHT_TOP: - return 90; - case ExifTag.Orientation.RIGHT_BOTTOM: - return 270; - default: - Log.i(TAG, "Unsupported orientation"); - return 0; - } - } - } - event = parser.next(); + exif.readExif(is); + Integer val = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION); + if (val == null) { + return 0; + } else { + return ExifInterface.getRotationForOrientationValue(val.shortValue()); } - Log.i(TAG, "Orientation not found"); - return 0; } catch (IOException e) { Log.w(TAG, "Failed to read EXIF orientation", e); return 0; - } catch (ExifInvalidFormatException e) { - Log.w(TAG, "Failed to read EXIF orientation", e); - return 0; } } } diff --git a/src/com/android/camera/PanoramaModule.java b/src/com/android/camera/PanoramaModule.java index 623d96dc6..dc27f56f1 100644 --- a/src/com/android/camera/PanoramaModule.java +++ b/src/com/android/camera/PanoramaModule.java @@ -60,10 +60,7 @@ import com.android.camera.ui.PopupManager; import com.android.camera.ui.Rotatable; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.exif.ExifData; -import com.android.gallery3d.exif.ExifInvalidFormatException; -import com.android.gallery3d.exif.ExifOutputStream; -import com.android.gallery3d.exif.ExifReader; +import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.exif.ExifTag; import com.android.gallery3d.ui.GLRootView; import com.android.gallery3d.util.UsageStatistics; @@ -913,31 +910,18 @@ public class PanoramaModule implements CameraModule, mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken); String filepath = Storage.generateFilepath(filename); - ExifOutputStream out = null; - InputStream is = null; + ExifInterface exif = new ExifInterface(); try { - is = new ByteArrayInputStream(jpegData); - ExifReader reader = new ExifReader(); - ExifData data = reader.read(is); - - // Add Exif tags. - data.addGpsDateTimeStampTag(mTimeTaken); - data.addDateTimeStampTag(ExifTag.TAG_DATE_TIME, mTimeTaken, TimeZone.getDefault()); - data.addTag(ExifTag.TAG_ORIENTATION). - setValue(getExifOrientation(orientation)); - - out = new ExifOutputStream(new FileOutputStream(filepath)); - out.setExifData(data); - out.write(jpegData); + exif.readExif(jpegData); + exif.addGpsDateTimeStampTag(mTimeTaken); + exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, mTimeTaken, + TimeZone.getDefault()); + exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION, + ExifInterface.getOrientationValueForRotation(orientation))); + exif.writeExif(jpegData, filepath); } catch (IOException e) { - Log.e(TAG, "Cannot set EXIF for " + filepath, e); + Log.e(TAG, "Cannot set exif for " + filepath, e); Storage.writeFile(filepath, jpegData); - } catch (ExifInvalidFormatException e) { - Log.e(TAG, "Cannot set EXIF for " + filepath, e); - Storage.writeFile(filepath, jpegData); - } finally { - Util.closeSilently(out); - Util.closeSilently(is); } int jpegLength = (int) (new File(filepath).length()); @@ -947,21 +931,6 @@ public class PanoramaModule implements CameraModule, return null; } - private static int getExifOrientation(int orientation) { - switch (orientation) { - case 0: - return ExifTag.Orientation.TOP_LEFT; - case 90: - return ExifTag.Orientation.RIGHT_TOP; - case 180: - return ExifTag.Orientation.BOTTOM_LEFT; - case 270: - return ExifTag.Orientation.RIGHT_BOTTOM; - default: - throw new AssertionError("invalid: " + orientation); - } - } - private void clearMosaicFrameProcessorIfNeeded() { if (!mPaused || mThreadRunning) return; // Only clear the processor if it is initialized by this activity diff --git a/src/com/android/camera/VideoController.java b/src/com/android/camera/VideoController.java new file mode 100644 index 000000000..8bde8090f --- /dev/null +++ b/src/com/android/camera/VideoController.java @@ -0,0 +1,36 @@ +/* + * 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. + */ + +package com.android.camera; + +import android.view.View; + +import com.android.camera.ShutterButton.OnShutterButtonListener; + +public interface VideoController extends OnShutterButtonListener { + + public void onReviewDoneClicked(View view); + public void onReviewCancelClicked(View viwe); + public void onReviewPlayClicked(View view); + + public boolean isVideoCaptureIntent(); + + public int onZoomChanged(int index); + + public void onSingleTapUp(View view, int x, int y); + + public void stopPreview(); +} diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java index ab53e1546..aa3a80716 100644 --- a/src/com/android/camera/VideoMenu.java +++ b/src/com/android/camera/VideoMenu.java @@ -33,11 +33,10 @@ public class VideoMenu extends PieController ListPrefSettingPopup.Listener, TimeIntervalPopup.Listener { - - private static String TAG = "CAM_videocontrol"; + private static String TAG = "CAM_VideoMenu"; private static float FLOAT_PI_DIVIDED_BY_TWO = (float) Math.PI / 2; - private VideoModule mModule; + private VideoUI mUI; private String[] mOtherKeys; private AbstractSettingPopup mPopup; @@ -46,9 +45,9 @@ public class VideoMenu extends PieController private static final int POPUP_SECOND_LEVEL = 2; private int mPopupStatus; - public VideoMenu(CameraActivity activity, VideoModule module, PieRenderer pie) { + public VideoMenu(CameraActivity activity, VideoUI ui, PieRenderer pie) { super(activity, pie); - mModule = module; + mUI = ui; } public void initialize(PreferenceGroup group) { @@ -60,7 +59,7 @@ public class VideoMenu extends PieController addItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, FLOAT_PI_DIVIDED_BY_TWO - sweep, sweep); addItem(CameraSettings.KEY_WHITE_BALANCE, 3 * FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); PieItem item = makeItem(R.drawable.ic_switch_video_facing_holo_light); - item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); + item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO + sweep, sweep); item.setOnClickListener(new OnClickListener() { @Override @@ -81,7 +80,8 @@ public class VideoMenu extends PieController CameraSettings.KEY_VIDEO_EFFECT, CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL, CameraSettings.KEY_VIDEO_QUALITY, - CameraSettings.KEY_RECORD_LOCATION}; + CameraSettings.KEY_RECORD_LOCATION + }; item = makeItem(R.drawable.ic_settings_holo_light); item.setFixedSlice(FLOAT_PI_DIVIDED_BY_TWO * 3, sweep); @@ -92,17 +92,12 @@ public class VideoMenu extends PieController initializePopup(); mPopupStatus = POPUP_FIRST_LEVEL; } - mModule.showPopup(mPopup); + mUI.showPopup(mPopup); } }); mRenderer.addItem(item); } - protected void setCameraId(int cameraId) { - ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID); - pref.setValue("" + cameraId); - } - @Override public void reloadPreferences() { super.reloadPreferences(); @@ -126,7 +121,7 @@ public class VideoMenu extends PieController public void onListPrefChanged(ListPreference pref) { if (mPopup != null) { if (mPopupStatus == POPUP_SECOND_LEVEL) { - mModule.dismissPopup(true); + mUI.dismissPopup(true); } } super.onSettingChanged(pref); @@ -152,7 +147,7 @@ public class VideoMenu extends PieController if (mPopupStatus == POPUP_SECOND_LEVEL) { initializePopup(); mPopupStatus = POPUP_FIRST_LEVEL; - if (topPopupOnly) mModule.showPopup(mPopup); + if (topPopupOnly) mUI.showPopup(mPopup); } } @@ -170,17 +165,17 @@ public class VideoMenu extends PieController R.layout.time_interval_popup, null, false); timeInterval.initialize((IconListPreference) pref); timeInterval.setSettingChangedListener(this); - mModule.dismissPopup(true); + mUI.dismissPopup(true); mPopup = timeInterval; } else { ListPrefSettingPopup basic = (ListPrefSettingPopup) inflater.inflate( R.layout.list_pref_setting_popup, null, false); basic.initialize(pref); basic.setSettingChangedListener(this); - mModule.dismissPopup(true); + mUI.dismissPopup(true); mPopup = basic; } - mModule.showPopup(mPopup); + mUI.showPopup(mPopup); mPopupStatus = POPUP_SECOND_LEVEL; } diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java index 4ae46a897..115b7d0a7 100644 --- a/src/com/android/camera/VideoModule.java +++ b/src/com/android/camera/VideoModule.java @@ -47,33 +47,15 @@ import android.os.SystemClock; import android.provider.MediaStore; import android.provider.MediaStore.Video; import android.util.Log; -import android.view.Gravity; import android.view.KeyEvent; -import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.OrientationEventListener; -import android.view.SurfaceHolder; import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; import android.view.WindowManager; -import android.widget.FrameLayout; -import android.widget.FrameLayout.LayoutParams; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; import android.widget.Toast; -import com.android.camera.ui.AbstractSettingPopup; -import com.android.camera.ui.PieRenderer; import com.android.camera.ui.PopupManager; -import com.android.camera.ui.PreviewSurfaceView; -import com.android.camera.ui.RenderOverlay; -import com.android.camera.ui.Rotatable; -import com.android.camera.ui.RotateImageView; -import com.android.camera.ui.RotateLayout; import com.android.camera.ui.RotateTextToast; -import com.android.camera.ui.ZoomRenderer; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.util.AccessibilityUtils; @@ -87,13 +69,12 @@ import java.util.Iterator; import java.util.List; public class VideoModule implements CameraModule, + VideoController, CameraPreference.OnPreferenceChangedListener, ShutterButton.OnShutterButtonListener, MediaRecorder.OnErrorListener, MediaRecorder.OnInfoListener, - EffectsRecorder.EffectsListener, - PieRenderer.PieListener, - PreviewGestures.SingleTapListener { + EffectsRecorder.EffectsListener { private static final String TAG = "CAM_VideoModule"; @@ -124,7 +105,6 @@ public class VideoModule implements CameraModule, private static final int MIN_THUMB_SIZE = 64; // module fields private CameraActivity mActivity; - private View mRootView; private boolean mPaused; private int mCameraId; private Parameters mParameters; @@ -140,24 +120,8 @@ public class VideoModule implements CameraModule, private ComboPreferences mPreferences; private PreferenceGroup mPreferenceGroup; - private PreviewFrameLayout mPreviewFrameLayout; - private boolean mSurfaceViewReady; - private SurfaceHolder.Callback mSurfaceViewCallback; - private PreviewSurfaceView mPreviewSurfaceView; private CameraScreenNail.OnFrameDrawnListener mFrameDrawnListener; - // An review image having same size as preview. It is displayed when - // recording is stopped in capture intent. - private ImageView mReviewImage; - private View mReviewCancelButton; - private View mReviewDoneButton; - private View mReviewPlayButton; - private ShutterButton mShutterButton; - private TextView mRecordingTimeView; - private RotateLayout mBgLearningMessageRotater; - private View mBgLearningMessageFrame; - private LinearLayout mLabelsLinearLayout; - private boolean mIsVideoCaptureIntent; private boolean mQuickCapture; @@ -175,7 +139,6 @@ public class VideoModule implements CameraModule, private boolean mMediaRecorderRecording = false; private long mRecordingStartTime; private boolean mRecordingTimeCountsDown = false; - private RotateLayout mRecordingTimeRect; private long mOnResumeTime; // The video file that the hardware camera is about to record into // (or is recording into.) @@ -197,10 +160,6 @@ public class VideoModule implements CameraModule, private boolean mCaptureTimeLapse = false; // Default 0. If it is larger than 0, the camcorder is in time lapse mode. private int mTimeBetweenTimeLapseFrameCaptureMs = 0; - private View mTimeLapseLabel; - - private int mDesiredPreviewWidth; - private int mDesiredPreviewHeight; boolean mPreviewing = false; // True if preview is started. // The display rotation in degrees. This is only valid when mPreviewing is @@ -208,35 +167,23 @@ public class VideoModule implements CameraModule, private int mDisplayRotation; private int mCameraDisplayOrientation; + private int mDesiredPreviewWidth; + private int mDesiredPreviewHeight; private ContentResolver mContentResolver; private LocationManager mLocationManager; private VideoNamer mVideoNamer; - private RenderOverlay mRenderOverlay; - private PieRenderer mPieRenderer; - - private VideoMenu mVideoControl; - private AbstractSettingPopup mPopup; private int mPendingSwitchCameraId; - private ZoomRenderer mZoomRenderer; - - private PreviewGestures mGestures; - private View mMenu; - private View mBlocker; - private View mOnScreenIndicators; - private ImageView mFlashIndicator; - private final Handler mHandler = new MainHandler(); - + private VideoUI mUI; // The degrees of the device rotated clockwise from its natural orientation. private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; private int mZoomValue; // The current zoom value. - private int mZoomMax; - private List<Integer> mZoomRatios; + private boolean mRestoreFlash; // This is used to check if we need to restore the flash // status when going back from gallery. @@ -282,7 +229,7 @@ public class VideoModule implements CameraModule, switch (msg.what) { case ENABLE_SHUTTER_BUTTON: - mShutterButton.setEnabled(true); + mUI.enableShutter(true); break; case CLEAR_SCREEN_DELAY: { @@ -331,7 +278,7 @@ public class VideoModule implements CameraModule, } case HIDE_SURFACE_VIEW: { - mPreviewSurfaceView.setVisibility(View.GONE); + mUI.hideSurfaceView(); break; } @@ -377,65 +324,21 @@ public class VideoModule implements CameraModule, } private void initializeSurfaceView() { - mPreviewSurfaceView = (PreviewSurfaceView) mRootView.findViewById(R.id.preview_surface_view); - if (!ApiHelper.HAS_SURFACE_TEXTURE) { // API level < 11 - if (mSurfaceViewCallback == null) { - mSurfaceViewCallback = new SurfaceViewCallback(); - } - mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback); - mPreviewSurfaceView.setVisibility(View.VISIBLE); - } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16 - if (mSurfaceViewCallback == null) { - mSurfaceViewCallback = new SurfaceViewCallback(); + if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16 mFrameDrawnListener = new CameraScreenNail.OnFrameDrawnListener() { @Override public void onFrameDrawn(CameraScreenNail c) { mHandler.sendEmptyMessage(HIDE_SURFACE_VIEW); } }; - } - mPreviewSurfaceView.getHolder().addCallback(mSurfaceViewCallback); - } - } - - private void initializeOverlay() { - mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); - if (mPieRenderer == null) { - mPieRenderer = new PieRenderer(mActivity); - mVideoControl = new VideoMenu(mActivity, this, mPieRenderer); - mVideoControl.setListener(this); - mPieRenderer.setPieListener(this); - } - mRenderOverlay.addRenderer(mPieRenderer); - if (mZoomRenderer == null) { - mZoomRenderer = new ZoomRenderer(mActivity); - } - mRenderOverlay.addRenderer(mZoomRenderer); - if (mGestures == null) { - mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); - } - mGestures.setRenderOverlay(mRenderOverlay); - mGestures.clearTouchReceivers(); - mGestures.addTouchReceiver(mMenu); - mGestures.addTouchReceiver(mBlocker); - - if (isVideoCaptureIntent()) { - if (mReviewCancelButton != null) { - mGestures.addTouchReceiver(mReviewCancelButton); - } - if (mReviewDoneButton != null) { - mGestures.addTouchReceiver(mReviewDoneButton); - } - if (mReviewPlayButton != null) { - mGestures.addTouchReceiver(mReviewPlayButton); - } + mUI.getSurfaceHolder().addCallback(mUI); } } @Override public void init(CameraActivity activity, View root, boolean reuseScreenNail) { mActivity = activity; - mRootView = root; + mUI = new VideoUI(activity, this, root); mPreferences = new ComboPreferences(mActivity); CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal()); mCameraId = getPreferredCameraId(mPreferences); @@ -456,8 +359,6 @@ public class VideoModule implements CameraModule, mContentResolver = mActivity.getContentResolver(); - mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true); - // Surface texture is from camera screen nail and startPreview needs it. // This must be done before startPreview. mIsVideoCaptureIntent = isVideoCaptureIntent(); @@ -483,6 +384,7 @@ public class VideoModule implements CameraModule, } readVideoPreferences(); + mUI.setPrefChangedListener(this); new Thread(new Runnable() { @Override public void run() { @@ -490,23 +392,63 @@ public class VideoModule implements CameraModule, } }).start(); - initializeControlByIntent(); - initializeOverlay(); - initializeMiscControls(); - mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false); mLocationManager = new LocationManager(mActivity, null); - setOrientationIndicator(0, false); + mUI.setOrientationIndicator(0, false); setDisplayOrientation(); - showTimeLapseUI(mCaptureTimeLapse); + mUI.showTimeLapseUI(mCaptureTimeLapse); initializeVideoSnapshot(); resizeForPreviewAspectRatio(); initializeVideoControl(); mPendingSwitchCameraId = -1; - updateOnScreenIndicators(); + mUI.updateOnScreenIndicators(mParameters); + + // Disable the shutter button if effects are ON since it might take + // a little more time for the effects preview to be ready. We do not + // want to allow recording before that happens. The shutter button + // will be enabled when we get the message from effectsrecorder that + // the preview is running. This becomes critical when the camera is + // swapped. + if (effectsActive()) { + mUI.enableShutter(false); + } + } + + // SingleTapListener + // Preview area is touched. Take a picture. + @Override + public void onSingleTapUp(View view, int x, int y) { + if (mMediaRecorderRecording && effectsActive()) { + new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint, + mOrientation).show(); + return; + } + + MediaSaveService s = mActivity.getMediaSaveService(); + if (mPaused || mSnapshotInProgress || effectsActive() || s == null || s.isQueueFull()) { + return; + } + + if (!mMediaRecorderRecording) { + // check for dismissing popup + mUI.dismissPopup(true); + return; + } + + // Set rotation and gps data. + int rotation = Util.getJpegRotation(mCameraId, mOrientation); + mParameters.setRotation(rotation); + Location loc = mLocationManager.getCurrentLocation(); + Util.setGpsParameters(mParameters, loc); + mActivity.mCameraDevice.setParameters(mParameters); + + Log.v(TAG, "Video snapshot start"); + mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc)); + showVideoSnapshotUI(true); + mSnapshotInProgress = true; } @Override @@ -520,37 +462,11 @@ public class VideoModule implements CameraModule, settings.getPreferenceGroup(R.xml.video_preferences)); } - public boolean collapseCameraControls() { - boolean ret = false; - if (mPopup != null) { - dismissPopup(false); - ret = true; - } - return ret; - } - - public boolean removeTopLevelPopup() { - if (mPopup != null) { - dismissPopup(true); - return true; - } - return false; - } - - private void enableCameraControls(boolean enable) { - if (mGestures != null) { - mGestures.setZoomOnly(!enable); - } - if (mPieRenderer != null && mPieRenderer.showsItems()) { - mPieRenderer.hide(); - } - } - private void initializeVideoControl() { loadCameraPreferences(); - mVideoControl.initialize(mPreferenceGroup); + mUI.initializePopup(mPreferenceGroup); if (effectsActive()) { - mVideoControl.overrideSettings( + mUI.overrideSettings( CameraSettings.KEY_VIDEO_QUALITY, Integer.toString(getLowVideoQuality())); } @@ -593,28 +509,6 @@ public class VideoModule implements CameraModule, } } - private void setOrientationIndicator(int orientation, boolean animation) { - Rotatable[] indicators = { - mBgLearningMessageRotater}; - for (Rotatable indicator : indicators) { - if (indicator != null) indicator.setOrientation(orientation, animation); - } - if (mGestures != null) { - mGestures.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 (((orientation / 90) & 1) == 0) { - mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL); - } else { - mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL); - } - } - mRecordingTimeRect.setOrientation(0, animation); - } - private void startPlayVideoActivity() { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat)); @@ -671,7 +565,7 @@ public class VideoModule implements CameraModule, @Override public void onShutterButtonClick() { - if (collapseCameraControls() || mSwitchingCamera) return; + if (mUI.collapseCameraControls() || mSwitchingCamera) return; boolean stop = mMediaRecorderRecording; @@ -680,7 +574,7 @@ public class VideoModule implements CameraModule, } else { startVideoRecording(); } - mShutterButton.setEnabled(false); + mUI.enableShutter(false); // Keep the shutter button disabled when in video capture intent // mode and recording is stopped. It'll be re-enabled when @@ -794,7 +688,7 @@ public class VideoModule implements CameraModule, } private void resizeForPreviewAspectRatio() { - mPreviewFrameLayout.setAspectRatio( + mUI.setAspectRatio( (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight); } @@ -818,18 +712,13 @@ public class VideoModule implements CameraModule, public void onResumeAfterSuper() { if (mActivity.mOpenCameraFail || mActivity.mCameraDisabled) return; - if (mShutterButton != null) { - mShutterButton.setEnabled(false); - } + mUI.enableShutter(false); mZoomValue = 0; showVideoSnapshotUI(false); - if (!mPreviewing) { - if (resetEffect()) { - mBgLearningMessageFrame.setVisibility(View.GONE); - } + resetEffect(); openCamera(); if (mActivity.mOpenCameraFail) { Util.showErrorAndFinish(mActivity, @@ -850,7 +739,7 @@ public class VideoModule implements CameraModule, } // Initializing it here after the preview is started. - initializeZoom(); + mUI.initializeZoom(mParameters); keepScreenOnAwhile(); @@ -884,6 +773,19 @@ public class VideoModule implements CameraModule, mActivity.getGLRoot().requestLayoutContentPane(); } + @Override + public int onZoomChanged(int index) { + // Not useful to change zoom value when the activity is paused. + if (mPaused) return index; + mZoomValue = index; + if (mParameters == null || mActivity.mCameraDevice == null) return index; + // Set zoom parameters asynchronously + mParameters.setZoom(mZoomValue); + mActivity.mCameraDevice.setParametersAsync(mParameters); + Parameters p = mActivity.mCameraDevice.getParameters(); + if (p != null) return p.getZoom(); + return index; + } private void startPreview() { Log.v(TAG, "startPreview"); @@ -896,7 +798,6 @@ public class VideoModule implements CameraModule, } } - setDisplayOrientation(); mActivity.mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); setCameraParameters(); @@ -911,7 +812,7 @@ public class VideoModule implements CameraModule, } mActivity.mCameraDevice.setPreviewTextureAsync(surfaceTexture); } else { - mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder()); + mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); } mActivity.mCameraDevice.startPreviewAsync(); mPreviewing = true; @@ -941,12 +842,11 @@ public class VideoModule implements CameraModule, } private void onPreviewStarted() { - if (mShutterButton != null) { - mShutterButton.setEnabled(true); - } + mUI.enableShutter(true); } - private void stopPreview() { + @Override + public void stopPreview() { mActivity.mCameraDevice.stopPreview(); mPreviewing = false; } @@ -1013,7 +913,7 @@ public class VideoModule implements CameraModule, screenNail.releaseSurfaceTexture(); if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { mHandler.removeMessages(HIDE_SURFACE_VIEW); - mPreviewSurfaceView.setVisibility(View.GONE); + mUI.hideSurfaceView(); } } } @@ -1083,11 +983,10 @@ public class VideoModule implements CameraModule, if (mMediaRecorderRecording) { onStopVideoRecording(); return true; - } else if (mPieRenderer != null && mPieRenderer.showsItems()) { - mPieRenderer.hide(); + } else if (mUI.hidePieRenderer()) { return true; } else { - return removeTopLevelPopup(); + return mUI.removeTopLevelPopup(); } } @@ -1101,13 +1000,13 @@ public class VideoModule implements CameraModule, switch (keyCode) { case KeyEvent.KEYCODE_CAMERA: if (event.getRepeatCount() == 0) { - mShutterButton.performClick(); + mUI.clickShutter(); return true; } break; case KeyEvent.KEYCODE_DPAD_CENTER: if (event.getRepeatCount() == 0) { - mShutterButton.performClick(); + mUI.clickShutter(); return true; } break; @@ -1122,13 +1021,14 @@ public class VideoModule implements CameraModule, public boolean onKeyUp(int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_CAMERA: - mShutterButton.setPressed(false); + mUI.pressShutter(false); return true; } return false; } - private boolean isVideoCaptureIntent() { + @Override + public boolean isVideoCaptureIntent() { String action = mActivity.getIntent().getAction(); return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action)); } @@ -1159,12 +1059,12 @@ public class VideoModule implements CameraModule, private void setupMediaRecorderPreviewDisplay() { // Nothing to do here if using SurfaceTexture. if (!ApiHelper.HAS_SURFACE_TEXTURE) { - mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface()); + mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface()); } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // We stop the preview here before unlocking the device because we // need to change the SurfaceTexture to SurfaceView for preview. stopPreview(); - mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder()); + mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); // The orientation for SurfaceTexture is different from that for // SurfaceView. For SurfaceTexture we don't need to consider the // display rotation. Just consider the sensor's orientation and we @@ -1176,7 +1076,7 @@ public class VideoModule implements CameraModule, Util.getDisplayOrientation(mDisplayRotation, mCameraId)); mActivity.mCameraDevice.startPreviewAsync(); mPreviewing = true; - mMediaRecorder.setPreviewDisplay(mPreviewSurfaceView.getHolder().getSurface()); + mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface()); } } @@ -1191,8 +1091,8 @@ public class VideoModule implements CameraModule, // surfaceCreated() is called immediately when the visibility is // changed to visible. Thus, mSurfaceViewReady should become true // right after calling setVisibility(). - mPreviewSurfaceView.setVisibility(View.VISIBLE); - if (!mSurfaceViewReady) return; + mUI.showSurfaceView(); + if (!mUI.isSurfaceViewReady()) return; } Intent intent = mActivity.getIntent(); @@ -1622,7 +1522,7 @@ public class VideoModule implements CameraModule, // Make sure the video recording has started before announcing // this in accessibility. - AccessibilityUtils.makeAnnouncement(mShutterButton, + AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(), mActivity.getString(R.string.video_recording_started)); // The parameters may have been changed by MediaRecorder upon starting @@ -1633,45 +1533,17 @@ public class VideoModule implements CameraModule, mParameters = mActivity.mCameraDevice.getParameters(); } - enableCameraControls(false); + mUI.enableCameraControls(false); mMediaRecorderRecording = true; mActivity.getOrientationManager().lockOrientation(); mRecordingStartTime = SystemClock.uptimeMillis(); - showRecordingUI(true); + mUI.showRecordingUI(true, mParameters.isZoomSupported()); updateRecordingTime(); keepScreenOn(); } - private void showRecordingUI(boolean recording) { - mMenu.setVisibility(recording ? View.GONE : View.VISIBLE); - mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE); - if (recording) { - mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording); - mActivity.hideSwitcher(); - mRecordingTimeView.setText(""); - mRecordingTimeView.setVisibility(View.VISIBLE); - // The camera is not allowed to be accessed in older api levels during - // recording. It is therefore necessary to hide the zoom UI on older - // platforms. - // See the documentation of android.media.MediaRecorder.start() for - // further explanation. - if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING - && mParameters.isZoomSupported()) { - // TODO: disable zoom UI here. - } - } else { - mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); - mActivity.showSwitcher(); - mRecordingTimeView.setVisibility(View.GONE); - if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING - && mParameters.isZoomSupported()) { - // TODO: enable zoom UI here. - } - } - } - private void showCaptureResult() { Bitmap bitmap = null; if (mVideoFileDescriptor != null) { @@ -1687,35 +1559,19 @@ public class VideoModule implements CameraModule, CameraInfo[] info = CameraHolder.instance().getCameraInfo(); boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT); bitmap = Util.rotateAndMirror(bitmap, 0, mirror); - mReviewImage.setImageBitmap(bitmap); - mReviewImage.setVisibility(View.VISIBLE); + mUI.showReviewImage(bitmap); } - Util.fadeOut(mShutterButton); - - Util.fadeIn(mReviewDoneButton); - Util.fadeIn(mReviewPlayButton); - mMenu.setVisibility(View.GONE); - mOnScreenIndicators.setVisibility(View.GONE); - enableCameraControls(false); - - showTimeLapseUI(false); + mUI.showReviewControls(); + mUI.enableCameraControls(false); + mUI.showTimeLapseUI(false); } private void hideAlert() { - mReviewImage.setVisibility(View.GONE); - mShutterButton.setEnabled(true); - mMenu.setVisibility(View.VISIBLE); - mOnScreenIndicators.setVisibility(View.VISIBLE); - enableCameraControls(true); - - Util.fadeOut(mReviewDoneButton); - Util.fadeOut(mReviewPlayButton); - - Util.fadeIn(mShutterButton); - + mUI.enableCameraControls(true); + mUI.hideReviewUI(); if (mCaptureTimeLapse) { - showTimeLapseUI(true); + mUI.showTimeLapseUI(true); } } @@ -1744,7 +1600,7 @@ public class VideoModule implements CameraModule, mCurrentVideoFilename = mVideoFilename; Log.v(TAG, "stopVideoRecording: Setting current video filename: " + mCurrentVideoFilename); - AccessibilityUtils.makeAnnouncement(mShutterButton, + AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(), mActivity.getString(R.string.video_recording_stopped)); } catch (RuntimeException e) { Log.e(TAG, "stop fail", e); @@ -1770,13 +1626,13 @@ public class VideoModule implements CameraModule, closeCamera(closeEffects); } - showRecordingUI(false); + mUI.showRecordingUI(false, mParameters.isZoomSupported()); if (!mIsVideoCaptureIntent) { - enableCameraControls(true); + mUI.enableCameraControls(true); } // The orientation was fixed during video recording. Now make it // reflect the device orientation as video recording is stopped. - setOrientationIndicator(0, true); + mUI.setOrientationIndicator(0, true); keepScreenOnAwhile(); if (shouldAddToMediaStoreNow) { if (addVideoToMediaStore()) fail = true; @@ -1901,7 +1757,7 @@ public class VideoModule implements CameraModule, targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs; } - mRecordingTimeView.setText(text); + mUI.setRecordingTime(text); if (mRecordingTimeCountsDown != countdownRemainingTime) { // Avoid setting the color on every update, do it only @@ -1912,7 +1768,7 @@ public class VideoModule implements CameraModule, ? R.color.recording_time_remaining_text : R.color.recording_time_elapsed_text); - mRecordingTimeView.setTextColor(color); + mUI.setRecordingTimeTextColor(color); } long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay); @@ -2058,7 +1914,6 @@ public class VideoModule implements CameraModule, if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) { // Effects have shut down. Hide learning message if any, // and restart regular preview. - mBgLearningMessageFrame.setVisibility(View.GONE); checkQualityAndStartPreview(); } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) { // This follows the codepath from onStopVideoRecording. @@ -2080,17 +1935,7 @@ public class VideoModule implements CameraModule, } } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) { // Enable the shutter button once the preview is complete. - mShutterButton.setEnabled(true); - } else if (effectId == EffectsRecorder.EFFECT_BACKDROPPER) { - switch (effectMsg) { - case EffectsRecorder.EFFECT_MSG_STARTED_LEARNING: - mBgLearningMessageFrame.setVisibility(View.VISIBLE); - break; - case EffectsRecorder.EFFECT_MSG_DONE_LEARNING: - case EffectsRecorder.EFFECT_MSG_SWITCHING_EFFECT: - mBgLearningMessageFrame.setVisibility(View.GONE); - break; - } + mUI.enableShutter(true); } // In onPause, this was not called if the effects were active. We had to // wait till the effects completed to do this. @@ -2101,8 +1946,6 @@ public class VideoModule implements CameraModule, } public void onCancelBgTraining(View v) { - // Remove training message - mBgLearningMessageFrame.setVisibility(View.GONE); // Write default effect out to shared prefs writeDefaultEffectToPrefs(); // Tell VideoCamer to re-init based on new shared pref values. @@ -2128,87 +1971,6 @@ public class VideoModule implements CameraModule, throw new RuntimeException("Error during recording!", exception); } - private void initializeControlByIntent() { - mBlocker = mActivity.findViewById(R.id.blocker); - mMenu = mActivity.findViewById(R.id.menu); - mMenu.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - if (mPieRenderer != null) { - mPieRenderer.showInCenter(); - } - } - }); - mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators); - mFlashIndicator = (ImageView) mActivity.findViewById(R.id.menu_flash_indicator); - if (mIsVideoCaptureIntent) { - mActivity.hideSwitcher(); - ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls); - mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls); - // Cannot use RotateImageView for "done" and "cancel" button because - // the tablet layout uses RotateLayout, which cannot be cast to - // RotateImageView. - mReviewDoneButton = mActivity.findViewById(R.id.btn_done); - mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel); - mReviewPlayButton = mActivity.findViewById(R.id.btn_play); - - mReviewCancelButton.setVisibility(View.VISIBLE); - - mReviewDoneButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onReviewDoneClicked(v); - } - }); - mReviewCancelButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onReviewCancelClicked(v); - } - }); - - mReviewPlayButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - onReviewPlayClicked(v); - } - }); - } - } - - private void initializeMiscControls() { - mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame); - mPreviewFrameLayout.setOnLayoutChangeListener(mActivity); - mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image); - - mShutterButton = mActivity.getShutterButton(); - mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); - mShutterButton.setOnShutterButtonListener(this); - mShutterButton.setVisibility(View.VISIBLE); - mShutterButton.requestFocus(); - mShutterButton.enableTouch(true); - - // Disable the shutter button if effects are ON since it might take - // a little more time for the effects preview to be ready. We do not - // want to allow recording before that happens. The shutter button - // will be enabled when we get the message from effectsrecorder that - // the preview is running. This becomes critical when the camera is - // swapped. - if (effectsActive()) { - mShutterButton.setEnabled(false); - } - - mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time); - mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect); - mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label); - // The R.id.labels can only be found in phone layout. - // That is, mLabelsLinearLayout should be null in tablet layout. - mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels); - - mBgLearningMessageRotater = (RotateLayout) mRootView.findViewById(R.id.bg_replace_message); - mBgLearningMessageFrame = mRootView.findViewById(R.id.bg_replace_message_frame); - } - @Override public void onConfigurationChanged(Configuration newConfig) { Log.v(TAG, "onConfigurationChanged"); @@ -2245,7 +2007,7 @@ public class VideoModule implements CameraModule, if (updateEffectSelection()) return; readVideoPreferences(); - showTimeLapseUI(mCaptureTimeLapse); + mUI.showTimeLapseUI(mCaptureTimeLapse); // We need to restart the preview if preview size is changed. Size size = mParameters.getPreviewSize(); if (size.width != mDesiredPreviewWidth @@ -2261,31 +2023,13 @@ public class VideoModule implements CameraModule, } else { setCameraParameters(); } - updateOnScreenIndicators(); + mUI.updateOnScreenIndicators(mParameters); } } - private void updateOnScreenIndicators() { - if (mParameters == null) return; - updateFlashOnScreenIndicator(mParameters.getFlashMode()); - } - - private void updateFlashOnScreenIndicator(String value) { - if (mFlashIndicator == null) { - return; - } - if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) { - mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off); - } else { - if (Parameters.FLASH_MODE_AUTO.equals(value)) { - mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto); - } else if (Parameters.FLASH_MODE_ON.equals(value) || - Parameters.FLASH_MODE_TORCH.equals(value)) { - mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on); - } else { - mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off); - } - } + protected void setCameraId(int cameraId) { + ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID); + pref.setValue("" + cameraId); } private void switchCamera() { @@ -2294,10 +2038,10 @@ public class VideoModule implements CameraModule, Log.d(TAG, "Start to switch camera."); mCameraId = mPendingSwitchCameraId; mPendingSwitchCameraId = -1; - mVideoControl.setCameraId(mCameraId); + setCameraId(mCameraId); closeCamera(); - + mUI.collapseCameraControls(); // Restart the camera and initialize the UI. From onCreate. mPreferences.setLocalId(mActivity, mCameraId); CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); @@ -2309,15 +2053,15 @@ public class VideoModule implements CameraModule, initializeVideoControl(); // From onResume - initializeZoom(); - setOrientationIndicator(0, false); + mUI.initializeZoom(mParameters); + mUI.setOrientationIndicator(0, false); if (ApiHelper.HAS_SURFACE_TEXTURE) { // Start switch camera animation. Post a message because // onFrameAvailable from the old camera may already exist. mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION); } - updateOnScreenIndicators(); + mUI.updateOnScreenIndicators(mParameters); } // Preview texture has been copied. Now camera can be released and the @@ -2375,7 +2119,7 @@ public class VideoModule implements CameraModule, // preview. If not, resets the surface texture and resizes the view. private void checkQualityAndStartPreview() { readVideoPreferences(); - showTimeLapseUI(mCaptureTimeLapse); + mUI.showTimeLapseUI(mCaptureTimeLapse); Size size = mParameters.getPreviewSize(); if (size.width != mDesiredPreviewWidth || size.height != mDesiredPreviewHeight) { @@ -2385,60 +2129,16 @@ public class VideoModule implements CameraModule, startPreview(); } - private void showTimeLapseUI(boolean enable) { - if (mTimeLapseLabel != null) { - mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE); - } - } - @Override public boolean dispatchTouchEvent(MotionEvent m) { if (mSwitchingCamera) return true; - if (mPopup == null && mGestures != null && mRenderOverlay != null) { - return mGestures.dispatchTouch(m); - } else if (mPopup != null) { - return mActivity.superDispatchTouchEvent(m); - } - return false; - } - - private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { - @Override - public void onZoomValueChanged(int value) { - // Not useful to change zoom value when the activity is paused. - if (mPaused) return; - mZoomValue = value; - // Set zoom parameters asynchronously - mParameters.setZoom(mZoomValue); - mActivity.mCameraDevice.setParametersAsync(mParameters); - Parameters p = mActivity.mCameraDevice.getParameters(); - mZoomRenderer.setZoomValue(mZoomRatios.get(p.getZoom())); - } - - @Override - public void onZoomStart() { - } - @Override - public void onZoomEnd() { - } - } - - private void initializeZoom() { - if (mParameters == null || !mParameters.isZoomSupported()) return; - mZoomMax = mParameters.getMaxZoom(); - mZoomRatios = mParameters.getZoomRatios(); - // Currently we use immediate zoom for fast zooming to get better UX and - // there is no plan to take advantage of the smooth zoom. - mZoomRenderer.setZoomMax(mZoomMax); - mZoomRenderer.setZoom(mParameters.getZoom()); - mZoomRenderer.setZoomValue(mZoomRatios.get(mParameters.getZoom())); - mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener()); + return mUI.dispatchTouchEvent(m); } private void initializeVideoSnapshot() { if (mParameters == null) return; if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) { - mActivity.setSingleTapUpListener(mPreviewFrameLayout); + mActivity.setSingleTapUpListener(mUI.getPreview()); // Show the tap to focus toast if this is the first start. if (mPreferences.getBoolean( CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) { @@ -2456,47 +2156,12 @@ public class VideoModule implements CameraModule, if (ApiHelper.HAS_SURFACE_TEXTURE && enabled) { ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); } else { - mPreviewFrameLayout.showBorder(enabled); + mUI.showPreviewBorder(enabled); } - mShutterButton.setEnabled(!enabled); + mUI.enableShutter(!enabled); } } - // Preview area is touched. Take a picture. - @Override - public void onSingleTapUp(View view, int x, int y) { - if (mMediaRecorderRecording && effectsActive()) { - new RotateTextToast(mActivity, R.string.disable_video_snapshot_hint, - mOrientation).show(); - return; - } - - MediaSaveService s = mActivity.getMediaSaveService(); - if (mPaused || mSnapshotInProgress || effectsActive() || s == null || s.isQueueFull()) { - return; - } - - if (!mMediaRecorderRecording) { - // check for dismissing popup - if (mPopup != null) { - dismissPopup(true); - } - return; - } - - // Set rotation and gps data. - int rotation = Util.getJpegRotation(mCameraId, mOrientation); - mParameters.setRotation(rotation); - Location loc = mLocationManager.getCurrentLocation(); - Util.setGpsParameters(mParameters, loc); - mActivity.mCameraDevice.setParameters(mParameters); - - Log.v(TAG, "Video snapshot start"); - mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc)); - showVideoSnapshotUI(true); - mSnapshotInProgress = true; - } - @Override public void updateCameraAppView() { if (!mPreviewing || mParameters.getFlashMode() == null) return; @@ -2515,43 +2180,15 @@ public class VideoModule implements CameraModule, } } - private void setShowMenu(boolean show) { - if (mOnScreenIndicators != null) { - mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE); - } - if (mMenu != null) { - mMenu.setVisibility(show ? View.VISIBLE : View.GONE); - } - } - @Override public void onFullScreenChanged(boolean full) { - if (mGestures != null) { - mGestures.setEnabled(full); - } - if (mPopup != null) { - dismissPopup(false, full); - } - if (mRenderOverlay != null) { - // this can not happen in capture mode - mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE); - } - setShowMenu(full); - if (mBlocker != null) { - // this can not happen in capture mode - mBlocker.setVisibility(full ? View.VISIBLE : View.GONE); - } + mUI.onFullScreenChanged(full); if (ApiHelper.HAS_SURFACE_TEXTURE) { if (mActivity.mCameraScreenNail != null) { ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full); } return; } - if (full) { - mPreviewSurfaceView.expand(); - } else { - mPreviewSurfaceView.shrink(); - } } private final class JpegPictureCallback implements PictureCallback { @@ -2711,39 +2348,6 @@ public class VideoModule implements CameraModule, } } - private class SurfaceViewCallback implements SurfaceHolder.Callback { - public SurfaceViewCallback() {} - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - Log.v(TAG, "Surface changed. width=" + width + ". height=" + height); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.v(TAG, "Surface created"); - mSurfaceViewReady = true; - if (mPaused) return; - if (!ApiHelper.HAS_SURFACE_TEXTURE) { - mActivity.mCameraDevice.setPreviewDisplayAsync(mPreviewSurfaceView.getHolder()); - if (!mPreviewing) { - startPreview(); - } - } - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.v(TAG, "Surface destroyed"); - mSurfaceViewReady = false; - if (mPaused) return; - if (!ApiHelper.HAS_SURFACE_TEXTURE) { - stopVideoRecording(); - stopPreview(); - } - } - } - @Override public boolean updateStorageHintOnResume() { return true; @@ -2778,50 +2382,8 @@ public class VideoModule implements CameraModule, } @Override - public void onPieOpened(int centerX, int centerY) { - mActivity.cancelActivityTouchHandling(); - mActivity.setSwipingEnabled(false); - } - - @Override - public void onPieClosed() { - mActivity.setSwipingEnabled(true); - } - - public void showPopup(AbstractSettingPopup popup) { - mActivity.hideUI(); - mBlocker.setVisibility(View.INVISIBLE); - setShowMenu(false); - mPopup = popup; - mPopup.setVisibility(View.VISIBLE); - FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, - LayoutParams.WRAP_CONTENT); - lp.gravity = Gravity.CENTER; - ((FrameLayout) mRootView).addView(mPopup, lp); - } - - public void dismissPopup(boolean topLevelOnly) { - dismissPopup(topLevelOnly, true); - } - - public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) { - if (fullScreen) { - mActivity.showUI(); - mBlocker.setVisibility(View.VISIBLE); - } - setShowMenu(fullScreen); - if (mPopup != null) { - ((FrameLayout) mRootView).removeView(mPopup); - mPopup = null; - } - mVideoControl.popupDismissed(topLevelPopupOnly); - } - - @Override public void onShowSwitcherPopup() { - if (mPieRenderer.showsItems()) { - mPieRenderer.hide(); - } + mUI.onShowSwitcherPopup(); } @Override diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java new file mode 100644 index 000000000..63c190984 --- /dev/null +++ b/src/com/android/camera/VideoUI.java @@ -0,0 +1,510 @@ +/* + * 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. + */ + +package com.android.camera; + +import android.graphics.Bitmap; +import android.hardware.Camera.Parameters; +import android.util.Log; +import android.view.Gravity; +import android.view.MotionEvent; +import android.view.SurfaceHolder; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.ViewGroup; +import android.widget.FrameLayout; +import android.widget.FrameLayout.LayoutParams; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import com.android.camera.CameraPreference.OnPreferenceChangedListener; +import com.android.camera.ui.AbstractSettingPopup; +import com.android.camera.ui.PieRenderer; +import com.android.camera.ui.PreviewSurfaceView; +import com.android.camera.ui.RenderOverlay; +import com.android.camera.ui.RotateLayout; +import com.android.camera.ui.ZoomRenderer; +import com.android.gallery3d.R; +import com.android.gallery3d.common.ApiHelper; + +import java.util.List; + +public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, + PreviewGestures.SingleTapListener { + private final static String TAG = "CAM_VideoUI"; + // module fields + private CameraActivity mActivity; + private View mRootView; + private PreviewFrameLayout mPreviewFrameLayout; + private boolean mSurfaceViewReady; + private SurfaceHolder.Callback mSurfaceViewCallback; + private PreviewSurfaceView mPreviewSurfaceView; + // An review image having same size as preview. It is displayed when + // recording is stopped in capture intent. + private ImageView mReviewImage; + private View mReviewCancelButton; + private View mReviewDoneButton; + private View mReviewPlayButton; + private ShutterButton mShutterButton; + private TextView mRecordingTimeView; + private LinearLayout mLabelsLinearLayout; + private View mTimeLapseLabel; + private RenderOverlay mRenderOverlay; + private PieRenderer mPieRenderer; + private VideoMenu mVideoMenu; + private AbstractSettingPopup mPopup; + private ZoomRenderer mZoomRenderer; + private PreviewGestures mGestures; + private View mMenu; + private View mBlocker; + private View mOnScreenIndicators; + private ImageView mFlashIndicator; + private RotateLayout mRecordingTimeRect; + private VideoController mController; + private int mZoomMax; + private List<Integer> mZoomRatios; + + public VideoUI(CameraActivity activity, VideoController controller, View parent) { + mActivity = activity; + mController = controller; + mRootView = parent; + mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true); + mPreviewSurfaceView = (PreviewSurfaceView) mRootView + .findViewById(R.id.preview_surface_view); + initializeMiscControls(); + initializeControlByIntent(); + initializeOverlay(); + } + + private void initializeControlByIntent() { + mBlocker = mActivity.findViewById(R.id.blocker); + mMenu = mActivity.findViewById(R.id.menu); + mMenu.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + if (mPieRenderer != null) { + mPieRenderer.showInCenter(); + } + } + }); + mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators); + mFlashIndicator = (ImageView) mActivity.findViewById(R.id.menu_flash_indicator); + if (mController.isVideoCaptureIntent()) { + mActivity.hideSwitcher(); + ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls); + mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls); + // Cannot use RotateImageView for "done" and "cancel" button because + // the tablet layout uses RotateLayout, which cannot be cast to + // RotateImageView. + mReviewDoneButton = mActivity.findViewById(R.id.btn_done); + mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel); + mReviewPlayButton = mActivity.findViewById(R.id.btn_play); + mReviewCancelButton.setVisibility(View.VISIBLE); + mReviewDoneButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mController.onReviewDoneClicked(v); + } + }); + mReviewCancelButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mController.onReviewCancelClicked(v); + } + }); + mReviewPlayButton.setOnClickListener(new OnClickListener() { + @Override + public void onClick(View v) { + mController.onReviewPlayClicked(v); + } + }); + } + } + + public boolean collapseCameraControls() { + boolean ret = false; + if (mPopup != null) { + dismissPopup(false); + ret = true; + } + return ret; + } + + public boolean removeTopLevelPopup() { + if (mPopup != null) { + dismissPopup(true); + return true; + } + return false; + } + + public void enableCameraControls(boolean enable) { + if (mGestures != null) { + mGestures.setZoomOnly(!enable); + } + if (mPieRenderer != null && mPieRenderer.showsItems()) { + mPieRenderer.hide(); + } + } + + public void overrideSettings(final String... keyvalues) { + mVideoMenu.overrideSettings(keyvalues); + } + + public View getPreview() { + return mPreviewFrameLayout; + } + + public void setOrientationIndicator(int orientation, boolean animation) { + if (mGestures != null) { + mGestures.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 (((orientation / 90) & 1) == 0) { + mLabelsLinearLayout.setOrientation(LinearLayout.VERTICAL); + } else { + mLabelsLinearLayout.setOrientation(LinearLayout.HORIZONTAL); + } + } + mRecordingTimeRect.setOrientation(0, animation); + } + + public SurfaceHolder getSurfaceHolder() { + return mPreviewSurfaceView.getHolder(); + } + + public void hideSurfaceView() { + mPreviewSurfaceView.setVisibility(View.GONE); + } + + public void showSurfaceView() { + mPreviewSurfaceView.setVisibility(View.VISIBLE); + } + + private void initializeOverlay() { + mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); + if (mPieRenderer == null) { + mPieRenderer = new PieRenderer(mActivity); + mVideoMenu = new VideoMenu(mActivity, this, mPieRenderer); + mPieRenderer.setPieListener(this); + } + mRenderOverlay.addRenderer(mPieRenderer); + if (mZoomRenderer == null) { + mZoomRenderer = new ZoomRenderer(mActivity); + } + mRenderOverlay.addRenderer(mZoomRenderer); + if (mGestures == null) { + mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); + } + mGestures.setRenderOverlay(mRenderOverlay); + mGestures.clearTouchReceivers(); + mGestures.addTouchReceiver(mMenu); + mGestures.addTouchReceiver(mBlocker); + if (mController.isVideoCaptureIntent()) { + if (mReviewCancelButton != null) { + mGestures.addTouchReceiver(mReviewCancelButton); + } + if (mReviewDoneButton != null) { + mGestures.addTouchReceiver(mReviewDoneButton); + } + if (mReviewPlayButton != null) { + mGestures.addTouchReceiver(mReviewPlayButton); + } + } + } + + public void setPrefChangedListener(OnPreferenceChangedListener listener) { + mVideoMenu.setListener(listener); + } + + private void initializeMiscControls() { + mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame); + mPreviewFrameLayout.setOnLayoutChangeListener(mActivity); + mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image); + mShutterButton = mActivity.getShutterButton(); + mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); + mShutterButton.setOnShutterButtonListener(mController); + mShutterButton.setVisibility(View.VISIBLE); + mShutterButton.requestFocus(); + mShutterButton.enableTouch(true); + mRecordingTimeView = (TextView) mRootView.findViewById(R.id.recording_time); + mRecordingTimeRect = (RotateLayout) mRootView.findViewById(R.id.recording_time_rect); + mTimeLapseLabel = mRootView.findViewById(R.id.time_lapse_label); + // The R.id.labels can only be found in phone layout. + // That is, mLabelsLinearLayout should be null in tablet layout. + mLabelsLinearLayout = (LinearLayout) mRootView.findViewById(R.id.labels); + } + + public void updateOnScreenIndicators(Parameters param) { + if (param == null) return; + String value = param.getFlashMode(); + if (mFlashIndicator == null) return; + if (value == null || Parameters.FLASH_MODE_OFF.equals(value)) { + mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off); + } else { + if (Parameters.FLASH_MODE_AUTO.equals(value)) { + mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_auto); + } else if (Parameters.FLASH_MODE_ON.equals(value) + || Parameters.FLASH_MODE_TORCH.equals(value)) { + mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_on); + } else { + mFlashIndicator.setImageResource(R.drawable.ic_indicator_flash_off); + } + } + } + + public void setAspectRatio(double ratio) { + mPreviewFrameLayout.setAspectRatio(ratio); + } + + public void showTimeLapseUI(boolean enable) { + if (mTimeLapseLabel != null) { + mTimeLapseLabel.setVisibility(enable ? View.VISIBLE : View.GONE); + } + } + + public void showPopup(AbstractSettingPopup popup) { + mActivity.hideUI(); + mBlocker.setVisibility(View.INVISIBLE); + setShowMenu(false); + mPopup = popup; + mPopup.setVisibility(View.VISIBLE); + FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams(LayoutParams.WRAP_CONTENT, + LayoutParams.WRAP_CONTENT); + lp.gravity = Gravity.CENTER; + ((FrameLayout) mRootView).addView(mPopup, lp); + } + + public void dismissPopup(boolean topLevelOnly) { + dismissPopup(topLevelOnly, true); + } + + public void dismissPopup(boolean topLevelPopupOnly, boolean fullScreen) { + if (fullScreen) { + mActivity.showUI(); + mBlocker.setVisibility(View.VISIBLE); + } + setShowMenu(fullScreen); + if (mPopup != null) { + ((FrameLayout) mRootView).removeView(mPopup); + mPopup = null; + } + mVideoMenu.popupDismissed(topLevelPopupOnly); + } + + public void onShowSwitcherPopup() { + hidePieRenderer(); + } + + public boolean hidePieRenderer() { + if (mPieRenderer != null && mPieRenderer.showsItems()) { + mPieRenderer.hide(); + return true; + } + return false; + } + + public void enableShutter(boolean enable) { + if (mShutterButton != null) { + mShutterButton.setEnabled(enable); + } + } + + // PieListener + @Override + public void onPieOpened(int centerX, int centerY) { + mActivity.cancelActivityTouchHandling(); + mActivity.setSwipingEnabled(false); + } + + @Override + public void onPieClosed() { + mActivity.setSwipingEnabled(true); + } + + public void showPreviewBorder(boolean enable) { + mPreviewFrameLayout.showBorder(enable); + } + + // SingleTapListener + // Preview area is touched. Take a picture. + @Override + public void onSingleTapUp(View view, int x, int y) { + mController.onSingleTapUp(view, x, y); + } + + // SurfaceView callback + @Override + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + Log.v(TAG, "Surface changed. width=" + width + ". height=" + height); + } + + @Override + public void surfaceCreated(SurfaceHolder holder) { + Log.v(TAG, "Surface created"); + mSurfaceViewReady = true; + } + + @Override + public void surfaceDestroyed(SurfaceHolder holder) { + Log.v(TAG, "Surface destroyed"); + mSurfaceViewReady = false; + mController.stopPreview(); + } + + public boolean isSurfaceViewReady() { + return mSurfaceViewReady; + } + + public void showRecordingUI(boolean recording, boolean zoomSupported) { + mMenu.setVisibility(recording ? View.GONE : View.VISIBLE); + mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE); + if (recording) { + mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording); + mActivity.hideSwitcher(); + mRecordingTimeView.setText(""); + mRecordingTimeView.setVisibility(View.VISIBLE); + // The camera is not allowed to be accessed in older api levels during + // recording. It is therefore necessary to hide the zoom UI on older + // platforms. + // See the documentation of android.media.MediaRecorder.start() for + // further explanation. + if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) { + // TODO: disable zoom UI here. + } + } else { + mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); + mActivity.showSwitcher(); + mRecordingTimeView.setVisibility(View.GONE); + if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) { + // TODO: enable zoom UI here. + } + } + } + + public void showReviewImage(Bitmap bitmap) { + mReviewImage.setImageBitmap(bitmap); + mReviewImage.setVisibility(View.VISIBLE); + } + + public void showReviewControls() { + Util.fadeOut(mShutterButton); + Util.fadeIn(mReviewDoneButton); + Util.fadeIn(mReviewPlayButton); + mReviewImage.setVisibility(View.VISIBLE); + mMenu.setVisibility(View.GONE); + mOnScreenIndicators.setVisibility(View.GONE); + } + + public void hideReviewUI() { + mReviewImage.setVisibility(View.GONE); + mShutterButton.setEnabled(true); + mMenu.setVisibility(View.VISIBLE); + mOnScreenIndicators.setVisibility(View.VISIBLE); + Util.fadeOut(mReviewDoneButton); + Util.fadeOut(mReviewPlayButton); + Util.fadeIn(mShutterButton); + } + + private void setShowMenu(boolean show) { + if (mOnScreenIndicators != null) { + mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE); + } + if (mMenu != null) { + mMenu.setVisibility(show ? View.VISIBLE : View.GONE); + } + } + + public void onFullScreenChanged(boolean full) { + if (mGestures != null) { + mGestures.setEnabled(full); + } + if (mPopup != null) { + dismissPopup(false, full); + } + if (mRenderOverlay != null) { + // this can not happen in capture mode + mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE); + } + setShowMenu(full); + if (mBlocker != null) { + // this can not happen in capture mode + mBlocker.setVisibility(full ? View.VISIBLE : View.GONE); + } + } + + public void initializePopup(PreferenceGroup pref) { + mVideoMenu.initialize(pref); + } + + public void initializeZoom(Parameters param) { + if (param == null || !param.isZoomSupported()) return; + mZoomMax = param.getMaxZoom(); + mZoomRatios = param.getZoomRatios(); + // Currently we use immediate zoom for fast zooming to get better UX and + // there is no plan to take advantage of the smooth zoom. + mZoomRenderer.setZoomMax(mZoomMax); + mZoomRenderer.setZoom(param.getZoom()); + mZoomRenderer.setZoomValue(mZoomRatios.get(param.getZoom())); + mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener()); + } + + public void clickShutter() { + mShutterButton.performClick(); + } + + public void pressShutter(boolean pressed) { + mShutterButton.setPressed(pressed); + } + + public boolean dispatchTouchEvent(MotionEvent m) { + if (mPopup == null && mGestures != null && mRenderOverlay != null) { + return mGestures.dispatchTouch(m); + } else if (mPopup != null) { + return mActivity.superDispatchTouchEvent(m); + } + return false; + } + + public void setRecordingTime(String text) { + mRecordingTimeView.setText(text); + } + + public void setRecordingTimeTextColor(int color) { + mRecordingTimeView.setTextColor(color); + } + + private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { + @Override + public void onZoomValueChanged(int index) { + int newZoom = mController.onZoomChanged(index); + if (mZoomRenderer != null) { + mZoomRenderer.setZoomValue(mZoomRatios.get(newZoom)); + } + } + + @Override + public void onZoomStart() { + } + + @Override + public void onZoomEnd() { + } + } +} diff --git a/src/com/android/gallery3d/data/Exif.java b/src/com/android/gallery3d/data/Exif.java index 30aba7e97..950e7de18 100644 --- a/src/com/android/gallery3d/data/Exif.java +++ b/src/com/android/gallery3d/data/Exif.java @@ -18,55 +18,31 @@ package com.android.gallery3d.data; import android.util.Log; -import com.android.gallery3d.exif.ExifInvalidFormatException; -import com.android.gallery3d.exif.ExifParser; -import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.exif.ExifInterface; import java.io.IOException; import java.io.InputStream; public class Exif { - private static final String TAG = "GalleryExif"; + private static final String TAG = "CameraExif"; + // Returns the degrees in clockwise. Values are 0, 90, 180, or 270. public static int getOrientation(InputStream is) { if (is == null) { return 0; } - + ExifInterface exif = new ExifInterface(); try { - ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0); - int event = parser.next(); - while (event != ExifParser.EVENT_END) { - if (event == ExifParser.EVENT_NEW_TAG) { - ExifTag tag = parser.getTag(); - if (tag.getTagId() == ExifTag.TAG_ORIENTATION && - tag.hasValue()) { - int orient = (int) tag.getValueAt(0); - switch (orient) { - case ExifTag.Orientation.TOP_LEFT: - return 0; - case ExifTag.Orientation.BOTTOM_LEFT: - return 180; - case ExifTag.Orientation.RIGHT_TOP: - return 90; - case ExifTag.Orientation.RIGHT_BOTTOM: - return 270; - default: - Log.i(TAG, "Unsupported orientation"); - return 0; - } - } - } - event = parser.next(); + exif.readExif(is); + Integer val = exif.getTagIntValue(ExifInterface.TAG_ORIENTATION); + if (val == null) { + return 0; + } else { + return ExifInterface.getRotationForOrientationValue(val.shortValue()); } - Log.i(TAG, "Orientation not found"); - return 0; } catch (IOException e) { Log.w(TAG, "Failed to read EXIF orientation", e); return 0; - } catch (ExifInvalidFormatException e) { - Log.w(TAG, "Failed to read EXIF orientation", e); - return 0; } } } diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java index d5fad5483..1ed67ecf4 100644 --- a/src/com/android/gallery3d/data/LocalImage.java +++ b/src/com/android/gallery3d/data/LocalImage.java @@ -23,7 +23,6 @@ import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; -import android.media.ExifInterface; import android.net.Uri; import android.os.Build; import android.provider.MediaStore.Images; @@ -37,8 +36,7 @@ import com.android.gallery3d.app.StitchingProgressManager; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.BitmapUtils; import com.android.gallery3d.common.Utils; -import com.android.gallery3d.exif.ExifInvalidFormatException; -import com.android.gallery3d.exif.ExifModifier; +import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.exif.ExifTag; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.ThreadPool.Job; @@ -46,6 +44,7 @@ import com.android.gallery3d.util.ThreadPool.JobContext; import com.android.gallery3d.util.UpdateHelper; import java.io.File; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.channels.FileChannel.MapMode; @@ -196,15 +195,15 @@ public class LocalImage extends LocalMediaItem { // try to decode from JPEG EXIF if (type == MediaItem.TYPE_MICROTHUMBNAIL) { - ExifInterface exif = null; - byte [] thumbData = null; + ExifInterface exif = new ExifInterface(); + byte[] thumbData = null; try { - exif = new ExifInterface(mLocalFilePath); - if (exif != null) { - thumbData = exif.getThumbnail(); - } - } catch (Throwable t) { - Log.w(TAG, "fail to get exif thumb", t); + exif.readExif(mLocalFilePath); + thumbData = exif.getThumbnail(); + } catch (FileNotFoundException e) { + Log.w(TAG, "failed to find file to read thumbnail: " + mLocalFilePath); + } catch (IOException e) { + Log.w(TAG, "failed to get thumbnail from: " + mLocalFilePath); } if (thumbData != null) { Bitmap bitmap = DecodeUtils.decodeIfBigEnough( @@ -276,21 +275,6 @@ public class LocalImage extends LocalMediaItem { new String[]{String.valueOf(id)}); } - private static int getExifOrientation(int orientation) { - switch (orientation) { - case 0: - return ExifInterface.ORIENTATION_NORMAL; - case 90: - return ExifInterface.ORIENTATION_ROTATE_90; - case 180: - return ExifInterface.ORIENTATION_ROTATE_180; - case 270: - return ExifInterface.ORIENTATION_ROTATE_270; - default: - throw new AssertionError("invalid: " + orientation); - } - } - @Override public void rotate(int degrees) { GalleryUtils.assertNotInRenderThread(); @@ -300,34 +284,22 @@ public class LocalImage extends LocalMediaItem { if (rotation < 0) rotation += 360; if (mimeType.equalsIgnoreCase("image/jpeg")) { - RandomAccessFile file = null; - try { - // Because most of the images contain the orientation tag, we - // use ExifModifier to modify the tag for better efficiency. - // If the tag doesn't exist, ExifInterface will be used to replace the entire - // header. - file = new RandomAccessFile(filePath, "rw"); - ExifModifier modifier = new ExifModifier( - file.getChannel().map(MapMode.READ_WRITE, 0, file.length())); - ExifTag tag = ExifTag.buildTag(ExifTag.TAG_ORIENTATION); - tag.setValue(getExifOrientation(rotation)); - modifier.modifyTag(tag); - if (!modifier.commit()) { - // Need to change the file size, use ExifInterface instead. - ExifInterface exif = new ExifInterface(filePath); - exif.setAttribute(ExifInterface.TAG_ORIENTATION, - String.valueOf(getExifOrientation(rotation))); - exif.saveAttributes(); - // We need to update the filesize as well + ExifInterface exifInterface = new ExifInterface(); + ExifTag tag = exifInterface.buildTag(ExifInterface.TAG_ORIENTATION, + ExifInterface.getOrientationValueForRotation(rotation)); + if(tag != null) { + exifInterface.setTag(tag); + try { + exifInterface.forceRewriteExif(filePath); fileSize = new File(filePath).length(); values.put(Images.Media.SIZE, fileSize); + } catch (FileNotFoundException e) { + Log.w(TAG, "cannot find file to set exif: " + filePath); + } catch (IOException e) { + Log.w(TAG, "cannot set exif data: " + filePath); } - } catch (IOException e) { - Log.w(TAG, "cannot set exif data: " + filePath); - } catch (ExifInvalidFormatException e) { - Log.w(TAG, "cannot set exif data: " + filePath); - } finally { - Utils.closeSilently(file); + } else { + Log.w(TAG, "Could not build tag: " + ExifInterface.TAG_ORIENTATION); } } diff --git a/src/com/android/gallery3d/data/MediaDetails.java b/src/com/android/gallery3d/data/MediaDetails.java index 662bd141c..cac524b88 100644 --- a/src/com/android/gallery3d/data/MediaDetails.java +++ b/src/com/android/gallery3d/data/MediaDetails.java @@ -18,12 +18,12 @@ package com.android.gallery3d.data; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; -import com.android.gallery3d.exif.ExifData; -import com.android.gallery3d.exif.ExifInvalidFormatException; -import com.android.gallery3d.exif.ExifReader; +import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.exif.Rational; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.util.HashMap; @@ -115,11 +115,11 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> { String value = null; int type = tag.getDataType(); if (type == ExifTag.TYPE_UNSIGNED_RATIONAL || type == ExifTag.TYPE_RATIONAL) { - value = String.valueOf(tag.getRational(0).toDouble()); + value = String.valueOf(tag.getValueAsRational(0).toDouble()); } else if (type == ExifTag.TYPE_ASCII) { - value = tag.getString(); + value = tag.getValueAsString(); } else { - value = String.valueOf(tag.getValueAt(0)); + value = String.valueOf(tag.forceGetValueAsLong(0)); } if (key == MediaDetails.INDEX_FLASH) { MediaDetails.FlashState state = new MediaDetails.FlashState( @@ -132,37 +132,39 @@ public class MediaDetails implements Iterable<Entry<Integer, Object>> { } public static void extractExifInfo(MediaDetails details, String filePath) { - InputStream is = null; + + ExifInterface exif = new ExifInterface(); try { - is = new FileInputStream(filePath); - ExifData data = new ExifReader().read(is); - setExifData(details, data.getTag(ExifTag.TAG_FLASH), MediaDetails.INDEX_FLASH); - setExifData(details, data.getTag(ExifTag.TAG_IMAGE_WIDTH), MediaDetails.INDEX_WIDTH); - setExifData(details, data.getTag(ExifTag.TAG_IMAGE_LENGTH), MediaDetails.INDEX_HEIGHT); - setExifData(details, data.getTag(ExifTag.TAG_MAKE), MediaDetails.INDEX_MAKE); - setExifData(details, data.getTag(ExifTag.TAG_MODEL),MediaDetails.INDEX_MODEL); - setExifData(details, data.getTag(ExifTag.TAG_APERTURE_VALUE), - MediaDetails.INDEX_APERTURE); - setExifData(details, data.getTag(ExifTag.TAG_ISO_SPEED_RATINGS), - MediaDetails.INDEX_ISO); - setExifData(details, data.getTag(ExifTag.TAG_WHITE_BALANCE), - MediaDetails.INDEX_WHITE_BALANCE); - setExifData(details, data.getTag(ExifTag.TAG_EXPOSURE_TIME), - MediaDetails.INDEX_EXPOSURE_TIME); - ExifTag focalTag = data.getTag(ExifTag.TAG_FOCAL_LENGTH); - if (focalTag != null) { - details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH, - focalTag.getRational(0).toDouble()); - details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm); - } - } catch (IOException ex) { - // ignore it. - Log.w(TAG, "", ex); - } catch (ExifInvalidFormatException ex) { - // ignore it. - Log.w(TAG, "", ex); - } finally { - Utils.closeSilently(is); + exif.readExif(filePath); + } catch (FileNotFoundException e) { + Log.w(TAG, "Could not find file to read exif: " + filePath, e); + } catch (IOException e) { + Log.w(TAG, "Could not read exif from file: " + filePath, e); + } + + setExifData(details, exif.getTag(ExifInterface.TAG_FLASH), + MediaDetails.INDEX_FLASH); + setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_WIDTH), + MediaDetails.INDEX_WIDTH); + setExifData(details, exif.getTag(ExifInterface.TAG_IMAGE_LENGTH), + MediaDetails.INDEX_HEIGHT); + setExifData(details, exif.getTag(ExifInterface.TAG_MAKE), + MediaDetails.INDEX_MAKE); + setExifData(details, exif.getTag(ExifInterface.TAG_MODEL), + MediaDetails.INDEX_MODEL); + setExifData(details, exif.getTag(ExifInterface.TAG_APERTURE_VALUE), + MediaDetails.INDEX_APERTURE); + setExifData(details, exif.getTag(ExifInterface.TAG_ISO_SPEED_RATINGS), + MediaDetails.INDEX_ISO); + setExifData(details, exif.getTag(ExifInterface.TAG_WHITE_BALANCE), + MediaDetails.INDEX_WHITE_BALANCE); + setExifData(details, exif.getTag(ExifInterface.TAG_EXPOSURE_TIME), + MediaDetails.INDEX_EXPOSURE_TIME); + ExifTag focalTag = exif.getTag(ExifInterface.TAG_FOCAL_LENGTH); + if (focalTag != null) { + details.addDetail(MediaDetails.INDEX_FOCAL_LENGTH, + focalTag.getValueAsRational(0).toDouble()); + details.setUnit(MediaDetails.INDEX_FOCAL_LENGTH, R.string.unit_mm); } } } diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index 37b2cd9da..081cf5d9a 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -138,6 +138,7 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, clearGalleryBitmapPool(); setupMasterImage(); + ImageFilterRS.createRenderscriptContext(this); setDefaultValues(); fillEditors(); @@ -343,7 +344,6 @@ public class FilterShowActivity extends Activity implements OnItemClickListener, private void setDefaultValues() { ImageFilter.setActivityForMemoryToasts(this); - ImageFilterRS.setRenderScriptContext(this); Resources res = getResources(); // TODO: add a mechanism to set the resources in FiltersManagmer diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java index 6cf462269..2c1a847f8 100644 --- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java +++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java @@ -27,7 +27,7 @@ import android.graphics.BitmapFactory; import android.graphics.BitmapRegionDecoder; import android.graphics.Matrix; import android.graphics.Rect; -import android.media.ExifInterface; +import android.graphics.Bitmap.CompressFormat; import android.net.Uri; import android.provider.MediaStore; import android.util.Log; @@ -36,9 +36,8 @@ import com.adobe.xmp.XMPException; import com.adobe.xmp.XMPMeta; import com.android.gallery3d.R; import com.android.gallery3d.common.Utils; -import com.android.gallery3d.exif.ExifInvalidFormatException; -import com.android.gallery3d.exif.ExifParser; import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.filtershow.FilterShowActivity; import com.android.gallery3d.filtershow.HistoryAdapter; import com.android.gallery3d.filtershow.imageshow.ImageShow; @@ -48,6 +47,8 @@ import com.android.gallery3d.filtershow.tools.SaveCopyTask; import com.android.gallery3d.util.InterruptableOutputStream; import com.android.gallery3d.util.XmpUtilHelper; +import java.io.ByteArrayInputStream; +import java.io.Closeable; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; @@ -79,14 +80,14 @@ public class ImageLoader { public static final String DEFAULT_SAVE_DIRECTORY = "EditedOnlinePhotos"; public static final int DEFAULT_COMPRESS_QUALITY = 95; - public static final int ORI_NORMAL = ExifInterface.ORIENTATION_NORMAL; - public static final int ORI_ROTATE_90 = ExifInterface.ORIENTATION_ROTATE_90; - public static final int ORI_ROTATE_180 = ExifInterface.ORIENTATION_ROTATE_180; - public static final int ORI_ROTATE_270 = ExifInterface.ORIENTATION_ROTATE_270; - public static final int ORI_FLIP_HOR = ExifInterface.ORIENTATION_FLIP_HORIZONTAL; - public static final int ORI_FLIP_VERT = ExifInterface.ORIENTATION_FLIP_VERTICAL; - public static final int ORI_TRANSPOSE = ExifInterface.ORIENTATION_TRANSPOSE; - public static final int ORI_TRANSVERSE = ExifInterface.ORIENTATION_TRANSVERSE; + public static final int ORI_NORMAL = ExifInterface.Orientation.TOP_LEFT; + public static final int ORI_ROTATE_90 = ExifInterface.Orientation.RIGHT_TOP; + public static final int ORI_ROTATE_180 = ExifInterface.Orientation.BOTTOM_LEFT; + public static final int ORI_ROTATE_270 = ExifInterface.Orientation.RIGHT_BOTTOM; + public static final int ORI_FLIP_HOR = ExifInterface.Orientation.TOP_RIGHT; + public static final int ORI_FLIP_VERT = ExifInterface.Orientation.BOTTOM_RIGHT; + public static final int ORI_TRANSPOSE = ExifInterface.Orientation.LEFT_TOP; + public static final int ORI_TRANSVERSE = ExifInterface.Orientation.LEFT_BOTTOM; private static final int BITMAP_LOAD_BACKOUT_ATTEMPTS = 5; private Context mContext = null; @@ -147,26 +148,13 @@ public class ImageLoader { String path = uri.getPath(); int orientation = -1; InputStream is = null; + ExifInterface exif = new ExifInterface(); try { - is = new FileInputStream(path); - ExifParser parser = ExifParser.parse(is, ExifParser.OPTION_IFD_0); - int event = parser.next(); - while (event != ExifParser.EVENT_END) { - if (event == ExifParser.EVENT_NEW_TAG) { - ExifTag tag = parser.getTag(); - if (tag.getTagId() == ExifTag.TAG_ORIENTATION) { - orientation = (int) tag.getValueAt(0); - break; - } - } - event = parser.next(); - } + exif.readExif(path); + orientation = ExifInterface.getRotationForOrientationValue( + exif.getTagIntValue(ExifInterface.TAG_ORIENTATION).shortValue()); } catch (IOException e) { - e.printStackTrace(); - } catch (ExifInvalidFormatException e) { - e.printStackTrace(); - } finally { - Utils.closeSilently(is); + Log.w(LOGTAG, "Failed to read EXIF orientation", e); } return orientation; } @@ -196,9 +184,9 @@ public class ImageLoader { return -1; } } catch (SQLiteException e) { - return ExifInterface.ORIENTATION_UNDEFINED; + return -1; } catch (IllegalArgumentException e) { - return ExifInterface.ORIENTATION_UNDEFINED; + return -1; } finally { Utils.closeSilently(cursor); } diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java index 595aa9b30..2ebd61f3d 100644 --- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java +++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java @@ -108,7 +108,7 @@ public abstract class ImageFilterRS extends ImageFilter { return sRS; } - public static synchronized void setRenderScriptContext(Activity context) { + public static synchronized void createRenderscriptContext(Activity context) { if( sRS != null) { Log.w(LOGTAG, "A prior RS context exists when calling setRenderScriptContext"); destroyRenderScriptContext(); @@ -144,12 +144,12 @@ public abstract class ImageFilterRS extends ImageFilter { } private static synchronized Allocation convertRGBAtoA(Bitmap bitmap) { - Type.Builder tb_a8 = new Type.Builder(sRS, Element.U8(sRS)); + Type.Builder tb_a8 = new Type.Builder(sRS, Element.A_8(sRS)); ScriptC_grey greyConvert = new ScriptC_grey(sRS, sRS.getApplicationContext().getResources(), R.raw.grey); Allocation bitmapTemp = convertBitmap(bitmap); - if (bitmapTemp.getType().getElement().isCompatible(Element.U8(sRS))) { + if (bitmapTemp.getType().getElement().isCompatible(Element.A_8(sRS))) { return bitmapTemp; } diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java index b88dbbc47..aa7e70065 100644 --- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java +++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java @@ -21,7 +21,6 @@ import android.content.ContentValues; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; -import android.graphics.Bitmap.CompressFormat; import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; @@ -31,21 +30,15 @@ import android.provider.MediaStore.Images.ImageColumns; import android.util.Log; import com.android.gallery3d.common.Utils; -import com.android.gallery3d.exif.ExifData; -import com.android.gallery3d.exif.ExifInvalidFormatException; -import com.android.gallery3d.exif.ExifOutputStream; -import com.android.gallery3d.exif.ExifReader; -import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.presets.ImagePreset; import com.android.gallery3d.util.XmpUtilHelper; import java.io.File; import java.io.FileNotFoundException; -import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; import java.sql.Date; import java.text.SimpleDateFormat; import java.util.TimeZone; @@ -55,35 +48,7 @@ import java.util.TimeZone; */ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { - private static final String LOGTAG = "SaveCopyTask"; - /** - * Saves the bitmap in the final destination - */ - public static void saveBitmap(Bitmap bitmap, File destination, Object xmp) { - saveBitmap(bitmap, destination, xmp, null); - } - - private static void saveBitmap(Bitmap bitmap, File destination, Object xmp, ExifData exif) { - OutputStream os = null; - try { - os = new FileOutputStream(destination); - if (exif != null) { - ExifOutputStream eos = new ExifOutputStream(os); - eos.setExifData(exif); - bitmap.compress(CompressFormat.JPEG, ImageLoader.DEFAULT_COMPRESS_QUALITY, eos); - } else { - bitmap.compress(CompressFormat.JPEG, ImageLoader.DEFAULT_COMPRESS_QUALITY, os); - } - } catch (FileNotFoundException e) { - Log.v(LOGTAG,"Error in writing "+destination.getAbsolutePath()); - } finally { - Utils.closeSilently(os);; - } - if (xmp != null) { - XmpUtilHelper.writeXMPMeta(destination.getAbsolutePath(), xmp); - } - } /** * Callback for the completed asynchronous task. @@ -128,7 +93,8 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { ImageLoader.DEFAULT_SAVE_DIRECTORY); } // Create the directory if it doesn't exist - if (!saveDirectory.exists()) saveDirectory.mkdirs(); + if (!saveDirectory.exists()) + saveDirectory.mkdirs(); return saveDirectory; } @@ -139,28 +105,59 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { return new File(saveDirectory, filename + ".JPG"); } - private ExifData getExifData(Uri sourceUri) { + public Object getPanoramaXMPData(Uri source, ImagePreset preset) { + Object xmp = null; + if (preset.isPanoramaSafe()) { + InputStream is = null; + try { + is = context.getContentResolver().openInputStream(source); + xmp = XmpUtilHelper.extractXMPMeta(is); + } catch (FileNotFoundException e) { + Log.w(LOGTAG, "Failed to get XMP data from image: ", e); + } finally { + Utils.closeSilently(is); + } + } + return xmp; + } + + public boolean putPanoramaXMPData(File file, Object xmp) { + if (xmp != null) { + return XmpUtilHelper.writeXMPMeta(file.getAbsolutePath(), xmp); + } + return false; + } + + public ExifInterface getExifData(Uri source) { + ExifInterface exif = new ExifInterface(); String mimeType = context.getContentResolver().getType(sourceUri); - if (mimeType != ImageLoader.JPEG_MIME_TYPE) { - return null; + if (mimeType == ImageLoader.JPEG_MIME_TYPE) { + InputStream inStream = null; + try { + inStream = context.getContentResolver().openInputStream(source); + exif.readExif(inStream); + } catch (FileNotFoundException e) { + Log.w(LOGTAG, "Cannot find file: " + source, e); + } catch (IOException e) { + Log.w(LOGTAG, "Cannot read exif for: " + source, e); + } finally { + Utils.closeSilently(inStream); + } } - InputStream is = null; + return exif; + } + + public boolean putExifData(File file, ExifInterface exif, Bitmap image) { + boolean ret = false; try { - is = context.getContentResolver().openInputStream(sourceUri); - ExifReader reader = new ExifReader(); - return reader.read(is); + exif.writeExif(image, file.getAbsolutePath()); + ret = true; } catch (FileNotFoundException e) { - Log.w(LOGTAG, "Failed to find file", e); - return null; - } catch (ExifInvalidFormatException e) { - Log.w(LOGTAG, "Invalid EXIF data", e); - return null; + Log.w(LOGTAG, "File not found: " + file.getAbsolutePath(), e); } catch (IOException e) { - Log.w(LOGTAG, "Failed to read original file", e); - return null; - } finally { - Utils.closeSilently(is); + Log.w(LOGTAG, "Could not write exif: ", e); } + return ret; } /** @@ -173,12 +170,12 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { return null; } ImagePreset preset = params[0]; - InputStream is = null; BitmapFactory.Options options = new BitmapFactory.Options(); + Uri uri = null; boolean noBitmap = true; int num_tries = 0; // Stopgap fix for low-memory devices. - while(noBitmap) { + while (noBitmap) { try { // Try to do bitmap operations, downsample if low-memory Bitmap bitmap = ImageLoader.loadMutableBitmap(context, sourceUri, options); @@ -189,24 +186,23 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { bitmap = preset.applyGeometry(bitmap); bitmap = preset.apply(bitmap); - Object xmp = null; - if (preset.isPanoramaSafe()) { - is = context.getContentResolver().openInputStream(sourceUri); - xmp = XmpUtilHelper.extractXMPMeta(is); - } - ExifData exif = getExifData(sourceUri); - if (exif != null) { - exif.addDateTimeStampTag(ExifTag.TAG_DATE_TIME, System.currentTimeMillis(), - TimeZone.getDefault()); - // Since the image has been modified, set the orientation to normal. - exif.addTag(ExifTag.TAG_ORIENTATION).setValue(ExifTag.Orientation.TOP_LEFT); + Object xmp = getPanoramaXMPData(sourceUri, preset); + ExifInterface exif = getExifData(sourceUri); + + // Set tags + long time = System.currentTimeMillis(); + exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, time, + TimeZone.getDefault()); + exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION, + ExifInterface.Orientation.TOP_LEFT)); + + // If we succeed in writing the bitmap as a jpeg, return a uri. + if (putExifData(this.destinationFile, exif, bitmap)) { + putPanoramaXMPData(this.destinationFile, xmp); + uri = insertContent(context, sourceUri, this.destinationFile, saveFileName, + time); } - saveBitmap(bitmap, this.destinationFile, xmp, exif); - bitmap.recycle(); noBitmap = false; - } catch (FileNotFoundException ex) { - Log.w(LOGTAG, "Failed to save image!", ex); - return null; } catch (java.lang.OutOfMemoryError e) { // Try 5 times before failing for good. if (++num_tries >= 5) { @@ -214,13 +210,9 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { } System.gc(); options.inSampleSize *= 2; - } finally { - Utils.closeSilently(is); } } - Uri uri = insertContent(context, sourceUri, this.destinationFile, saveFileName); return uri; - } @Override @@ -267,16 +259,17 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { /** * Insert the content (saved file) with proper source photo properties. */ - public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName) { - long now = System.currentTimeMillis() / 1000; + public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName, + long time) { + time /= 1000; final ContentValues values = new ContentValues(); values.put(Images.Media.TITLE, saveFileName); values.put(Images.Media.DISPLAY_NAME, file.getName()); values.put(Images.Media.MIME_TYPE, "image/jpeg"); - values.put(Images.Media.DATE_TAKEN, now); - values.put(Images.Media.DATE_MODIFIED, now); - values.put(Images.Media.DATE_ADDED, now); + values.put(Images.Media.DATE_TAKEN, time); + values.put(Images.Media.DATE_MODIFIED, time); + values.put(Images.Media.DATE_ADDED, time); values.put(Images.Media.ORIENTATION, 0); values.put(Images.Media.DATA, file.getAbsolutePath()); values.put(Images.Media.SIZE, file.length()); @@ -288,20 +281,20 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { querySource(context, sourceUri, projection, new ContentResolverQueryCallback() { - @Override - public void onCursorResult(Cursor cursor) { - values.put(Images.Media.DATE_TAKEN, cursor.getLong(0)); - - double latitude = cursor.getDouble(1); - double longitude = cursor.getDouble(2); - // TODO: Change || to && after the default location issue is - // fixed. - if ((latitude != 0f) || (longitude != 0f)) { - values.put(Images.Media.LATITUDE, latitude); - values.put(Images.Media.LONGITUDE, longitude); - } - } - }); + @Override + public void onCursorResult(Cursor cursor) { + values.put(Images.Media.DATE_TAKEN, cursor.getLong(0)); + + double latitude = cursor.getDouble(1); + double longitude = cursor.getDouble(2); + // TODO: Change || to && after the default location + // issue is fixed. + if ((latitude != 0f) || (longitude != 0f)) { + values.put(Images.Media.LATITUDE, latitude); + values.put(Images.Media.LONGITUDE, longitude); + } + } + }); return context.getContentResolver().insert( Images.Media.EXTERNAL_CONTENT_URI, values); diff --git a/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java b/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java index 9d50d5ac0..59c18e0f4 100644 --- a/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java +++ b/src/com/android/gallery3d/filtershow/ui/FilterIconButton.java @@ -19,13 +19,13 @@ package com.android.gallery3d.filtershow.ui; import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; +import android.graphics.Canvas; import android.graphics.Rect; import android.util.AttributeSet; import android.view.View; import android.widget.LinearLayout; import com.android.gallery3d.filtershow.PanelController; -import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.cache.RenderingRequest; import com.android.gallery3d.filtershow.cache.RenderingRequestCaller; import com.android.gallery3d.filtershow.filters.FilterRepresentation; @@ -33,6 +33,7 @@ import com.android.gallery3d.filtershow.imageshow.GeometryListener; import com.android.gallery3d.filtershow.imageshow.MasterImage; import com.android.gallery3d.filtershow.presets.ImagePreset; +// TODO: merge back IconButton and FilterIconButton? public class FilterIconButton extends IconButton implements View.OnClickListener, RenderingRequestCaller, GeometryListener { private static final String LOGTAG = "FilterIconButton"; @@ -43,9 +44,6 @@ public class FilterIconButton extends IconButton implements View.OnClickListener private LinearLayout mParentContainer = null; private View.OnClickListener mListener = null; private Bitmap mIconBitmap = null; - private ImagePreset mPreset = null; - private Rect mDestination = null; - public FilterIconButton(Context context) { super(context); } @@ -68,27 +66,6 @@ public class FilterIconButton extends IconButton implements View.OnClickListener } @Override - protected Bitmap drawImage(Bitmap dst, Bitmap image, Rect destination) { - if (mOverlayOnly) { - // TODO: merge back IconButton and FilterIconButton - return super.drawImage(dst, image, destination); - } - if (mIconBitmap == null && mPreset == null) { - dst = MasterImage.getImage().getThumbnailBitmap(); - if (dst != null) { - ImagePreset mPreset = new ImagePreset(); - mPreset.addFilter(mFilterRepresentation); - mPreset.setDoApplyGeometry(false); - mDestination = destination; - RenderingRequest.post(dst.copy(Bitmap.Config.ARGB_8888, true), mPreset, RenderingRequest.ICON_RENDERING, this); - } - return dst; - } else { - return mIconBitmap; - } - } - - @Override public void setOnClickListener(View.OnClickListener listener) { mListener = listener; } @@ -117,31 +94,55 @@ public class FilterIconButton extends IconButton implements View.OnClickListener } mOverlayOnly = mFilterRepresentation.getOverlayOnly(); if (mOverlayOnly) { + assert(mOverlayBitmap != null); setIcon(mOverlayBitmap); } - stale_icon = true; invalidate(); } @Override + protected void onDraw(Canvas canvas) { + if (mIconBitmap == null && !mOverlayOnly) { + postNewIconRenderRequest(); + } + super.onDraw(canvas); + } + + @Override public void available(RenderingRequest request) { - if (request.getBitmap() == null) { + Bitmap bmap = request.getBitmap(); + if (bmap == null) { return; } - mIconBitmap = request.getBitmap(); - if (mOverlayBitmap != null) { - mIconBitmap = super.drawImage(mIconBitmap, mOverlayBitmap, mDestination); + if (mOverlayOnly) { + setIcon(mOverlayBitmap); + } else { + mIconBitmap = bmap; + if (mOverlayBitmap != null) { + Rect destination = new Rect(0, 0, mIconBitmap.getWidth(), mIconBitmap.getHeight()); + drawImage(mIconBitmap, mOverlayBitmap, destination); + } + setIcon(mIconBitmap); } - stale_icon = true; - invalidate(); } @Override public void geometryChanged() { - stale_icon = true; - + if (mOverlayOnly) { + return; + } mIconBitmap = null; - mPreset = null; - invalidate(); + postNewIconRenderRequest(); + } + + private void postNewIconRenderRequest() { + Bitmap dst = MasterImage.getImage().getThumbnailBitmap(); + if (dst != null) { + ImagePreset mPreset = new ImagePreset(); + mPreset.addFilter(mFilterRepresentation); + mPreset.setDoApplyGeometry(false); + RenderingRequest.post(dst.copy(Bitmap.Config.ARGB_8888, true), + mPreset, RenderingRequest.ICON_RENDERING, this); + } } } diff --git a/src/com/android/gallery3d/filtershow/ui/IconButton.java b/src/com/android/gallery3d/filtershow/ui/IconButton.java index ed10be301..2484d5feb 100644 --- a/src/com/android/gallery3d/filtershow/ui/IconButton.java +++ b/src/com/android/gallery3d/filtershow/ui/IconButton.java @@ -30,10 +30,10 @@ import android.widget.Button; */ public class IconButton extends Button { - protected Bitmap mImageMirror = null; - protected Bitmap mIcon = null; + private Bitmap mImageMirror = null; + private Bitmap mIcon = null; - protected boolean stale_icon = true; + private boolean stale_icon = true; public IconButton(Context context) { this(context, null); @@ -53,7 +53,9 @@ public class IconButton extends Button { } /** - * Set the image that the button icon will use. + * Set the image that the button icon will use. The image bitmap will be scaled + * and cropped into the largest square bitmap that will fit cleanly within the + * IconButton's layout. * * @param image image that icon will be set to before next draw. */ @@ -68,7 +70,7 @@ public class IconButton extends Button { * * @param image bitmap to use as icon */ - protected boolean makeAndSetIcon(Bitmap image) { + private boolean makeAndSetIcon(Bitmap image) { int size = getGoodIconSideSize(); if (size > 0) { return setImageIcon(makeImageIcon(image, size, size)); @@ -81,7 +83,7 @@ public class IconButton extends Button { * * @param image bitmap to set the icon to. */ - protected boolean setImageIcon(Bitmap image) { + private boolean setImageIcon(Bitmap image) { if (image == null) { return false; } @@ -99,11 +101,11 @@ public class IconButton extends Button { * @param height icon height * @return the scaled/cropped icon bitmap */ - protected Bitmap makeImageIcon(Bitmap image, int width, int height) { + private Bitmap makeImageIcon(Bitmap image, int width, int height) { Rect destination = new Rect(0, 0, width, height); Bitmap bmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - bmap = drawImage(bmap, image, destination); + drawImage(bmap, image, destination); return bmap; } @@ -113,7 +115,7 @@ public class IconButton extends Button { * * @return icon side length */ - protected int getGoodIconSideSize() { + private int getGoodIconSideSize() { Paint p = getPaint(); Rect bounds = new Rect(); String s = getText().toString(); @@ -128,7 +130,9 @@ public class IconButton extends Button { @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); - stale_icon = true; + if (w != oldw || h != oldh) { + stale_icon = true; + } } @Override @@ -140,12 +144,20 @@ public class IconButton extends Button { super.onDraw(canvas); } - // Override this for custom icon generation - protected Bitmap drawImage(Bitmap dst, Bitmap image, Rect destination) { - if (image != null) { + /** + * Draws the src image into the destination rectangle within the dst bitmap. + * If src is a non-square image, clips to be a square before drawing into dst. + * + * @param dst bitmap being drawn on. + * @param src bitmap to draw into dst. + * @param destination square in dst in which to draw src. + */ + protected static void drawImage(Bitmap dst, Bitmap src, Rect destination) { + if (src != null && dst != null && src.getWidth() > 0 && dst.getWidth() > 0 + && src.getHeight() > 0 && dst.getHeight() > 0) { Canvas canvas = new Canvas(dst); - int iw = image.getWidth(); - int ih = image.getHeight(); + int iw = src.getWidth(); + int ih = src.getHeight(); int x = 0; int y = 0; int size = 0; @@ -160,9 +172,8 @@ public class IconButton extends Button { y = (int) ((ih - size) / 2.0f); } source = new Rect(x, y, x + size, y + size); - canvas.drawBitmap(image, source, destination, new Paint()); + canvas.drawBitmap(src, source, destination, new Paint()); } - return dst; } } diff --git a/src/com/android/photos/canvas/CanvasProvider.java b/src/com/android/photos/canvas/CanvasProvider.java index 1bc55669a..92ca33c2c 100644 --- a/src/com/android/photos/canvas/CanvasProvider.java +++ b/src/com/android/photos/canvas/CanvasProvider.java @@ -318,8 +318,8 @@ public class CanvasProvider extends CanvasProviderBase { break; case BROWSE_HEADER_CASE_DEFAULT_ITEM_WIDTH: case BROWSE_HEADER_CASE_DEFAULT_ITEM_HEIGHT: - obj = MediaItem - .getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL); + int px = MediaItem.getTargetSize(MediaItem.TYPE_MICROTHUMBNAIL); + obj = px / getContext().getResources().getDisplayMetrics().density; break; } header[j] = obj; diff --git a/src/com/android/photos/data/PhotoProvider.java b/src/com/android/photos/data/PhotoProvider.java index 084401d16..8413206b1 100644 --- a/src/com/android/photos/data/PhotoProvider.java +++ b/src/com/android/photos/data/PhotoProvider.java @@ -84,7 +84,7 @@ public class PhotoProvider extends SQLiteContentProvider { */ public static interface Photos extends BaseColumns { /** Internal database table used for basic photo information. */ - public static final String TABLE = "photo"; + public static final String TABLE = "photos"; /** Content URI for basic photo and video information. */ public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); @@ -123,7 +123,7 @@ public class PhotoProvider extends SQLiteContentProvider { */ public static interface Albums extends BaseColumns { /** Internal database table used album information. */ - public static final String TABLE = "album"; + public static final String TABLE = "albums"; /** Content URI for album information. */ public static final Uri CONTENT_URI = Uri.withAppendedPath(BASE_CONTENT_URI, TABLE); |