summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/NewCameraActivity.java354
-rw-r--r--src/com/android/camera/NewCameraModule.java76
-rw-r--r--src/com/android/camera/NewPhotoMenu.java208
-rw-r--r--src/com/android/camera/NewPhotoModule.java2005
-rw-r--r--src/com/android/camera/NewPhotoUI.java806
-rw-r--r--src/com/android/camera/NewPreviewGestures.java358
-rw-r--r--src/com/android/camera/NewVideoMenu.java209
-rw-r--r--src/com/android/camera/NewVideoModule.java2352
-rw-r--r--src/com/android/camera/NewVideoUI.java724
-rw-r--r--src/com/android/camera/data/CameraDataAdapter.java341
-rw-r--r--src/com/android/camera/data/LocalData.java440
-rw-r--r--src/com/android/camera/ui/FaceView.java10
-rw-r--r--src/com/android/camera/ui/FilmStripGestureRecognizer.java112
-rw-r--r--src/com/android/camera/ui/FilmStripView.java1004
-rw-r--r--src/com/android/camera/ui/NewCameraRootView.java134
-rw-r--r--src/com/android/gallery3d/app/CommonControllerOverlay.java4
-rw-r--r--src/com/android/gallery3d/app/MoviePlayer.java12
-rw-r--r--src/com/android/gallery3d/app/TimeBar.java4
-rw-r--r--src/com/android/gallery3d/filtershow/FilterShowActivity.java32
-rw-r--r--src/com/android/gallery3d/filtershow/cache/CachingPipeline.java41
-rw-r--r--src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java1
-rw-r--r--src/com/android/gallery3d/filtershow/controller/BasicSlider.java1
-rw-r--r--src/com/android/gallery3d/filtershow/crop/CropMath.java9
-rw-r--r--src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java101
-rw-r--r--src/com/android/gallery3d/filtershow/data/FilterStackSource.java143
-rw-r--r--src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java39
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java21
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java1
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java1
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java5
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java1
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java31
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java24
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java41
-rw-r--r--src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java21
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilter.java17
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java11
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java6
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java4
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java49
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java5
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java2
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java3
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java6
-rw-r--r--src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java2
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java113
-rw-r--r--src/com/android/gallery3d/filtershow/imageshow/MasterImage.java2
-rw-r--r--src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java24
-rw-r--r--src/com/android/gallery3d/filtershow/presets/ImagePreset.java146
-rw-r--r--src/com/android/gallery3d/filtershow/presets/PipelineInterface.java31
-rw-r--r--src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java3
-rw-r--r--src/com/android/gallery3d/filtershow/tools/XmpPresets.java133
-rw-r--r--src/com/android/gallery3d/ui/MenuExecutor.java2
-rw-r--r--src/com/android/gallery3d/util/SaveVideoFileUtils.java19
-rw-r--r--src/com/android/photos/data/BitmapDecoder.java2
65 files changed, 10155 insertions, 119 deletions
diff --git a/src/com/android/camera/NewCameraActivity.java b/src/com/android/camera/NewCameraActivity.java
new file mode 100644
index 000000000..8ce5ce43b
--- /dev/null
+++ b/src/com/android/camera/NewCameraActivity.java
@@ -0,0 +1,354 @@
+/*
+ * 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.app.Activity;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.ServiceConnection;
+import android.content.pm.ActivityInfo;
+import android.content.res.Configuration;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.provider.Settings;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.view.WindowManager;
+
+import com.android.camera.data.CameraDataAdapter;
+import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
+import com.android.camera.ui.FilmStripView;
+import com.android.camera.ui.NewCameraRootView;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.util.LightCycleHelper;
+
+public class NewCameraActivity extends Activity
+ implements CameraSwitchListener {
+ public static final int PHOTO_MODULE_INDEX = 0;
+ public static final int VIDEO_MODULE_INDEX = 1;
+ public static final int PANORAMA_MODULE_INDEX = 2;
+ public static final int LIGHTCYCLE_MODULE_INDEX = 3;
+ private static final String INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE =
+ "android.media.action.STILL_IMAGE_CAMERA_SECURE";
+ public static final String ACTION_IMAGE_CAPTURE_SECURE =
+ "android.media.action.IMAGE_CAPTURE_SECURE";
+ // The intent extra for camera from secure lock screen. True if the gallery
+ // should only show newly captured pictures. sSecureAlbumId does not
+ // increment. This is used when switching between camera, camcorder, and
+ // panorama. If the extra is not set, it is in the normal camera mode.
+ public static final String SECURE_CAMERA_EXTRA = "secure_camera";
+
+ private CameraDataAdapter mDataAdapter;
+ private int mCurrentModuleIndex;
+ private NewCameraModule mCurrentModule;
+ private View mRootView;
+ private FilmStripView mFilmStripView;
+ private int mResultCodeForTesting;
+ private Intent mResultDataForTesting;
+ private OnScreenHint mStorageHint;
+ private long mStorageSpace = Storage.LOW_STORAGE_THRESHOLD;
+ private PhotoModule mController;
+ private boolean mAutoRotateScreen;
+ private boolean mSecureCamera;
+ private int mLastRawOrientation;
+ private MyOrientationEventListener mOrientationListener;
+ private class MyOrientationEventListener
+ extends OrientationEventListener {
+ public MyOrientationEventListener(Context context) {
+ super(context);
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ // We keep the last known orientation. So if the user first orient
+ // the camera then point the camera to floor or sky, we still have
+ // the correct orientation.
+ if (orientation == ORIENTATION_UNKNOWN) return;
+ mLastRawOrientation = orientation;
+ mCurrentModule.onOrientationChanged(orientation);
+ }
+ }
+ private MediaSaveService mMediaSaveService;
+ private ServiceConnection mConnection = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName className, IBinder b) {
+ mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService();
+ mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
+ }
+ @Override
+ public void onServiceDisconnected(ComponentName className) {
+ mMediaSaveService = null;
+ }};
+
+ public MediaSaveService getMediaSaveService() {
+ return mMediaSaveService;
+ }
+
+ private void bindMediaSaveService() {
+ Intent intent = new Intent(this, MediaSaveService.class);
+ startService(intent); // start service before binding it so the
+ // service won't be killed if we unbind it.
+ bindService(intent, mConnection, Context.BIND_AUTO_CREATE);
+ }
+
+ private void unbindMediaSaveService() {
+ mMediaSaveService.setListener(null);
+ unbindService(mConnection);
+ }
+
+ @Override
+ public void onCreate(Bundle state) {
+ super.onCreate(state);
+ setContentView(R.layout.camera_filmstrip);
+ if (ApiHelper.HAS_ROTATION_ANIMATION) {
+ setRotationAnimation();
+ }
+ // Check if this is in the secure camera mode.
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ if (INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) {
+ mSecureCamera = true;
+ // Use a new album when this is started from the lock screen.
+ //TODO: sSecureAlbumId++;
+ } else if (ACTION_IMAGE_CAPTURE_SECURE.equals(action)) {
+ mSecureCamera = true;
+ } else {
+ mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false);
+ }
+ /*TODO: if (mSecureCamera) {
+ IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ registerReceiver(mScreenOffReceiver, filter);
+ if (sScreenOffReceiver == null) {
+ sScreenOffReceiver = new ScreenOffReceiver();
+ getApplicationContext().registerReceiver(sScreenOffReceiver, filter);
+ }
+ }*/
+ LayoutInflater inflater = getLayoutInflater();
+ View rootLayout = inflater.inflate(R.layout.camera, null, false);
+ mRootView = rootLayout.findViewById(R.id.camera_app_root);
+ mDataAdapter = new CameraDataAdapter(
+ new ColorDrawable(getResources().getColor(R.color.photo_placeholder)));
+ mFilmStripView = (FilmStripView) findViewById(R.id.filmstrip_view);
+ // Set up the camera preview first so the preview shows up ASAP.
+ mDataAdapter.setCameraPreviewInfo(rootLayout,
+ FilmStripView.ImageData.SIZE_FULL, FilmStripView.ImageData.SIZE_FULL);
+ mFilmStripView.setDataAdapter(mDataAdapter);
+ mFilmStripView.setListener(new FilmStripView.Listener() {
+ @Override
+ public void onDataPromoted(int dataID) {
+ mDataAdapter.removeData(dataID);
+ }
+
+ @Override
+ public void onDataDemoted(int dataID) {
+ mDataAdapter.removeData(dataID);
+ }
+
+ });
+ mCurrentModule = new NewPhotoModule();
+ mCurrentModule.init(this, mRootView);
+ mOrientationListener = new MyOrientationEventListener(this);
+ bindMediaSaveService();
+ }
+
+ private void setRotationAnimation() {
+ int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE;
+ rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE;
+ Window win = getWindow();
+ WindowManager.LayoutParams winParams = win.getAttributes();
+ winParams.rotationAnimation = rotationAnimation;
+ win.setAttributes(winParams);
+ }
+
+ @Override
+ public void onUserInteraction() {
+ super.onUserInteraction();
+ mCurrentModule.onUserInteraction();
+ }
+
+ @Override
+ public void onPause() {
+ mOrientationListener.disable();
+ mCurrentModule.onPauseBeforeSuper();
+ super.onPause();
+ mCurrentModule.onPauseAfterSuper();
+ }
+
+ @Override
+ public void onResume() {
+ if (Settings.System.getInt(getContentResolver(),
+ Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED);
+ mAutoRotateScreen = false;
+ } else {
+ setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR);
+ mAutoRotateScreen = true;
+ }
+ mOrientationListener.enable();
+ mCurrentModule.onResumeBeforeSuper();
+ super.onResume();
+ mCurrentModule.onResumeAfterSuper();
+
+ // The loading is done in background and will update the filmstrip later.
+ mDataAdapter.requestLoad(getContentResolver());
+ }
+
+ @Override
+ public void onDestroy() {
+ unbindMediaSaveService();
+ super.onDestroy();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration config) {
+ super.onConfigurationChanged(config);
+ mCurrentModule.onConfigurationChanged(config);
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent m) {
+ //if (mFilmStripView.isInCameraFullscreen()) {
+ // return mCurrentModule.dispatchTouchEvent(m);
+ //}
+ return mFilmStripView.dispatchTouchEvent(m);
+ }
+ public boolean isAutoRotateScreen() {
+ return mAutoRotateScreen;
+ }
+
+ protected void updateStorageSpace() {
+ mStorageSpace = Storage.getAvailableSpace();
+ }
+
+ protected long getStorageSpace() {
+ return mStorageSpace;
+ }
+
+ protected void updateStorageSpaceAndHint() {
+ updateStorageSpace();
+ updateStorageHint(mStorageSpace);
+ }
+
+ protected void updateStorageHint() {
+ updateStorageHint(mStorageSpace);
+ }
+
+ protected boolean updateStorageHintOnResume() {
+ return true;
+ }
+
+ protected void updateStorageHint(long storageSpace) {
+ String message = null;
+ if (storageSpace == Storage.UNAVAILABLE) {
+ message = getString(R.string.no_storage);
+ } else if (storageSpace == Storage.PREPARING) {
+ message = getString(R.string.preparing_sd);
+ } else if (storageSpace == Storage.UNKNOWN_SIZE) {
+ message = getString(R.string.access_sd_fail);
+ } else if (storageSpace <= Storage.LOW_STORAGE_THRESHOLD) {
+ message = getString(R.string.spaceIsLow_content);
+ }
+
+ if (message != null) {
+ if (mStorageHint == null) {
+ mStorageHint = OnScreenHint.makeText(this, message);
+ } else {
+ mStorageHint.setText(message);
+ }
+ mStorageHint.show();
+ } else if (mStorageHint != null) {
+ mStorageHint.cancel();
+ mStorageHint = null;
+ }
+ }
+
+ protected void setResultEx(int resultCode) {
+ mResultCodeForTesting = resultCode;
+ setResult(resultCode);
+ }
+
+ protected void setResultEx(int resultCode, Intent data) {
+ mResultCodeForTesting = resultCode;
+ mResultDataForTesting = data;
+ setResult(resultCode, data);
+ }
+
+ public int getResultCode() {
+ return mResultCodeForTesting;
+ }
+
+ public Intent getResultData() {
+ return mResultDataForTesting;
+ }
+
+ public boolean isSecureCamera() {
+ return mSecureCamera;
+ }
+
+ @Override
+ public void onCameraSelected(int i) {
+ if (mCurrentModuleIndex == i) return;
+
+ CameraHolder.instance().keep();
+ closeModule(mCurrentModule);
+ mCurrentModuleIndex = i;
+ switch (i) {
+ case VIDEO_MODULE_INDEX:
+ mCurrentModule = new NewVideoModule();
+ break;
+ case PHOTO_MODULE_INDEX:
+ mCurrentModule = new NewPhotoModule();
+ break;
+ /* TODO:
+ case LIGHTCYCLE_MODULE_INDEX:
+ mCurrentModule = LightCycleHelper.createPanoramaModule();
+ break; */
+ default:
+ break;
+ }
+
+ openModule(mCurrentModule);
+ mCurrentModule.onOrientationChanged(mLastRawOrientation);
+ if (mMediaSaveService != null) {
+ mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService);
+ }
+ }
+
+ private void openModule(NewCameraModule module) {
+ module.init(this, mRootView);
+ module.onResumeBeforeSuper();
+ module.onResumeAfterSuper();
+ }
+
+ private void closeModule(NewCameraModule module) {
+ module.onPauseBeforeSuper();
+ module.onPauseAfterSuper();
+ ((ViewGroup) mRootView).removeAllViews();
+ }
+
+ @Override
+ public void onShowSwitcherPopup() {
+ }
+}
diff --git a/src/com/android/camera/NewCameraModule.java b/src/com/android/camera/NewCameraModule.java
new file mode 100644
index 000000000..061cc6cca
--- /dev/null
+++ b/src/com/android/camera/NewCameraModule.java
@@ -0,0 +1,76 @@
+/*
+ * 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.content.Intent;
+import android.content.res.Configuration;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.View;
+
+public interface NewCameraModule {
+
+ public void init(NewCameraActivity activity, View frame);
+
+ public void onFullScreenChanged(boolean full);
+
+ public void onPauseBeforeSuper();
+
+ public void onPauseAfterSuper();
+
+ public void onResumeBeforeSuper();
+
+ public void onResumeAfterSuper();
+
+ public void onConfigurationChanged(Configuration config);
+
+ public void onStop();
+
+ public void installIntentFilter();
+
+ public void onActivityResult(int requestCode, int resultCode, Intent data);
+
+ public boolean onBackPressed();
+
+ public boolean onKeyDown(int keyCode, KeyEvent event);
+
+ public boolean onKeyUp(int keyCode, KeyEvent event);
+
+ public void onSingleTapUp(View view, int x, int y);
+
+ public boolean dispatchTouchEvent(MotionEvent m);
+
+ public void onPreviewTextureCopied();
+
+ public void onCaptureTextureCopied();
+
+ public void onUserInteraction();
+
+ public boolean updateStorageHintOnResume();
+
+ public void updateCameraAppView();
+
+ public boolean needsSwitcher();
+
+ public boolean needsPieMenu();
+
+ public void onOrientationChanged(int orientation);
+
+ public void onShowSwitcherPopup();
+
+ public void onMediaSaveServiceConnected(MediaSaveService s);
+}
diff --git a/src/com/android/camera/NewPhotoMenu.java b/src/com/android/camera/NewPhotoMenu.java
new file mode 100644
index 000000000..f3240339e
--- /dev/null
+++ b/src/com/android/camera/NewPhotoMenu.java
@@ -0,0 +1,208 @@
+/*
+ * 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 java.util.Locale;
+
+import android.content.res.Resources;
+import android.hardware.Camera.Parameters;
+
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.CountdownTimerPopup;
+import com.android.camera.ui.ListPrefSettingPopup;
+import com.android.camera.ui.PieItem;
+import com.android.camera.ui.PieItem.OnClickListener;
+import com.android.camera.ui.PieRenderer;
+import com.android.gallery3d.R;
+
+public class NewPhotoMenu extends PieController
+ implements CountdownTimerPopup.Listener,
+ ListPrefSettingPopup.Listener {
+ private static String TAG = "CAM_photomenu";
+
+ private static final int POS_HDR = 0;
+ private static final int POS_EXP = 1;
+ private static final int POS_MORE = 2;
+ private static final int POS_FLASH = 3;
+ private static final int POS_SWITCH = 4;
+ private static final int POS_WB = 1;
+ private static final int POS_SET = 2;
+
+ private final String mSettingOff;
+
+ private NewPhotoUI mUI;
+ private AbstractSettingPopup mPopup;
+ private NewCameraActivity mActivity;
+
+ public NewPhotoMenu(NewCameraActivity activity, NewPhotoUI ui, PieRenderer pie) {
+ super(activity, pie);
+ mUI = ui;
+ mSettingOff = activity.getString(R.string.setting_off_value);
+ mActivity = activity;
+ }
+
+ public void initialize(PreferenceGroup group) {
+ super.initialize(group);
+ mPopup = null;
+ PieItem item = null;
+ final Resources res = mActivity.getResources();
+ Locale locale = res.getConfiguration().locale;
+ // the order is from left to right in the menu
+
+ // hdr
+ if (group.findPreference(CameraSettings.KEY_CAMERA_HDR) != null) {
+ item = makeSwitchItem(CameraSettings.KEY_CAMERA_HDR, true);
+ mRenderer.addItem(item);
+ }
+ // exposure compensation
+ if (group.findPreference(CameraSettings.KEY_EXPOSURE) != null) {
+ item = makeItem(CameraSettings.KEY_EXPOSURE);
+ item.setLabel(res.getString(R.string.pref_exposure_label));
+ mRenderer.addItem(item);
+ }
+ // more settings
+ PieItem more = makeItem(R.drawable.ic_settings_holo_light);
+ more.setLabel(res.getString(R.string.camera_menu_more_label));
+ mRenderer.addItem(more);
+ // flash
+ if (group.findPreference(CameraSettings.KEY_FLASH_MODE) != null) {
+ item = makeItem(CameraSettings.KEY_FLASH_MODE);
+ item.setLabel(res.getString(R.string.pref_camera_flashmode_label));
+ mRenderer.addItem(item);
+ }
+ // camera switcher
+ if (group.findPreference(CameraSettings.KEY_CAMERA_ID) != null) {
+ item = makeSwitchItem(CameraSettings.KEY_CAMERA_ID, false);
+ final PieItem fitem = item;
+ item.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(PieItem item) {
+ // Find the index of next camera.
+ ListPreference pref = mPreferenceGroup
+ .findPreference(CameraSettings.KEY_CAMERA_ID);
+ if (pref != null) {
+ int index = pref.findIndexOfValue(pref.getValue());
+ CharSequence[] values = pref.getEntryValues();
+ index = (index + 1) % values.length;
+ pref.setValueIndex(index);
+ mListener.onCameraPickerClicked(index);
+ }
+ updateItem(fitem, CameraSettings.KEY_CAMERA_ID);
+ }
+ });
+ mRenderer.addItem(item);
+ }
+ // location
+ if (group.findPreference(CameraSettings.KEY_RECORD_LOCATION) != null) {
+ item = makeSwitchItem(CameraSettings.KEY_RECORD_LOCATION, true);
+ more.addItem(item);
+ if (mActivity.isSecureCamera()) {
+ // Prevent location preference from getting changed in secure camera mode
+ item.setEnabled(false);
+ }
+ }
+ // countdown timer
+ final ListPreference ctpref = group.findPreference(CameraSettings.KEY_TIMER);
+ final ListPreference beeppref = group.findPreference(CameraSettings.KEY_TIMER_SOUND_EFFECTS);
+ item = makeItem(R.drawable.ic_timer);
+ item.setLabel(res.getString(R.string.pref_camera_timer_title).toUpperCase(locale));
+ item.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(PieItem item) {
+ CountdownTimerPopup timerPopup = (CountdownTimerPopup) mActivity.getLayoutInflater().inflate(
+ R.layout.countdown_setting_popup, null, false);
+ timerPopup.initialize(ctpref, beeppref);
+ timerPopup.setSettingChangedListener(NewPhotoMenu.this);
+ mUI.dismissPopup();
+ mPopup = timerPopup;
+ mUI.showPopup(mPopup);
+ }
+ });
+ more.addItem(item);
+ // image size
+ item = makeItem(R.drawable.ic_imagesize);
+ final ListPreference sizePref = group.findPreference(CameraSettings.KEY_PICTURE_SIZE);
+ item.setLabel(res.getString(R.string.pref_camera_picturesize_title).toUpperCase(locale));
+ item.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(PieItem item) {
+ ListPrefSettingPopup popup = (ListPrefSettingPopup) mActivity.getLayoutInflater().inflate(
+ R.layout.list_pref_setting_popup, null, false);
+ popup.initialize(sizePref);
+ popup.setSettingChangedListener(NewPhotoMenu.this);
+ mUI.dismissPopup();
+ mPopup = popup;
+ mUI.showPopup(mPopup);
+ }
+ });
+ more.addItem(item);
+ // white balance
+ if (group.findPreference(CameraSettings.KEY_WHITE_BALANCE) != null) {
+ item = makeItem(CameraSettings.KEY_WHITE_BALANCE);
+ item.setLabel(res.getString(R.string.pref_camera_whitebalance_label));
+ more.addItem(item);
+ }
+ // scene mode
+ if (group.findPreference(CameraSettings.KEY_SCENE_MODE) != null) {
+ IconListPreference pref = (IconListPreference) group.findPreference(
+ CameraSettings.KEY_SCENE_MODE);
+ pref.setUseSingleIcon(true);
+ item = makeItem(CameraSettings.KEY_SCENE_MODE);
+ more.addItem(item);
+ }
+ }
+
+ @Override
+ // Hit when an item in a popup gets selected
+ public void onListPrefChanged(ListPreference pref) {
+ if (mPopup != null) {
+ mUI.dismissPopup();
+ }
+ onSettingChanged(pref);
+ }
+
+ public void popupDismissed() {
+ if (mPopup != null) {
+ mPopup = null;
+ }
+ }
+
+ // Return true if the preference has the specified key but not the value.
+ private static boolean notSame(ListPreference pref, String key, String value) {
+ return (key.equals(pref.getKey()) && !value.equals(pref.getValue()));
+ }
+
+ private void setPreference(String key, String value) {
+ ListPreference pref = mPreferenceGroup.findPreference(key);
+ if (pref != null && !value.equals(pref.getValue())) {
+ pref.setValue(value);
+ reloadPreferences();
+ }
+ }
+
+ @Override
+ public void onSettingChanged(ListPreference pref) {
+ // Reset the scene mode if HDR is set to on. Reset HDR if scene mode is
+ // set to non-auto.
+ if (notSame(pref, CameraSettings.KEY_CAMERA_HDR, mSettingOff)) {
+ setPreference(CameraSettings.KEY_SCENE_MODE, Parameters.SCENE_MODE_AUTO);
+ } else if (notSame(pref, CameraSettings.KEY_SCENE_MODE, Parameters.SCENE_MODE_AUTO)) {
+ setPreference(CameraSettings.KEY_CAMERA_HDR, mSettingOff);
+ }
+ super.onSettingChanged(pref);
+ }
+}
diff --git a/src/com/android/camera/NewPhotoModule.java b/src/com/android/camera/NewPhotoModule.java
new file mode 100644
index 000000000..e045d5d30
--- /dev/null
+++ b/src/com/android/camera/NewPhotoModule.java
@@ -0,0 +1,2005 @@
+/*
+ * 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.annotation.TargetApi;
+import android.app.Activity;
+import android.app.AlertDialog;
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.DialogInterface;
+import android.content.Intent;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.Size;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+import android.location.Location;
+import android.media.CameraProfile;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.ConditionVariable;
+import android.os.Handler;
+import android.os.Looper;
+import android.os.Message;
+import android.os.MessageQueue;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.SurfaceHolder;
+import android.view.View;
+import android.view.WindowManager;
+
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
+import com.android.camera.ui.PopupManager;
+import com.android.camera.ui.RotateTextToast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.exif.ExifTag;
+import com.android.gallery3d.exif.Rational;
+import com.android.gallery3d.filtershow.crop.CropExtras;
+import com.android.gallery3d.filtershow.FilterShowActivity;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Formatter;
+import java.util.List;
+
+public class NewPhotoModule
+ implements NewCameraModule,
+ PhotoController,
+ FocusOverlayManager.Listener,
+ CameraPreference.OnPreferenceChangedListener,
+ ShutterButton.OnShutterButtonListener,
+ MediaSaveService.Listener,
+ OnCountDownFinishedListener,
+ SensorEventListener {
+
+ private static final String TAG = "CAM_PhotoModule";
+
+ // We number the request code from 1000 to avoid collision with Gallery.
+ private static final int REQUEST_CROP = 1000;
+
+ private static final int SETUP_PREVIEW = 1;
+ private static final int FIRST_TIME_INIT = 2;
+ private static final int CLEAR_SCREEN_DELAY = 3;
+ private static final int SET_CAMERA_PARAMETERS_WHEN_IDLE = 4;
+ private static final int CHECK_DISPLAY_ROTATION = 5;
+ private static final int SHOW_TAP_TO_FOCUS_TOAST = 6;
+ private static final int SWITCH_CAMERA = 7;
+ private static final int SWITCH_CAMERA_START_ANIMATION = 8;
+ private static final int CAMERA_OPEN_DONE = 9;
+ private static final int START_PREVIEW_DONE = 10;
+ private static final int OPEN_CAMERA_FAIL = 11;
+ private static final int CAMERA_DISABLED = 12;
+
+ // The subset of parameters we need to update in setCameraParameters().
+ private static final int UPDATE_PARAM_INITIALIZE = 1;
+ private static final int UPDATE_PARAM_ZOOM = 2;
+ private static final int UPDATE_PARAM_PREFERENCE = 4;
+ private static final int UPDATE_PARAM_ALL = -1;
+
+ // This is the timeout to keep the camera in onPause for the first time
+ // after screen on if the activity is started from secure lock screen.
+ private static final int KEEP_CAMERA_TIMEOUT = 1000; // ms
+
+ // copied from Camera hierarchy
+ private NewCameraActivity mActivity;
+ private CameraProxy mCameraDevice;
+ private int mCameraId;
+ private Parameters mParameters;
+ private boolean mPaused;
+
+ private NewPhotoUI mUI;
+
+ // -1 means camera is not switching.
+ protected int mPendingSwitchCameraId = -1;
+ private boolean mOpenCameraFail;
+ private boolean mCameraDisabled;
+
+ // When setCameraParametersWhenIdle() is called, we accumulate the subsets
+ // needed to be updated in mUpdateSet.
+ private int mUpdateSet;
+
+ private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+ private int mZoomValue; // The current zoom value.
+
+ private Parameters mInitialParams;
+ private boolean mFocusAreaSupported;
+ private boolean mMeteringAreaSupported;
+ private boolean mAeLockSupported;
+ private boolean mAwbLockSupported;
+ private boolean mContinousFocusSupported;
+
+ // The degrees of the device rotated clockwise from its natural orientation.
+ private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+ private ComboPreferences mPreferences;
+
+ private static final String sTempCropFilename = "crop-temp";
+
+ private ContentProviderClient mMediaProviderClient;
+ private boolean mFaceDetectionStarted = false;
+
+ // mCropValue and mSaveUri are used only if isImageCaptureIntent() is true.
+ private String mCropValue;
+ private Uri mSaveUri;
+
+ // We use a queue to generated names of the images to be used later
+ // when the image is ready to be saved.
+ private NamedImages mNamedImages;
+
+ private Runnable mDoSnapRunnable = new Runnable() {
+ @Override
+ public void run() {
+ onShutterButtonClick();
+ }
+ };
+
+ private final StringBuilder mBuilder = new StringBuilder();
+ private final Formatter mFormatter = new Formatter(mBuilder);
+ private final Object[] mFormatterArgs = new Object[1];
+
+ /**
+ * An unpublished intent flag requesting to return as soon as capturing
+ * is completed.
+ *
+ * TODO: consider publishing by moving into MediaStore.
+ */
+ private static final String EXTRA_QUICK_CAPTURE =
+ "android.intent.extra.quickCapture";
+
+ // The display rotation in degrees. This is only valid when mCameraState is
+ // not PREVIEW_STOPPED.
+ private int mDisplayRotation;
+ // The value for android.hardware.Camera.setDisplayOrientation.
+ private int mCameraDisplayOrientation;
+ // The value for UI components like indicators.
+ private int mDisplayOrientation;
+ // The value for android.hardware.Camera.Parameters.setRotation.
+ private int mJpegRotation;
+ private boolean mFirstTimeInitialized;
+ private boolean mIsImageCaptureIntent;
+
+ private int mCameraState = PREVIEW_STOPPED;
+ private boolean mSnapshotOnIdle = false;
+
+ private ContentResolver mContentResolver;
+
+ private LocationManager mLocationManager;
+
+ private final ShutterCallback mShutterCallback = new ShutterCallback();
+ private final PostViewPictureCallback mPostViewPictureCallback =
+ new PostViewPictureCallback();
+ private final RawPictureCallback mRawPictureCallback =
+ new RawPictureCallback();
+ private final AutoFocusCallback mAutoFocusCallback =
+ new AutoFocusCallback();
+ private final Object mAutoFocusMoveCallback =
+ ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK
+ ? new AutoFocusMoveCallback()
+ : null;
+
+ private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
+
+ private long mFocusStartTime;
+ private long mShutterCallbackTime;
+ private long mPostViewPictureCallbackTime;
+ private long mRawPictureCallbackTime;
+ private long mJpegPictureCallbackTime;
+ private long mOnResumeTime;
+ private byte[] mJpegImageData;
+
+ // These latency time are for the CameraLatency test.
+ public long mAutoFocusTime;
+ public long mShutterLag;
+ public long mShutterToPictureDisplayedTime;
+ public long mPictureDisplayedToJpegCallbackTime;
+ public long mJpegCallbackFinishTime;
+ public long mCaptureStartTime;
+
+ // This handles everything about focus.
+ private FocusOverlayManager mFocusManager;
+
+ private String mSceneMode;
+
+ private final Handler mHandler = new MainHandler();
+ private PreferenceGroup mPreferenceGroup;
+
+ private boolean mQuickCapture;
+ private SensorManager mSensorManager;
+ private float[] mGData = new float[3];
+ private float[] mMData = new float[3];
+ private float[] mR = new float[16];
+ private int mHeading = -1;
+
+ CameraStartUpThread mCameraStartUpThread;
+ ConditionVariable mStartPreviewPrerequisiteReady = new ConditionVariable();
+
+ private MediaSaveService.OnMediaSavedListener mOnMediaSavedListener =
+ new MediaSaveService.OnMediaSavedListener() {
+ @Override
+ public void onMediaSaved(Uri uri) {
+ if (uri != null) {
+ // TODO: Commenting out the line below for now. need to get it working
+ // mActivity.addSecureAlbumItemIfNeeded(false, uri);
+ Util.broadcastNewPicture(mActivity, uri);
+ }
+ }
+ };
+
+ // The purpose is not to block the main thread in onCreate and onResume.
+ private class CameraStartUpThread extends Thread {
+ private volatile boolean mCancelled;
+
+ public void cancel() {
+ mCancelled = true;
+ interrupt();
+ }
+
+ public boolean isCanceled() {
+ return mCancelled;
+ }
+
+ @Override
+ public void run() {
+ try {
+ // We need to check whether the activity is paused before long
+ // operations to ensure that onPause() can be done ASAP.
+ if (mCancelled) return;
+ mCameraDevice = Util.openCamera(mActivity, mCameraId);
+ mParameters = mCameraDevice.getParameters();
+ // Wait until all the initialization needed by startPreview are
+ // done.
+ mStartPreviewPrerequisiteReady.block();
+
+ initializeCapabilities();
+ if (mFocusManager == null) initializeFocusManager();
+ if (mCancelled) return;
+ setCameraParameters(UPDATE_PARAM_ALL);
+ mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
+ if (mCancelled) return;
+ startPreview();
+ mHandler.sendEmptyMessage(START_PREVIEW_DONE);
+ mOnResumeTime = SystemClock.uptimeMillis();
+ mHandler.sendEmptyMessage(CHECK_DISPLAY_ROTATION);
+ } catch (CameraHardwareException e) {
+ mHandler.sendEmptyMessage(OPEN_CAMERA_FAIL);
+ } catch (CameraDisabledException e) {
+ mHandler.sendEmptyMessage(CAMERA_DISABLED);
+ }
+ }
+ }
+
+ /**
+ * This Handler is used to post message back onto the main thread of the
+ * application
+ */
+ private class MainHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case SETUP_PREVIEW: {
+ setupPreview();
+ break;
+ }
+
+ case CLEAR_SCREEN_DELAY: {
+ mActivity.getWindow().clearFlags(
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ break;
+ }
+
+ case FIRST_TIME_INIT: {
+ initializeFirstTime();
+ break;
+ }
+
+ case SET_CAMERA_PARAMETERS_WHEN_IDLE: {
+ setCameraParametersWhenIdle(0);
+ break;
+ }
+
+ case CHECK_DISPLAY_ROTATION: {
+ // Set the display orientation if display rotation has changed.
+ // Sometimes this happens when the device is held upside
+ // down and camera app is opened. Rotation animation will
+ // take some time and the rotation value we have got may be
+ // wrong. Framework does not have a callback for this now.
+ if (Util.getDisplayRotation(mActivity) != mDisplayRotation) {
+ setDisplayOrientation();
+ }
+ if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
+ mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+ }
+ break;
+ }
+
+ case SHOW_TAP_TO_FOCUS_TOAST: {
+ showTapToFocusToast();
+ break;
+ }
+
+ case SWITCH_CAMERA: {
+ switchCamera();
+ break;
+ }
+
+ case SWITCH_CAMERA_START_ANIMATION: {
+ // TODO: Need to revisit
+ // ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
+ break;
+ }
+
+ case CAMERA_OPEN_DONE: {
+ onCameraOpened();
+ break;
+ }
+
+ case START_PREVIEW_DONE: {
+ onPreviewStarted();
+ break;
+ }
+
+ case OPEN_CAMERA_FAIL: {
+ mCameraStartUpThread = null;
+ mOpenCameraFail = true;
+ Util.showErrorAndFinish(mActivity,
+ R.string.cannot_connect_camera);
+ break;
+ }
+
+ case CAMERA_DISABLED: {
+ mCameraStartUpThread = null;
+ mCameraDisabled = true;
+ Util.showErrorAndFinish(mActivity,
+ R.string.camera_disabled);
+ break;
+ }
+ }
+ }
+ }
+
+ @Override
+ public void init(NewCameraActivity activity, View parent) {
+ mActivity = activity;
+ mUI = new NewPhotoUI(activity, this, parent);
+ mPreferences = new ComboPreferences(mActivity);
+ CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+ mCameraId = getPreferredCameraId(mPreferences);
+
+ mContentResolver = mActivity.getContentResolver();
+
+ // To reduce startup time, open the camera and start the preview in
+ // another thread.
+ mCameraStartUpThread = new CameraStartUpThread();
+ mCameraStartUpThread.start();
+
+ // Surface texture is from camera screen nail and startPreview needs it.
+ // This must be done before startPreview.
+ mIsImageCaptureIntent = isImageCaptureIntent();
+
+ mPreferences.setLocalId(mActivity, mCameraId);
+ CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+ // we need to reset exposure for the preview
+ resetExposureCompensation();
+ // Starting the preview needs preferences, camera screen nail, and
+ // focus area indicator.
+ mStartPreviewPrerequisiteReady.open();
+
+ initializeControlByIntent();
+ mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
+ mLocationManager = new LocationManager(mActivity, mUI);
+ mSensorManager = (SensorManager)(mActivity.getSystemService(Context.SENSOR_SERVICE));
+ }
+
+ private void initializeControlByIntent() {
+ mUI.initializeControlByIntent();
+ if (mIsImageCaptureIntent) {
+ setupCaptureParams();
+ }
+ }
+
+ private void onPreviewStarted() {
+ mCameraStartUpThread = null;
+ setCameraState(IDLE);
+ startFaceDetection();
+ locationFirstRun();
+ }
+
+ // Prompt the user to pick to record location for the very first run of
+ // camera only
+ private void locationFirstRun() {
+ if (RecordLocationPreference.isSet(mPreferences)) {
+ return;
+ }
+ if (mActivity.isSecureCamera()) return;
+ // Check if the back camera exists
+ int backCameraId = CameraHolder.instance().getBackCameraId();
+ if (backCameraId == -1) {
+ // If there is no back camera, do not show the prompt.
+ return;
+ }
+
+ new AlertDialog.Builder(mActivity)
+ .setTitle(R.string.remember_location_title)
+ .setMessage(R.string.remember_location_prompt)
+ .setPositiveButton(R.string.remember_location_yes, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int arg1) {
+ setLocationPreference(RecordLocationPreference.VALUE_ON);
+ }
+ })
+ .setNegativeButton(R.string.remember_location_no, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int arg1) {
+ dialog.cancel();
+ }
+ })
+ .setOnCancelListener(new DialogInterface.OnCancelListener() {
+ @Override
+ public void onCancel(DialogInterface dialog) {
+ setLocationPreference(RecordLocationPreference.VALUE_OFF);
+ }
+ })
+ .show();
+ }
+
+ private void setLocationPreference(String value) {
+ mPreferences.edit()
+ .putString(CameraSettings.KEY_RECORD_LOCATION, value)
+ .apply();
+ // TODO: Fix this to use the actual onSharedPreferencesChanged listener
+ // instead of invoking manually
+ onSharedPreferenceChanged();
+ }
+
+ private void onCameraOpened() {
+ View root = mUI.getRootView();
+ // These depend on camera parameters.
+
+ int width = root.getWidth();
+ int height = root.getHeight();
+ mFocusManager.setPreviewSize(width, height);
+ openCameraCommon();
+ }
+
+ private void switchCamera() {
+ if (mPaused) return;
+
+ Log.v(TAG, "Start to switch camera. id=" + mPendingSwitchCameraId);
+ mCameraId = mPendingSwitchCameraId;
+ mPendingSwitchCameraId = -1;
+ setCameraId(mCameraId);
+
+ // from onPause
+ closeCamera();
+ mUI.collapseCameraControls();
+ mUI.clearFaces();
+ if (mFocusManager != null) mFocusManager.removeMessages();
+
+ // Restart the camera and initialize the UI. From onCreate.
+ mPreferences.setLocalId(mActivity, mCameraId);
+ CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+ try {
+ mCameraDevice = Util.openCamera(mActivity, mCameraId);
+ mParameters = mCameraDevice.getParameters();
+ } catch (CameraHardwareException e) {
+ Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+ return;
+ } catch (CameraDisabledException e) {
+ Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+ return;
+ }
+ initializeCapabilities();
+ CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+ boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+ mFocusManager.setMirror(mirror);
+ mFocusManager.setParameters(mInitialParams);
+ setupPreview();
+
+ openCameraCommon();
+
+ 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);
+ }
+ }
+
+ protected void setCameraId(int cameraId) {
+ ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+ pref.setValue("" + cameraId);
+ }
+
+ // either open a new camera or switch cameras
+ private void openCameraCommon() {
+ loadCameraPreferences();
+
+ mUI.onCameraOpened(mPreferenceGroup, mPreferences, mParameters, this);
+ updateSceneMode();
+ showTapToFocusToastIfNeeded();
+
+
+ }
+
+ public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
+ if (mFocusManager != null) mFocusManager.setPreviewSize(width, height);
+ }
+
+ private void resetExposureCompensation() {
+ String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE,
+ CameraSettings.EXPOSURE_DEFAULT_VALUE);
+ if (!CameraSettings.EXPOSURE_DEFAULT_VALUE.equals(value)) {
+ Editor editor = mPreferences.edit();
+ editor.putString(CameraSettings.KEY_EXPOSURE, "0");
+ editor.apply();
+ }
+ }
+
+ private void keepMediaProviderInstance() {
+ // We want to keep a reference to MediaProvider in camera's lifecycle.
+ // TODO: Utilize mMediaProviderClient instance to replace
+ // ContentResolver calls.
+ if (mMediaProviderClient == null) {
+ mMediaProviderClient = mContentResolver
+ .acquireContentProviderClient(MediaStore.AUTHORITY);
+ }
+ }
+
+ // Snapshots can only be taken after this is called. It should be called
+ // once only. We could have done these things in onCreate() but we want to
+ // make preview screen appear as soon as possible.
+ private void initializeFirstTime() {
+ if (mFirstTimeInitialized) return;
+
+ // Initialize location service.
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+
+ keepMediaProviderInstance();
+
+ mUI.initializeFirstTime();
+ MediaSaveService s = mActivity.getMediaSaveService();
+ // We set the listener only when both service and shutterbutton
+ // are initialized.
+ if (s != null) {
+ s.setListener(this);
+ }
+
+ mNamedImages = new NamedImages();
+
+ mFirstTimeInitialized = true;
+ addIdleHandler();
+
+ mActivity.updateStorageSpaceAndHint();
+ }
+
+ // If the activity is paused and resumed, this method will be called in
+ // onResume.
+ private void initializeSecondTime() {
+ // Start location update if needed.
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+ MediaSaveService s = mActivity.getMediaSaveService();
+ if (s != null) {
+ s.setListener(this);
+ }
+ mNamedImages = new NamedImages();
+ mUI.initializeSecondTime(mParameters);
+ keepMediaProviderInstance();
+ }
+
+ @Override
+ public void onSurfaceCreated(SurfaceHolder holder) {
+ // Do not access the camera if camera start up thread is not finished.
+ if (mCameraDevice == null || mCameraStartUpThread != null)
+ return;
+
+ mCameraDevice.setPreviewDisplayAsync(holder);
+ // This happens when onConfigurationChanged arrives, surface has been
+ // destroyed, and there is no onFullScreenChanged.
+ if (mCameraState == PREVIEW_STOPPED) {
+ setupPreview();
+ }
+ }
+
+ private void showTapToFocusToastIfNeeded() {
+ // Show the tap to focus toast if this is the first start.
+ if (mFocusAreaSupported &&
+ mPreferences.getBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, true)) {
+ // Delay the toast for one second to wait for orientation.
+ mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_FOCUS_TOAST, 1000);
+ }
+ }
+
+ private void addIdleHandler() {
+ MessageQueue queue = Looper.myQueue();
+ queue.addIdleHandler(new MessageQueue.IdleHandler() {
+ @Override
+ public boolean queueIdle() {
+ Storage.ensureOSXCompatible();
+ return false;
+ }
+ });
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void startFaceDetection() {
+ if (!ApiHelper.HAS_FACE_DETECTION) return;
+ if (mFaceDetectionStarted) return;
+ if (mParameters.getMaxNumDetectedFaces() > 0) {
+ mFaceDetectionStarted = true;
+ CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+ mUI.onStartFaceDetection(mDisplayOrientation,
+ (info.facing == CameraInfo.CAMERA_FACING_FRONT));
+ mCameraDevice.setFaceDetectionListener(mUI);
+ mCameraDevice.startFaceDetection();
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ @Override
+ public void stopFaceDetection() {
+ if (!ApiHelper.HAS_FACE_DETECTION) return;
+ if (!mFaceDetectionStarted) return;
+ if (mParameters.getMaxNumDetectedFaces() > 0) {
+ mFaceDetectionStarted = false;
+ mCameraDevice.setFaceDetectionListener(null);
+ mCameraDevice.stopFaceDetection();
+ mUI.clearFaces();
+ }
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent m) {
+ if (mCameraState == SWITCHING_CAMERA) return true;
+ return mUI.dispatchTouchEvent(m);
+ }
+
+ private final class ShutterCallback
+ implements android.hardware.Camera.ShutterCallback {
+ @Override
+ public void onShutter() {
+ mShutterCallbackTime = System.currentTimeMillis();
+ mShutterLag = mShutterCallbackTime - mCaptureStartTime;
+ Log.v(TAG, "mShutterLag = " + mShutterLag + "ms");
+ }
+ }
+
+ private final class PostViewPictureCallback implements PictureCallback {
+ @Override
+ public void onPictureTaken(
+ byte [] data, android.hardware.Camera camera) {
+ mPostViewPictureCallbackTime = System.currentTimeMillis();
+ Log.v(TAG, "mShutterToPostViewCallbackTime = "
+ + (mPostViewPictureCallbackTime - mShutterCallbackTime)
+ + "ms");
+ }
+ }
+
+ private final class RawPictureCallback implements PictureCallback {
+ @Override
+ public void onPictureTaken(
+ byte [] rawData, android.hardware.Camera camera) {
+ mRawPictureCallbackTime = System.currentTimeMillis();
+ Log.v(TAG, "mShutterToRawCallbackTime = "
+ + (mRawPictureCallbackTime - mShutterCallbackTime) + "ms");
+ }
+ }
+
+ private final class JpegPictureCallback implements PictureCallback {
+ Location mLocation;
+
+ public JpegPictureCallback(Location loc) {
+ mLocation = loc;
+ }
+
+ @Override
+ public void onPictureTaken(
+ final byte [] jpegData, final android.hardware.Camera camera) {
+ if (mPaused) {
+ return;
+ }
+ if (mSceneMode == Util.SCENE_MODE_HDR) {
+ mUI.showSwitcher();
+ //TODO: mActivity.setSwipingEnabled(true);
+ }
+
+ mJpegPictureCallbackTime = System.currentTimeMillis();
+ // If postview callback has arrived, the captured image is displayed
+ // in postview callback. If not, the captured image is displayed in
+ // raw picture callback.
+ if (mPostViewPictureCallbackTime != 0) {
+ mShutterToPictureDisplayedTime =
+ mPostViewPictureCallbackTime - mShutterCallbackTime;
+ mPictureDisplayedToJpegCallbackTime =
+ mJpegPictureCallbackTime - mPostViewPictureCallbackTime;
+ } else {
+ mShutterToPictureDisplayedTime =
+ mRawPictureCallbackTime - mShutterCallbackTime;
+ mPictureDisplayedToJpegCallbackTime =
+ mJpegPictureCallbackTime - mRawPictureCallbackTime;
+ }
+ Log.v(TAG, "mPictureDisplayedToJpegCallbackTime = "
+ + mPictureDisplayedToJpegCallbackTime + "ms");
+
+ /*TODO:
+ // Only animate when in full screen capture mode
+ // i.e. If monkey/a user swipes to the gallery during picture taking,
+ // don't show animation
+ if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
+ && mActivity.mShowCameraAppView) {
+ // Finish capture animation
+ ((CameraScreenNail) mActivity.mCameraScreenNail).animateSlide();
+ } */
+ mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden.
+ if (!mIsImageCaptureIntent) {
+ if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) {
+ setupPreview();
+ } else {
+ // Camera HAL of some devices have a bug. Starting preview
+ // immediately after taking a picture will fail. Wait some
+ // time before starting the preview.
+ mHandler.sendEmptyMessageDelayed(SETUP_PREVIEW, 300);
+ }
+ }
+
+ if (!mIsImageCaptureIntent) {
+ // Calculate the width and the height of the jpeg.
+ Size s = mParameters.getPictureSize();
+ ExifInterface exif = Exif.getExif(jpegData);
+ int orientation = Exif.getOrientation(exif);
+ int width, height;
+ if ((mJpegRotation + orientation) % 180 == 0) {
+ width = s.width;
+ height = s.height;
+ } else {
+ width = s.height;
+ height = s.width;
+ }
+ String title = mNamedImages.getTitle();
+ long date = mNamedImages.getDate();
+ if (title == null) {
+ Log.e(TAG, "Unbalanced name/data pair");
+ } else {
+ if (date == -1) date = mCaptureStartTime;
+ if (mHeading >= 0) {
+ // heading direction has been updated by the sensor.
+ ExifTag directionRefTag = exif.buildTag(
+ ExifInterface.TAG_GPS_IMG_DIRECTION_REF,
+ ExifInterface.GpsTrackRef.MAGNETIC_DIRECTION);
+ ExifTag directionTag = exif.buildTag(
+ ExifInterface.TAG_GPS_IMG_DIRECTION,
+ new Rational(mHeading, 1));
+ exif.setTag(directionRefTag);
+ exif.setTag(directionTag);
+ }
+ mActivity.getMediaSaveService().addImage(
+ jpegData, title, date, mLocation, width, height,
+ orientation, exif, mOnMediaSavedListener, mContentResolver);
+ }
+ } else {
+ mJpegImageData = jpegData;
+ if (!mQuickCapture) {
+ mUI.showPostCaptureAlert();
+ } else {
+ onCaptureDone();
+ }
+ }
+
+ // Check this in advance of each shot so we don't add to shutter
+ // latency. It's true that someone else could write to the SD card in
+ // the mean time and fill it, but that could have happened between the
+ // shutter press and saving the JPEG too.
+ mActivity.updateStorageSpaceAndHint();
+
+ long now = System.currentTimeMillis();
+ mJpegCallbackFinishTime = now - mJpegPictureCallbackTime;
+ Log.v(TAG, "mJpegCallbackFinishTime = "
+ + mJpegCallbackFinishTime + "ms");
+ mJpegPictureCallbackTime = 0;
+ }
+ }
+
+ private final class AutoFocusCallback
+ implements android.hardware.Camera.AutoFocusCallback {
+ @Override
+ public void onAutoFocus(
+ boolean focused, android.hardware.Camera camera) {
+ if (mPaused) return;
+
+ mAutoFocusTime = System.currentTimeMillis() - mFocusStartTime;
+ Log.v(TAG, "mAutoFocusTime = " + mAutoFocusTime + "ms");
+ setCameraState(IDLE);
+ mFocusManager.onAutoFocus(focused, mUI.isShutterPressed());
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private final class AutoFocusMoveCallback
+ implements android.hardware.Camera.AutoFocusMoveCallback {
+ @Override
+ public void onAutoFocusMoving(
+ boolean moving, android.hardware.Camera camera) {
+ mFocusManager.onAutoFocusMoving(moving);
+ }
+ }
+
+ private static class NamedImages {
+ private ArrayList<NamedEntity> mQueue;
+ private boolean mStop;
+ private NamedEntity mNamedEntity;
+
+ public NamedImages() {
+ mQueue = new ArrayList<NamedEntity>();
+ }
+
+ public void nameNewImage(ContentResolver resolver, long date) {
+ NamedEntity r = new NamedEntity();
+ r.title = Util.createJpegName(date);
+ r.date = date;
+ mQueue.add(r);
+ }
+
+ public String getTitle() {
+ if (mQueue.isEmpty()) {
+ mNamedEntity = null;
+ return null;
+ }
+ mNamedEntity = mQueue.get(0);
+ mQueue.remove(0);
+
+ return mNamedEntity.title;
+ }
+
+ // Must be called after getTitle().
+ public long getDate() {
+ if (mNamedEntity == null) return -1;
+ return mNamedEntity.date;
+ }
+
+ private static class NamedEntity {
+ String title;
+ long date;
+ }
+ }
+
+ private void setCameraState(int state) {
+ mCameraState = state;
+ switch (state) {
+ case PhotoController.PREVIEW_STOPPED:
+ case PhotoController.SNAPSHOT_IN_PROGRESS:
+ case PhotoController.FOCUSING:
+ case PhotoController.SWITCHING_CAMERA:
+ mUI.enableGestures(false);
+ break;
+ case PhotoController.IDLE:
+ mUI.enableGestures(true);
+ break;
+ }
+ }
+
+ private void animateFlash() {
+ /* //TODO:
+ // Only animate when in full screen capture mode
+ // i.e. If monkey/a user swipes to the gallery during picture taking,
+ // don't show animation
+ if (ApiHelper.HAS_SURFACE_TEXTURE && !mIsImageCaptureIntent
+ && mActivity.mShowCameraAppView) {
+ // Start capture animation.
+ ((CameraScreenNail) mActivity.mCameraScreenNail).animateFlash(mDisplayRotation);
+ } */
+ }
+
+ @Override
+ public boolean capture() {
+ // If we are already in the middle of taking a snapshot or the image save request
+ // is full then ignore.
+ if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
+ || mCameraState == SWITCHING_CAMERA
+ || mActivity.getMediaSaveService().isQueueFull()) {
+ return false;
+ }
+ mCaptureStartTime = System.currentTimeMillis();
+ mPostViewPictureCallbackTime = 0;
+ mJpegImageData = null;
+
+ final boolean animateBefore = (mSceneMode == Util.SCENE_MODE_HDR);
+
+ if (animateBefore) {
+ animateFlash();
+ }
+
+ // Set rotation and gps data.
+ int orientation = (360 - mDisplayRotation) % 360;
+ // We need to be consistent with the framework orientation (i.e. the
+ // orientation of the UI.) when the auto-rotate screen setting is on.
+ if (mActivity.isAutoRotateScreen()) {
+ orientation = (360 - mDisplayRotation) % 360;
+ } else {
+ orientation = mOrientation;
+ }
+ mJpegRotation = Util.getJpegRotation(mCameraId, orientation);
+ mParameters.setRotation(mJpegRotation);
+ Location loc = mLocationManager.getCurrentLocation();
+ Util.setGpsParameters(mParameters, loc);
+ mCameraDevice.setParameters(mParameters);
+
+ mCameraDevice.takePicture2(mShutterCallback, mRawPictureCallback,
+ mPostViewPictureCallback, new JpegPictureCallback(loc),
+ mCameraState, mFocusManager.getFocusState());
+
+ if (!animateBefore) {
+ animateFlash();
+ }
+
+ mNamedImages.nameNewImage(mContentResolver, mCaptureStartTime);
+
+ mFaceDetectionStarted = false;
+ setCameraState(SNAPSHOT_IN_PROGRESS);
+ return true;
+ }
+
+ @Override
+ public void setFocusParameters() {
+ setCameraParameters(UPDATE_PARAM_PREFERENCE);
+ }
+
+ private int getPreferredCameraId(ComboPreferences preferences) {
+ int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
+ if (intentCameraId != -1) {
+ // Testing purpose. Launch a specific camera through the intent
+ // extras.
+ return intentCameraId;
+ } else {
+ return CameraSettings.readPreferredCameraId(preferences);
+ }
+ }
+
+ private void updateSceneMode() {
+ // If scene mode is set, we cannot set flash mode, white balance, and
+ // focus mode, instead, we read it from driver
+ if (!Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+ overrideCameraSettings(mParameters.getFlashMode(),
+ mParameters.getWhiteBalance(), mParameters.getFocusMode());
+ } else {
+ overrideCameraSettings(null, null, null);
+ }
+ }
+
+ private void overrideCameraSettings(final String flashMode,
+ final String whiteBalance, final String focusMode) {
+ mUI.overrideSettings(
+ CameraSettings.KEY_FLASH_MODE, flashMode,
+ CameraSettings.KEY_WHITE_BALANCE, whiteBalance,
+ CameraSettings.KEY_FOCUS_MODE, focusMode);
+ }
+
+ private void loadCameraPreferences() {
+ CameraSettings settings = new CameraSettings(mActivity, mInitialParams,
+ mCameraId, CameraHolder.instance().getCameraInfo());
+ mPreferenceGroup = settings.getPreferenceGroup(R.xml.camera_preferences);
+ }
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ // We keep the last known orientation. So if the user first orient
+ // the camera then point the camera to floor or sky, we still have
+ // the correct orientation.
+ if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+ mOrientation = Util.roundOrientation(orientation, mOrientation);
+
+ // Show the toast after getting the first orientation changed.
+ if (mHandler.hasMessages(SHOW_TAP_TO_FOCUS_TOAST)) {
+ mHandler.removeMessages(SHOW_TAP_TO_FOCUS_TOAST);
+ showTapToFocusToast();
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (mMediaProviderClient != null) {
+ mMediaProviderClient.release();
+ mMediaProviderClient = null;
+ }
+ }
+
+ @Override
+ public void onCaptureCancelled() {
+ mActivity.setResultEx(Activity.RESULT_CANCELED, new Intent());
+ mActivity.finish();
+ }
+
+ @Override
+ public void onCaptureRetake() {
+ if (mPaused)
+ return;
+ mUI.hidePostCaptureAlert();
+ setupPreview();
+ }
+
+ @Override
+ public void onCaptureDone() {
+ if (mPaused) {
+ return;
+ }
+
+ byte[] data = mJpegImageData;
+
+ if (mCropValue == null) {
+ // First handle the no crop case -- just return the value. If the
+ // caller specifies a "save uri" then write the data to its
+ // stream. Otherwise, pass back a scaled down version of the bitmap
+ // directly in the extras.
+ if (mSaveUri != null) {
+ OutputStream outputStream = null;
+ try {
+ outputStream = mContentResolver.openOutputStream(mSaveUri);
+ outputStream.write(data);
+ outputStream.close();
+
+ mActivity.setResultEx(Activity.RESULT_OK);
+ mActivity.finish();
+ } catch (IOException ex) {
+ // ignore exception
+ } finally {
+ Util.closeSilently(outputStream);
+ }
+ } else {
+ ExifInterface exif = Exif.getExif(data);
+ int orientation = Exif.getOrientation(exif);
+ Bitmap bitmap = Util.makeBitmap(data, 50 * 1024);
+ bitmap = Util.rotate(bitmap, orientation);
+ mActivity.setResultEx(Activity.RESULT_OK,
+ new Intent("inline-data").putExtra("data", bitmap));
+ mActivity.finish();
+ }
+ } else {
+ // Save the image to a temp file and invoke the cropper
+ Uri tempUri = null;
+ FileOutputStream tempStream = null;
+ try {
+ File path = mActivity.getFileStreamPath(sTempCropFilename);
+ path.delete();
+ tempStream = mActivity.openFileOutput(sTempCropFilename, 0);
+ tempStream.write(data);
+ tempStream.close();
+ tempUri = Uri.fromFile(path);
+ } catch (FileNotFoundException ex) {
+ mActivity.setResultEx(Activity.RESULT_CANCELED);
+ mActivity.finish();
+ return;
+ } catch (IOException ex) {
+ mActivity.setResultEx(Activity.RESULT_CANCELED);
+ mActivity.finish();
+ return;
+ } finally {
+ Util.closeSilently(tempStream);
+ }
+
+ Bundle newExtras = new Bundle();
+ if (mCropValue.equals("circle")) {
+ newExtras.putString("circleCrop", "true");
+ }
+ if (mSaveUri != null) {
+ newExtras.putParcelable(MediaStore.EXTRA_OUTPUT, mSaveUri);
+ } else {
+ newExtras.putBoolean(CropExtras.KEY_RETURN_DATA, true);
+ }
+ if (mActivity.isSecureCamera()) {
+ newExtras.putBoolean(CropExtras.KEY_SHOW_WHEN_LOCKED, true);
+ }
+
+ Intent cropIntent = new Intent(FilterShowActivity.CROP_ACTION);
+
+ cropIntent.setData(tempUri);
+ cropIntent.putExtras(newExtras);
+
+ mActivity.startActivityForResult(cropIntent, REQUEST_CROP);
+ }
+ }
+
+ @Override
+ public void onShutterButtonFocus(boolean pressed) {
+ if (mPaused || mUI.collapseCameraControls()
+ || (mCameraState == SNAPSHOT_IN_PROGRESS)
+ || (mCameraState == PREVIEW_STOPPED)) return;
+
+ // Do not do focus if there is not enough storage.
+ if (pressed && !canTakePicture()) return;
+
+ if (pressed) {
+ if (mSceneMode == Util.SCENE_MODE_HDR) {
+ mUI.hideSwitcher();
+ //TODO: mActivity.setSwipingEnabled(false);
+ }
+ mFocusManager.onShutterDown();
+ } else {
+ // for countdown mode, we need to postpone the shutter release
+ // i.e. lock the focus during countdown.
+ if (!mUI.isCountingDown()) {
+ mFocusManager.onShutterUp();
+ }
+ }
+ }
+
+ @Override
+ public void onShutterButtonClick() {
+ if (mPaused || mUI.collapseCameraControls()
+ || (mCameraState == SWITCHING_CAMERA)
+ || (mCameraState == PREVIEW_STOPPED)) return;
+
+ // Do not take the picture if there is not enough storage.
+ if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
+ Log.i(TAG, "Not enough space or storage not ready. remaining="
+ + mActivity.getStorageSpace());
+ return;
+ }
+ Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState);
+
+ // If the user wants to do a snapshot while the previous one is still
+ // in progress, remember the fact and do it after we finish the previous
+ // one and re-start the preview. Snapshot in progress also includes the
+ // state that autofocus is focusing and a picture will be taken when
+ // focus callback arrives.
+ if ((mFocusManager.isFocusingSnapOnFinish() || mCameraState == SNAPSHOT_IN_PROGRESS)
+ && !mIsImageCaptureIntent) {
+ mSnapshotOnIdle = true;
+ return;
+ }
+
+ String timer = mPreferences.getString(
+ CameraSettings.KEY_TIMER,
+ mActivity.getString(R.string.pref_camera_timer_default));
+ boolean playSound = mPreferences.getString(CameraSettings.KEY_TIMER_SOUND_EFFECTS,
+ mActivity.getString(R.string.pref_camera_timer_sound_default))
+ .equals(mActivity.getString(R.string.setting_on_value));
+
+ int seconds = Integer.parseInt(timer);
+ // When shutter button is pressed, check whether the previous countdown is
+ // finished. If not, cancel the previous countdown and start a new one.
+ if (mUI.isCountingDown()) {
+ mUI.cancelCountDown();
+ }
+ if (seconds > 0) {
+ mUI.startCountDown(seconds, playSound);
+ } else {
+ mSnapshotOnIdle = false;
+ mFocusManager.doSnap();
+ }
+ }
+
+ @Override
+ public void installIntentFilter() {
+ }
+
+ @Override
+ public boolean updateStorageHintOnResume() {
+ return mFirstTimeInitialized;
+ }
+
+ @Override
+ public void updateCameraAppView() {
+ }
+
+ @Override
+ public void onResumeBeforeSuper() {
+ mPaused = false;
+ }
+
+ @Override
+ public void onResumeAfterSuper() {
+ if (mOpenCameraFail || mCameraDisabled) return;
+
+ mJpegPictureCallbackTime = 0;
+ mZoomValue = 0;
+ // Start the preview if it is not started.
+ if (mCameraState == PREVIEW_STOPPED && mCameraStartUpThread == null) {
+ resetExposureCompensation();
+ mCameraStartUpThread = new CameraStartUpThread();
+ mCameraStartUpThread.start();
+ }
+
+ // If first time initialization is not finished, put it in the
+ // message queue.
+ if (!mFirstTimeInitialized) {
+ mHandler.sendEmptyMessage(FIRST_TIME_INIT);
+ } else {
+ initializeSecondTime();
+ }
+ keepScreenOnAwhile();
+
+ // Dismiss open menu if exists.
+ PopupManager.getInstance(mActivity).notifyShowPopup(null);
+ UsageStatistics.onContentViewChanged(
+ UsageStatistics.COMPONENT_CAMERA, "PhotoModule");
+
+ Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (gsensor != null) {
+ mSensorManager.registerListener(this, gsensor, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+
+ Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+ if (msensor != null) {
+ mSensorManager.registerListener(this, msensor, SensorManager.SENSOR_DELAY_NORMAL);
+ }
+ }
+
+ void waitCameraStartUpThread() {
+ try {
+ if (mCameraStartUpThread != null) {
+ mCameraStartUpThread.cancel();
+ mCameraStartUpThread.join();
+ mCameraStartUpThread = null;
+ setCameraState(IDLE);
+ }
+ } catch (InterruptedException e) {
+ // ignore
+ }
+ }
+
+ @Override
+ public void onPauseBeforeSuper() {
+ mPaused = true;
+ Sensor gsensor = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
+ if (gsensor != null) {
+ mSensorManager.unregisterListener(this, gsensor);
+ }
+
+ Sensor msensor = mSensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD);
+ if (msensor != null) {
+ mSensorManager.unregisterListener(this, msensor);
+ }
+ }
+
+ @Override
+ public void onPauseAfterSuper() {
+ // Wait the camera start up thread to finish.
+ waitCameraStartUpThread();
+
+ // When camera is started from secure lock screen for the first time
+ // after screen on, the activity gets onCreate->onResume->onPause->onResume.
+ // To reduce the latency, keep the camera for a short time so it does
+ // not need to be opened again.
+ if (mCameraDevice != null && mActivity.isSecureCamera()
+ && ActivityBase.isFirstStartAfterScreenOn()) {
+ ActivityBase.resetFirstStartAfterScreenOn();
+ CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT);
+ }
+ // Reset the focus first. Camera CTS does not guarantee that
+ // cancelAutoFocus is allowed after preview stops.
+ if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+ mCameraDevice.cancelAutoFocus();
+ }
+ stopPreview();
+
+ mNamedImages = null;
+
+ if (mLocationManager != null) mLocationManager.recordLocation(false);
+
+ // If we are in an image capture intent and has taken
+ // a picture, we just clear it in onPause.
+ mJpegImageData = null;
+
+ // Remove the messages in the event queue.
+ mHandler.removeMessages(SETUP_PREVIEW);
+ mHandler.removeMessages(FIRST_TIME_INIT);
+ mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
+ mHandler.removeMessages(SWITCH_CAMERA);
+ mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
+ mHandler.removeMessages(CAMERA_OPEN_DONE);
+ mHandler.removeMessages(START_PREVIEW_DONE);
+ mHandler.removeMessages(OPEN_CAMERA_FAIL);
+ mHandler.removeMessages(CAMERA_DISABLED);
+
+ closeCamera();
+
+ resetScreenOn();
+ mUI.onPause();
+
+ mPendingSwitchCameraId = -1;
+ if (mFocusManager != null) mFocusManager.removeMessages();
+ MediaSaveService s = mActivity.getMediaSaveService();
+ if (s != null) {
+ s.setListener(null);
+ }
+ }
+
+ /**
+ * The focus manager is the first UI related element to get initialized,
+ * and it requires the RenderOverlay, so initialize it here
+ */
+ private void initializeFocusManager() {
+ // Create FocusManager object. startPreview needs it.
+ // if mFocusManager not null, reuse it
+ // otherwise create a new instance
+ if (mFocusManager != null) {
+ mFocusManager.removeMessages();
+ } else {
+ CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+ boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
+ String[] defaultFocusModes = mActivity.getResources().getStringArray(
+ R.array.pref_camera_focusmode_default_array);
+ mFocusManager = new FocusOverlayManager(mPreferences, defaultFocusModes,
+ mInitialParams, this, mirror,
+ mActivity.getMainLooper(), mUI);
+ }
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ Log.v(TAG, "onConfigurationChanged");
+ setDisplayOrientation();
+ }
+
+ @Override
+ public void onActivityResult(
+ int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_CROP: {
+ Intent intent = new Intent();
+ if (data != null) {
+ Bundle extras = data.getExtras();
+ if (extras != null) {
+ intent.putExtras(extras);
+ }
+ }
+ mActivity.setResultEx(resultCode, intent);
+ mActivity.finish();
+
+ File path = mActivity.getFileStreamPath(sTempCropFilename);
+ path.delete();
+
+ break;
+ }
+ }
+ }
+
+ private boolean canTakePicture() {
+ return isCameraIdle() && (mActivity.getStorageSpace() > Storage.LOW_STORAGE_THRESHOLD);
+ }
+
+ @Override
+ public void autoFocus() {
+ mFocusStartTime = System.currentTimeMillis();
+ mCameraDevice.autoFocus(mAutoFocusCallback);
+ setCameraState(FOCUSING);
+ }
+
+ @Override
+ public void cancelAutoFocus() {
+ mCameraDevice.cancelAutoFocus();
+ setCameraState(IDLE);
+ setCameraParameters(UPDATE_PARAM_PREFERENCE);
+ }
+
+ // Preview area is touched. Handle touch focus.
+ @Override
+ public void onSingleTapUp(View view, int x, int y) {
+ if (mPaused || mCameraDevice == null || !mFirstTimeInitialized
+ || mCameraState == SNAPSHOT_IN_PROGRESS
+ || mCameraState == SWITCHING_CAMERA
+ || mCameraState == PREVIEW_STOPPED) {
+ return;
+ }
+
+ // Do not trigger touch focus if popup window is opened.
+ if (mUI.removeTopLevelPopup()) return;
+
+ // Check if metering area or focus area is supported.
+ if (!mFocusAreaSupported && !mMeteringAreaSupported) return;
+ mFocusManager.onSingleTapUp(x, y);
+ }
+
+ @Override
+ public boolean onBackPressed() {
+ return mUI.onBackPressed();
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ case KeyEvent.KEYCODE_FOCUS:
+ if (/*TODO: mActivity.isInCameraApp() &&*/ mFirstTimeInitialized) {
+ if (event.getRepeatCount() == 0) {
+ onShutterButtonFocus(true);
+ }
+ return true;
+ }
+ return false;
+ case KeyEvent.KEYCODE_CAMERA:
+ if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+ onShutterButtonClick();
+ }
+ return true;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ // If we get a dpad center event without any focused view, move
+ // the focus to the shutter button and press it.
+ if (mFirstTimeInitialized && event.getRepeatCount() == 0) {
+ // Start auto-focus immediately to reduce shutter lag. After
+ // the shutter button gets the focus, onShutterButtonFocus()
+ // will be called again but it is fine.
+ if (mUI.removeTopLevelPopup()) return true;
+ onShutterButtonFocus(true);
+ mUI.pressShutterButton();
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_VOLUME_UP:
+ case KeyEvent.KEYCODE_VOLUME_DOWN:
+ if (/*mActivity.isInCameraApp() && */ mFirstTimeInitialized) {
+ onShutterButtonClick();
+ return true;
+ }
+ return false;
+ case KeyEvent.KEYCODE_FOCUS:
+ if (mFirstTimeInitialized) {
+ onShutterButtonFocus(false);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void closeCamera() {
+ if (mCameraDevice != null) {
+ mCameraDevice.setZoomChangeListener(null);
+ if(ApiHelper.HAS_FACE_DETECTION) {
+ mCameraDevice.setFaceDetectionListener(null);
+ }
+ mCameraDevice.setErrorCallback(null);
+ CameraHolder.instance().release();
+ mFaceDetectionStarted = false;
+ mCameraDevice = null;
+ setCameraState(PREVIEW_STOPPED);
+ mFocusManager.onCameraReleased();
+ }
+ }
+
+ private void setDisplayOrientation() {
+ mDisplayRotation = Util.getDisplayRotation(mActivity);
+ mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
+ mCameraDisplayOrientation = mDisplayOrientation;
+ mUI.setDisplayOrientation(mDisplayOrientation);
+ if (mFocusManager != null) {
+ mFocusManager.setDisplayOrientation(mDisplayOrientation);
+ }
+ // Change the camera display orientation
+ if (mCameraDevice != null) {
+ mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+ }
+ }
+
+ // Only called by UI thread.
+ private void setupPreview() {
+ mFocusManager.resetTouchFocus();
+ startPreview();
+ setCameraState(IDLE);
+ startFaceDetection();
+ }
+
+ // This can be called by UI Thread or CameraStartUpThread. So this should
+ // not modify the views.
+ private void startPreview() {
+ mCameraDevice.setErrorCallback(mErrorCallback);
+
+ // ICS camera frameworks has a bug. Face detection state is not cleared
+ // after taking a picture. Stop the preview to work around it. The bug
+ // was fixed in JB.
+ if (mCameraState != PREVIEW_STOPPED) stopPreview();
+
+ setDisplayOrientation();
+
+ if (!mSnapshotOnIdle) {
+ // If the focus mode is continuous autofocus, call cancelAutoFocus to
+ // resume it because it may have been paused by autoFocus call.
+ if (Util.FOCUS_MODE_CONTINUOUS_PICTURE.equals(mFocusManager.getFocusMode())) {
+ mCameraDevice.cancelAutoFocus();
+ }
+ mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
+ }
+ setCameraParameters(UPDATE_PARAM_ALL);
+ // Let UI set its expected aspect ratio
+ mUI.setPreviewSize(mParameters.getPreviewSize());
+ Object st = mUI.getSurfaceTexture();
+ if (st != null) {
+ mCameraDevice.setPreviewTextureAsync((SurfaceTexture) st);
+ }
+
+ Log.v(TAG, "startPreview");
+ mCameraDevice.startPreviewAsync();
+ mFocusManager.onPreviewStarted();
+
+ if (mSnapshotOnIdle) {
+ mHandler.post(mDoSnapRunnable);
+ }
+ }
+
+ @Override
+ public void stopPreview() {
+ if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) {
+ Log.v(TAG, "stopPreview");
+ mCameraDevice.stopPreview();
+ mFaceDetectionStarted = false;
+ }
+ setCameraState(PREVIEW_STOPPED);
+ if (mFocusManager != null) mFocusManager.onPreviewStopped();
+ }
+
+ @SuppressWarnings("deprecation")
+ private void updateCameraParametersInitialize() {
+ // Reset preview frame rate to the maximum because it may be lowered by
+ // video camera application.
+ List<Integer> frameRates = mParameters.getSupportedPreviewFrameRates();
+ if (frameRates != null) {
+ Integer max = Collections.max(frameRates);
+ mParameters.setPreviewFrameRate(max);
+ }
+
+ mParameters.set(Util.RECORDING_HINT, Util.FALSE);
+
+ // Disable video stabilization. Convenience methods not available in API
+ // level <= 14
+ String vstabSupported = mParameters.get("video-stabilization-supported");
+ if ("true".equals(vstabSupported)) {
+ mParameters.set("video-stabilization", "false");
+ }
+ }
+
+ private void updateCameraParametersZoom() {
+ // Set zoom.
+ if (mParameters.isZoomSupported()) {
+ mParameters.setZoom(mZoomValue);
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private void setAutoExposureLockIfSupported() {
+ if (mAeLockSupported) {
+ mParameters.setAutoExposureLock(mFocusManager.getAeAwbLock());
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private void setAutoWhiteBalanceLockIfSupported() {
+ if (mAwbLockSupported) {
+ mParameters.setAutoWhiteBalanceLock(mFocusManager.getAeAwbLock());
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void setFocusAreasIfSupported() {
+ if (mFocusAreaSupported) {
+ mParameters.setFocusAreas(mFocusManager.getFocusAreas());
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void setMeteringAreasIfSupported() {
+ if (mMeteringAreaSupported) {
+ // Use the same area for focus and metering.
+ mParameters.setMeteringAreas(mFocusManager.getMeteringAreas());
+ }
+ }
+
+ private void updateCameraParametersPreference() {
+ setAutoExposureLockIfSupported();
+ setAutoWhiteBalanceLockIfSupported();
+ setFocusAreasIfSupported();
+ setMeteringAreasIfSupported();
+
+ // Set picture size.
+ String pictureSize = mPreferences.getString(
+ CameraSettings.KEY_PICTURE_SIZE, null);
+ if (pictureSize == null) {
+ CameraSettings.initialCameraPictureSize(mActivity, mParameters);
+ } else {
+ List<Size> supported = mParameters.getSupportedPictureSizes();
+ CameraSettings.setCameraPictureSize(
+ pictureSize, supported, mParameters);
+ }
+ Size size = mParameters.getPictureSize();
+
+ // Set a preview size that is closest to the viewfinder height and has
+ // the right aspect ratio.
+ List<Size> sizes = mParameters.getSupportedPreviewSizes();
+ Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
+ (double) size.width / size.height);
+ Size original = mParameters.getPreviewSize();
+ if (!original.equals(optimalSize)) {
+ mParameters.setPreviewSize(optimalSize.width, optimalSize.height);
+
+ // Zoom related settings will be changed for different preview
+ // sizes, so set and read the parameters to get latest values
+ mCameraDevice.setParameters(mParameters);
+ mParameters = mCameraDevice.getParameters();
+ }
+ Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height);
+
+ // Since changing scene mode may change supported values, set scene mode
+ // first. HDR is a scene mode. To promote it in UI, it is stored in a
+ // separate preference.
+ String hdr = mPreferences.getString(CameraSettings.KEY_CAMERA_HDR,
+ mActivity.getString(R.string.pref_camera_hdr_default));
+ if (mActivity.getString(R.string.setting_on_value).equals(hdr)) {
+ mSceneMode = Util.SCENE_MODE_HDR;
+ } else {
+ mSceneMode = mPreferences.getString(
+ CameraSettings.KEY_SCENE_MODE,
+ mActivity.getString(R.string.pref_camera_scenemode_default));
+ }
+ if (Util.isSupported(mSceneMode, mParameters.getSupportedSceneModes())) {
+ if (!mParameters.getSceneMode().equals(mSceneMode)) {
+ mParameters.setSceneMode(mSceneMode);
+
+ // Setting scene mode will change the settings of flash mode,
+ // white balance, and focus mode. Here we read back the
+ // parameters, so we can know those settings.
+ mCameraDevice.setParameters(mParameters);
+ mParameters = mCameraDevice.getParameters();
+ }
+ } else {
+ mSceneMode = mParameters.getSceneMode();
+ if (mSceneMode == null) {
+ mSceneMode = Parameters.SCENE_MODE_AUTO;
+ }
+ }
+
+ // Set JPEG quality.
+ int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
+ CameraProfile.QUALITY_HIGH);
+ mParameters.setJpegQuality(jpegQuality);
+
+ // For the following settings, we need to check if the settings are
+ // still supported by latest driver, if not, ignore the settings.
+
+ // Set exposure compensation
+ int value = CameraSettings.readExposure(mPreferences);
+ int max = mParameters.getMaxExposureCompensation();
+ int min = mParameters.getMinExposureCompensation();
+ if (value >= min && value <= max) {
+ mParameters.setExposureCompensation(value);
+ } else {
+ Log.w(TAG, "invalid exposure range: " + value);
+ }
+
+ if (Parameters.SCENE_MODE_AUTO.equals(mSceneMode)) {
+ // Set flash mode.
+ String flashMode = mPreferences.getString(
+ CameraSettings.KEY_FLASH_MODE,
+ mActivity.getString(R.string.pref_camera_flashmode_default));
+ List<String> supportedFlash = mParameters.getSupportedFlashModes();
+ if (Util.isSupported(flashMode, supportedFlash)) {
+ mParameters.setFlashMode(flashMode);
+ } else {
+ flashMode = mParameters.getFlashMode();
+ if (flashMode == null) {
+ flashMode = mActivity.getString(
+ R.string.pref_camera_flashmode_no_flash);
+ }
+ }
+
+ // Set white balance parameter.
+ String whiteBalance = mPreferences.getString(
+ CameraSettings.KEY_WHITE_BALANCE,
+ mActivity.getString(R.string.pref_camera_whitebalance_default));
+ if (Util.isSupported(whiteBalance,
+ mParameters.getSupportedWhiteBalance())) {
+ mParameters.setWhiteBalance(whiteBalance);
+ } else {
+ whiteBalance = mParameters.getWhiteBalance();
+ if (whiteBalance == null) {
+ whiteBalance = Parameters.WHITE_BALANCE_AUTO;
+ }
+ }
+
+ // Set focus mode.
+ mFocusManager.overrideFocusMode(null);
+ mParameters.setFocusMode(mFocusManager.getFocusMode());
+ } else {
+ mFocusManager.overrideFocusMode(mParameters.getFocusMode());
+ }
+
+ if (mContinousFocusSupported && ApiHelper.HAS_AUTO_FOCUS_MOVE_CALLBACK) {
+ updateAutoFocusMoveCallback();
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.JELLY_BEAN)
+ private void updateAutoFocusMoveCallback() {
+ if (mParameters.getFocusMode().equals(Util.FOCUS_MODE_CONTINUOUS_PICTURE)) {
+ mCameraDevice.setAutoFocusMoveCallback(
+ (AutoFocusMoveCallback) mAutoFocusMoveCallback);
+ } else {
+ mCameraDevice.setAutoFocusMoveCallback(null);
+ }
+ }
+
+ // We separate the parameters into several subsets, so we can update only
+ // the subsets actually need updating. The PREFERENCE set needs extra
+ // locking because the preference can be changed from GLThread as well.
+ private void setCameraParameters(int updateSet) {
+ if ((updateSet & UPDATE_PARAM_INITIALIZE) != 0) {
+ updateCameraParametersInitialize();
+ }
+
+ if ((updateSet & UPDATE_PARAM_ZOOM) != 0) {
+ updateCameraParametersZoom();
+ }
+
+ if ((updateSet & UPDATE_PARAM_PREFERENCE) != 0) {
+ updateCameraParametersPreference();
+ }
+
+ mCameraDevice.setParameters(mParameters);
+ }
+
+ // If the Camera is idle, update the parameters immediately, otherwise
+ // accumulate them in mUpdateSet and update later.
+ private void setCameraParametersWhenIdle(int additionalUpdateSet) {
+ mUpdateSet |= additionalUpdateSet;
+ if (mCameraDevice == null) {
+ // We will update all the parameters when we open the device, so
+ // we don't need to do anything now.
+ mUpdateSet = 0;
+ return;
+ } else if (isCameraIdle()) {
+ setCameraParameters(mUpdateSet);
+ updateSceneMode();
+ mUpdateSet = 0;
+ } else {
+ if (!mHandler.hasMessages(SET_CAMERA_PARAMETERS_WHEN_IDLE)) {
+ mHandler.sendEmptyMessageDelayed(
+ SET_CAMERA_PARAMETERS_WHEN_IDLE, 1000);
+ }
+ }
+ }
+
+ public boolean isCameraIdle() {
+ return (mCameraState == IDLE) ||
+ (mCameraState == PREVIEW_STOPPED) ||
+ ((mFocusManager != null) && mFocusManager.isFocusCompleted()
+ && (mCameraState != SWITCHING_CAMERA));
+ }
+
+ public boolean isImageCaptureIntent() {
+ String action = mActivity.getIntent().getAction();
+ return (MediaStore.ACTION_IMAGE_CAPTURE.equals(action)
+ || ActivityBase.ACTION_IMAGE_CAPTURE_SECURE.equals(action));
+ }
+
+ private void setupCaptureParams() {
+ Bundle myExtras = mActivity.getIntent().getExtras();
+ if (myExtras != null) {
+ mSaveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+ mCropValue = myExtras.getString("crop");
+ }
+ }
+
+ @Override
+ public void onSharedPreferenceChanged() {
+ // ignore the events after "onPause()"
+ if (mPaused) return;
+
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+
+ setCameraParametersWhenIdle(UPDATE_PARAM_PREFERENCE);
+ mUI.updateOnScreenIndicators(mParameters, mPreferenceGroup, mPreferences);
+ }
+
+ @Override
+ public void onCameraPickerClicked(int cameraId) {
+ if (mPaused || mPendingSwitchCameraId != -1) return;
+
+ mPendingSwitchCameraId = cameraId;
+
+ Log.v(TAG, "Start to switch camera. cameraId=" + cameraId);
+ // We need to keep a preview frame for the animation before
+ // releasing the camera. This will trigger onPreviewTextureCopied.
+ //TODO: Need to animate the camera switch
+ switchCamera();
+ }
+
+ // Preview texture has been copied. Now camera can be released and the
+ // animation can be started.
+ @Override
+ public void onPreviewTextureCopied() {
+ mHandler.sendEmptyMessage(SWITCH_CAMERA);
+ }
+
+ @Override
+ public void onCaptureTextureCopied() {
+ }
+
+ @Override
+ public void onUserInteraction() {
+ if (!mActivity.isFinishing()) keepScreenOnAwhile();
+ }
+
+ private void resetScreenOn() {
+ mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ private void keepScreenOnAwhile() {
+ mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+ }
+
+ @Override
+ public void onOverriddenPreferencesClicked() {
+ if (mPaused) return;
+ mUI.showPreferencesToast();
+ }
+
+ private void showTapToFocusToast() {
+ // TODO: Use a toast?
+ new RotateTextToast(mActivity, R.string.tap_to_focus, 0).show();
+ // Clear the preference.
+ Editor editor = mPreferences.edit();
+ editor.putBoolean(CameraSettings.KEY_CAMERA_FIRST_USE_HINT_SHOWN, false);
+ editor.apply();
+ }
+
+ private void initializeCapabilities() {
+ mInitialParams = mCameraDevice.getParameters();
+ mFocusAreaSupported = Util.isFocusAreaSupported(mInitialParams);
+ mMeteringAreaSupported = Util.isMeteringAreaSupported(mInitialParams);
+ mAeLockSupported = Util.isAutoExposureLockSupported(mInitialParams);
+ mAwbLockSupported = Util.isAutoWhiteBalanceLockSupported(mInitialParams);
+ mContinousFocusSupported = mInitialParams.getSupportedFocusModes().contains(
+ Util.FOCUS_MODE_CONTINUOUS_PICTURE);
+ }
+
+ @Override
+ public void onCountDownFinished() {
+ mSnapshotOnIdle = false;
+ mFocusManager.doSnap();
+ mFocusManager.onShutterUp();
+ }
+
+ @Override
+ public boolean needsSwitcher() {
+ return !mIsImageCaptureIntent;
+ }
+
+ @Override
+ public boolean needsPieMenu() {
+ return true;
+ }
+
+ @Override
+ public void onShowSwitcherPopup() {
+ mUI.onShowSwitcherPopup();
+ }
+
+ @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 || mCameraDevice == null) return index;
+ // Set zoom parameters asynchronously
+ mParameters.setZoom(mZoomValue);
+ mCameraDevice.setParameters(mParameters);
+ Parameters p = mCameraDevice.getParameters();
+ if (p != null) return p.getZoom();
+ return index;
+ }
+
+ @Override
+ public int getCameraState() {
+ return mCameraState;
+ }
+
+ @Override
+ public void onQueueStatus(boolean full) {
+ mUI.enableShutter(!full);
+ }
+
+ @Override
+ public void onMediaSaveServiceConnected(MediaSaveService s) {
+ // We set the listener only when both service and shutterbutton
+ // are initialized.
+ if (mFirstTimeInitialized) {
+ s.setListener(this);
+ }
+ }
+
+ @Override
+ public void onAccuracyChanged(Sensor sensor, int accuracy) {
+ }
+
+ @Override
+ public void onSensorChanged(SensorEvent event) {
+ int type = event.sensor.getType();
+ float[] data;
+ if (type == Sensor.TYPE_ACCELEROMETER) {
+ data = mGData;
+ } else if (type == Sensor.TYPE_MAGNETIC_FIELD) {
+ data = mMData;
+ } else {
+ // we should not be here.
+ return;
+ }
+ for (int i = 0; i < 3 ; i++) {
+ data[i] = event.values[i];
+ }
+ float[] orientation = new float[3];
+ SensorManager.getRotationMatrix(mR, null, mGData, mMData);
+ SensorManager.getOrientation(mR, orientation);
+ mHeading = (int) (orientation[0] * 180f / Math.PI) % 360;
+ if (mHeading < 0) {
+ mHeading += 360;
+ }
+ }
+/* Below is no longer needed, except to get rid of compile error
+ * TODO: Remove these
+ */
+
+ // TODO: Delete this function after old camera code is removed
+ @Override
+ public void onRestorePreferencesClicked() {}
+
+ @Override
+ public void onFullScreenChanged(boolean full) {
+ /* //TODO:
+ mUI.onFullScreenChanged(full);
+ if (ApiHelper.HAS_SURFACE_TEXTURE) {
+ if (mActivity.mCameraScreenNail != null) {
+ ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full);
+ }
+ return;
+ } */
+ }
+
+}
diff --git a/src/com/android/camera/NewPhotoUI.java b/src/com/android/camera/NewPhotoUI.java
new file mode 100644
index 000000000..1a14cf524
--- /dev/null
+++ b/src/com/android/camera/NewPhotoUI.java
@@ -0,0 +1,806 @@
+/*
+ * 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.Matrix;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera;
+import android.hardware.Camera.Face;
+import android.hardware.Camera.FaceDetectionListener;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.TextureView;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+import android.view.ViewGroup;
+import android.view.ViewStub;
+import android.widget.FrameLayout;
+import android.widget.FrameLayout.LayoutParams;
+import android.widget.ImageView;
+import android.widget.Toast;
+
+import com.android.camera.CameraPreference.OnPreferenceChangedListener;
+import com.android.camera.FocusOverlayManager.FocusUI;
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
+import com.android.camera.ui.CountDownView;
+import com.android.camera.ui.CountDownView.OnCountDownFinishedListener;
+import com.android.camera.ui.CameraSwitcher;
+import com.android.camera.ui.FaceView;
+import com.android.camera.ui.FocusIndicator;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.PieRenderer.PieListener;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.io.IOException;
+import java.util.List;
+
+public class NewPhotoUI implements PieListener,
+ NewPreviewGestures.SingleTapListener,
+ NewPreviewGestures.CancelEventListener,
+ FocusUI, TextureView.SurfaceTextureListener,
+ LocationManager.Listener,
+ FaceDetectionListener,
+ NewPreviewGestures.SwipeListener {
+
+ private static final String TAG = "CAM_UI";
+ private static final int UPDATE_TRANSFORM_MATRIX = 1;
+ private NewCameraActivity mActivity;
+ private PhotoController mController;
+ private NewPreviewGestures mGestures;
+
+ private View mRootView;
+ private Object mSurfaceTexture;
+
+ private AbstractSettingPopup mPopup;
+ private ShutterButton mShutterButton;
+ private CountDownView mCountDownView;
+
+ private FaceView mFaceView;
+ private RenderOverlay mRenderOverlay;
+ private View mReviewCancelButton;
+ private View mReviewDoneButton;
+ private View mReviewRetakeButton;
+
+ private View mMenuButton;
+ private View mBlocker;
+ private NewPhotoMenu mMenu;
+ private CameraSwitcher mSwitcher;
+ private View mCameraControls;
+
+ // Small indicators which show the camera settings in the viewfinder.
+ private OnScreenIndicators mOnScreenIndicators;
+
+ private PieRenderer mPieRenderer;
+ private ZoomRenderer mZoomRenderer;
+ private Toast mNotSelectableToast;
+
+ private int mZoomMax;
+ private List<Integer> mZoomRatios;
+
+ private int mPreviewWidth = 0;
+ private int mPreviewHeight = 0;
+ private float mSurfaceTextureUncroppedWidth;
+ private float mSurfaceTextureUncroppedHeight;
+
+ private SurfaceTextureSizeChangedListener mSurfaceTextureSizeListener;
+ private TextureView mTextureView;
+ private Matrix mMatrix = null;
+ private float mAspectRatio = 4f / 3f;
+ private final Object mLock = new Object();
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case UPDATE_TRANSFORM_MATRIX:
+ setTransformMatrix(mPreviewWidth, mPreviewHeight);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+
+ public interface SurfaceTextureSizeChangedListener {
+ public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight);
+ }
+
+ private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ int width = right - left;
+ int height = bottom - top;
+ // Full-screen screennail
+ int w = width;
+ int h = height;
+ if (Util.getDisplayRotation(mActivity) % 180 != 0) {
+ w = height;
+ h = width;
+ }
+ if (mPreviewWidth != width || mPreviewHeight != height) {
+ mPreviewWidth = width;
+ mPreviewHeight = height;
+ onScreenSizeChanged(width, height, w, h);
+ mController.onScreenSizeChanged(width, height, w, h);
+ }
+ }
+ };
+
+ public NewPhotoUI(NewCameraActivity activity, PhotoController controller, View parent) {
+ mActivity = activity;
+ mController = controller;
+ mRootView = parent;
+
+ mActivity.getLayoutInflater().inflate(R.layout.new_photo_module,
+ (ViewGroup) mRootView, true);
+ mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
+ // display the view
+ mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);
+ mTextureView.setSurfaceTextureListener(this);
+ mTextureView.addOnLayoutChangeListener(mLayoutListener);
+ initIndicators();
+
+ mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
+ mSwitcher = (CameraSwitcher) mRootView.findViewById(R.id.camera_switcher);
+ mSwitcher.setCurrentIndex(0);
+ mSwitcher.setSwitchListener((CameraSwitchListener) mActivity);
+ mMenuButton = mRootView.findViewById(R.id.menu);
+ if (ApiHelper.HAS_FACE_DETECTION) {
+ ViewStub faceViewStub = (ViewStub) mRootView
+ .findViewById(R.id.face_view_stub);
+ if (faceViewStub != null) {
+ faceViewStub.inflate();
+ mFaceView = (FaceView) mRootView.findViewById(R.id.face_view);
+ setSurfaceTextureSizeChangedListener(
+ (SurfaceTextureSizeChangedListener) mFaceView);
+ }
+ }
+ mCameraControls = mRootView.findViewById(R.id.camera_controls);
+ }
+
+ public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
+ setTransformMatrix(width, height);
+ }
+
+ public void setSurfaceTextureSizeChangedListener(SurfaceTextureSizeChangedListener listener) {
+ mSurfaceTextureSizeListener = listener;
+ }
+
+ public void setPreviewSize(Size size) {
+ int width = size.width;
+ int height = size.height;
+ if (width == 0 || height == 0) {
+ Log.w(TAG, "Preview size should not be 0.");
+ return;
+ }
+ if (width > height) {
+ mAspectRatio = (float) width / height;
+ } else {
+ mAspectRatio = (float) height / width;
+ }
+ mHandler.sendEmptyMessage(UPDATE_TRANSFORM_MATRIX);
+ }
+
+ private void setTransformMatrix(int width, int height) {
+ mMatrix = mTextureView.getTransform(mMatrix);
+ int orientation = Util.getDisplayRotation(mActivity);
+ float scaleX = 1f, scaleY = 1f;
+ float scaledTextureWidth, scaledTextureHeight;
+ if (width > height) {
+ scaledTextureWidth = Math.max(width,
+ (int) (height * mAspectRatio));
+ scaledTextureHeight = Math.max(height,
+ (int)(width / mAspectRatio));
+ } else {
+ scaledTextureWidth = Math.max(width,
+ (int) (height / mAspectRatio));
+ scaledTextureHeight = Math.max(height,
+ (int) (width * mAspectRatio));
+ }
+
+ if (mSurfaceTextureUncroppedWidth != scaledTextureWidth ||
+ mSurfaceTextureUncroppedHeight != scaledTextureHeight) {
+ mSurfaceTextureUncroppedWidth = scaledTextureWidth;
+ mSurfaceTextureUncroppedHeight = scaledTextureHeight;
+ if (mSurfaceTextureSizeListener != null) {
+ mSurfaceTextureSizeListener.onSurfaceTextureSizeChanged(
+ (int) mSurfaceTextureUncroppedWidth, (int) mSurfaceTextureUncroppedHeight);
+ }
+ }
+ scaleX = scaledTextureWidth / width;
+ scaleY = scaledTextureHeight / height;
+ mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2);
+ mTextureView.setTransform(mMatrix);
+ }
+
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ synchronized (mLock) {
+ mSurfaceTexture = surface;
+ mLock.notifyAll();
+ }
+ }
+
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ // Ignored, Camera does all the work for us
+ }
+
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ mSurfaceTexture = null;
+ mController.stopPreview();
+ Log.w(TAG, "surfaceTexture is destroyed");
+ return true;
+ }
+
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ // Invoked every time there's a new Camera preview frame
+ }
+
+ public View getRootView() {
+ return mRootView;
+ }
+
+ private void initIndicators() {
+ mOnScreenIndicators = new OnScreenIndicators(mActivity,
+ mRootView.findViewById(R.id.on_screen_indicators));
+ }
+
+ public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs,
+ Camera.Parameters params, OnPreferenceChangedListener listener) {
+ if (mPieRenderer == null) {
+ mPieRenderer = new PieRenderer(mActivity);
+ mPieRenderer.setPieListener(this);
+ }
+
+ if (mMenu == null) {
+ mMenu = new NewPhotoMenu(mActivity, this, mPieRenderer);
+ mMenu.setListener(listener);
+ }
+ mMenu.initialize(prefGroup);
+
+ if (mZoomRenderer == null) {
+ mZoomRenderer = new ZoomRenderer(mActivity);
+ }
+ mRenderOverlay.addRenderer(mPieRenderer);
+ mRenderOverlay.addRenderer(mZoomRenderer);
+
+ if (mGestures == null) {
+ // this will handle gesture disambiguation and dispatching
+ mGestures = new NewPreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer,
+ this);
+ mGestures.setCancelEventListener(this);
+ }
+ mGestures.clearTouchReceivers();
+ mGestures.setRenderOverlay(mRenderOverlay);
+ mGestures.addTouchReceiver(mMenuButton);
+ mGestures.addTouchReceiver(mBlocker);
+ // make sure to add touch targets for image capture
+ if (mController.isImageCaptureIntent()) {
+ if (mReviewCancelButton != null) {
+ mGestures.addTouchReceiver(mReviewCancelButton);
+ }
+ if (mReviewDoneButton != null) {
+ mGestures.addTouchReceiver(mReviewDoneButton);
+ }
+ }
+ mRenderOverlay.requestLayout();
+
+ initializeZoom(params);
+ updateOnScreenIndicators(params, prefGroup, prefs);
+ }
+
+ private void openMenu() {
+ if (mPieRenderer != null) {
+ // If autofocus is not finished, cancel autofocus so that the
+ // subsequent touch can be handled by PreviewGestures
+ if (mController.getCameraState() == PhotoController.FOCUSING) {
+ mController.cancelAutoFocus();
+ }
+ mPieRenderer.showInCenter();
+ }
+ }
+
+ public void initializeControlByIntent() {
+ mBlocker = mRootView.findViewById(R.id.blocker);
+ mMenuButton = mRootView.findViewById(R.id.menu);
+ mMenuButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ openMenu();
+ }
+ });
+ if (mController.isImageCaptureIntent()) {
+ hideSwitcher();
+ ViewGroup cameraControls = (ViewGroup) mRootView.findViewById(R.id.camera_controls);
+ mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls);
+
+ mReviewDoneButton = mRootView.findViewById(R.id.btn_done);
+ mReviewCancelButton = mRootView.findViewById(R.id.btn_cancel);
+ mReviewRetakeButton = mRootView.findViewById(R.id.btn_retake);
+ mReviewCancelButton.setVisibility(View.VISIBLE);
+
+ mReviewDoneButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mController.onCaptureDone();
+ }
+ });
+ mReviewCancelButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mController.onCaptureCancelled();
+ }
+ });
+
+ mReviewRetakeButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mController.onCaptureRetake();
+ }
+ });
+ }
+ }
+
+ public void hideUI() {
+ mCameraControls.setVisibility(View.INVISIBLE);
+ hideSwitcher();
+ mShutterButton.setVisibility(View.GONE);
+ }
+
+ public void showUI() {
+ mCameraControls.setVisibility(View.VISIBLE);
+ showSwitcher();
+ mShutterButton.setVisibility(View.VISIBLE);
+ }
+
+ public void hideSwitcher() {
+ mSwitcher.closePopup();
+ mSwitcher.setVisibility(View.INVISIBLE);
+ }
+
+ public void showSwitcher() {
+ mSwitcher.setVisibility(View.VISIBLE);
+ }
+
+ // called from onResume but only the first time
+ public void initializeFirstTime() {
+ // Initialize shutter button.
+ mShutterButton.setImageResource(R.drawable.btn_new_shutter);
+ mShutterButton.setOnShutterButtonListener(mController);
+ mShutterButton.setVisibility(View.VISIBLE);
+ }
+
+ // called from onResume every other time
+ public void initializeSecondTime(Camera.Parameters params) {
+ initializeZoom(params);
+ if (mController.isImageCaptureIntent()) {
+ hidePostCaptureAlert();
+ }
+ if (mMenu != null) {
+ mMenu.reloadPreferences();
+ }
+ }
+
+ public void initializeZoom(Camera.Parameters params) {
+ if ((params == null) || !params.isZoomSupported()
+ || (mZoomRenderer == null)) return;
+ mZoomMax = params.getMaxZoom();
+ mZoomRatios = params.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.
+ if (mZoomRenderer != null) {
+ mZoomRenderer.setZoomMax(mZoomMax);
+ mZoomRenderer.setZoom(params.getZoom());
+ mZoomRenderer.setZoomValue(mZoomRatios.get(params.getZoom()));
+ mZoomRenderer.setOnZoomChangeListener(new ZoomChangeListener());
+ }
+ }
+
+ public void showGpsOnScreenIndicator(boolean hasSignal) { }
+
+ public void hideGpsOnScreenIndicator() { }
+
+ public void overrideSettings(final String ... keyvalues) {
+ mMenu.overrideSettings(keyvalues);
+ }
+
+ public void updateOnScreenIndicators(Camera.Parameters params,
+ PreferenceGroup group, ComboPreferences prefs) {
+ if (params == null) return;
+ mOnScreenIndicators.updateSceneOnScreenIndicator(params.getSceneMode());
+ mOnScreenIndicators.updateExposureOnScreenIndicator(params,
+ CameraSettings.readExposure(prefs));
+ mOnScreenIndicators.updateFlashOnScreenIndicator(params.getFlashMode());
+ int wbIndex = 2;
+ ListPreference pref = group.findPreference(CameraSettings.KEY_WHITE_BALANCE);
+ if (pref != null) {
+ wbIndex = pref.getCurrentIndex();
+ }
+ mOnScreenIndicators.updateWBIndicator(wbIndex);
+ boolean location = RecordLocationPreference.get(
+ prefs, mActivity.getContentResolver());
+ mOnScreenIndicators.updateLocationIndicator(location);
+ }
+
+ public void setCameraState(int state) {
+ }
+
+ // Gestures and touch events
+
+ public boolean dispatchTouchEvent(MotionEvent m) {
+ if (mPopup != null || mSwitcher.showsPopup()) {
+ boolean handled = mRootView.dispatchTouchEvent(m);
+ if (!handled && mPopup != null) {
+ dismissPopup();
+ }
+ return handled;
+ } else if (mGestures != null && mRenderOverlay != null) {
+ if (mGestures.dispatchTouch(m)) {
+ return true;
+ } else {
+ return mRootView.dispatchTouchEvent(m);
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public void onTouchEventCancelled(MotionEvent cancelEvent) {
+ mRootView.dispatchTouchEvent(cancelEvent);
+ }
+
+ public void enableGestures(boolean enable) {
+ if (mGestures != null) {
+ mGestures.setEnabled(enable);
+ }
+ }
+
+ // forward from preview gestures to controller
+ @Override
+ public void onSingleTapUp(View view, int x, int y) {
+ mController.onSingleTapUp(view, x, y);
+ }
+
+ public boolean onBackPressed() {
+ if (mPieRenderer != null && mPieRenderer.showsItems()) {
+ mPieRenderer.hide();
+ return true;
+ }
+ // In image capture mode, back button should:
+ // 1) if there is any popup, dismiss them, 2) otherwise, get out of
+ // image capture
+ if (mController.isImageCaptureIntent()) {
+ if (!removeTopLevelPopup()) {
+ // no popup to dismiss, cancel image capture
+ mController.onCaptureCancelled();
+ }
+ return true;
+ } else if (!mController.isCameraIdle()) {
+ // ignore backs while we're taking a picture
+ return true;
+ } else {
+ return removeTopLevelPopup();
+ }
+ }
+
+ public void onFullScreenChanged(boolean full) {
+ if (mFaceView != null) {
+ mFaceView.setBlockDraw(!full);
+ }
+ if (mPopup != null) {
+ dismissPopup(full);
+ }
+ if (mGestures != null) {
+ mGestures.setEnabled(full);
+ }
+ if (mRenderOverlay != null) {
+ // this can not happen in capture mode
+ mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE);
+ }
+ if (mPieRenderer != null) {
+ mPieRenderer.setBlockFocus(!full);
+ }
+ setShowMenu(full);
+ if (mBlocker != null) {
+ mBlocker.setVisibility(full ? View.VISIBLE : View.GONE);
+ }
+ if (!full && mCountDownView != null) mCountDownView.cancelCountDown();
+ }
+
+ public boolean removeTopLevelPopup() {
+ // Remove the top level popup or dialog box and return true if there's any
+ if (mPopup != null) {
+ dismissPopup();
+ return true;
+ }
+ return false;
+ }
+
+ public void showPopup(AbstractSettingPopup popup) {
+ 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() {
+ dismissPopup(true);
+ }
+
+ private void dismissPopup(boolean fullScreen) {
+ if (fullScreen) {
+ showUI();
+ mBlocker.setVisibility(View.VISIBLE);
+ }
+ setShowMenu(fullScreen);
+ if (mPopup != null) {
+ ((FrameLayout) mRootView).removeView(mPopup);
+ mPopup = null;
+ }
+ mMenu.popupDismissed();
+ }
+
+ public void onShowSwitcherPopup() {
+ if (mPieRenderer != null && mPieRenderer.showsItems()) {
+ mPieRenderer.hide();
+ }
+ }
+
+ private void setShowMenu(boolean show) {
+ if (mOnScreenIndicators != null) {
+ mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+ if (mMenuButton != null) {
+ mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE);
+ }
+ }
+
+ public boolean collapseCameraControls() {
+ // Remove all the popups/dialog boxes
+ boolean ret = false;
+ if (mPopup != null) {
+ dismissPopup();
+ ret = true;
+ }
+ return ret;
+ }
+
+ protected void showPostCaptureAlert() {
+ mOnScreenIndicators.setVisibility(View.GONE);
+ mMenuButton.setVisibility(View.GONE);
+ Util.fadeIn(mReviewDoneButton);
+ mShutterButton.setVisibility(View.INVISIBLE);
+ Util.fadeIn(mReviewRetakeButton);
+ }
+
+ protected void hidePostCaptureAlert() {
+ mOnScreenIndicators.setVisibility(View.VISIBLE);
+ mMenuButton.setVisibility(View.VISIBLE);
+ Util.fadeOut(mReviewDoneButton);
+ mShutterButton.setVisibility(View.VISIBLE);
+ Util.fadeOut(mReviewRetakeButton);
+ }
+
+ public void setDisplayOrientation(int orientation) {
+ if (mFaceView != null) {
+ mFaceView.setDisplayOrientation(orientation);
+ }
+ }
+
+ // shutter button handling
+
+ public boolean isShutterPressed() {
+ return mShutterButton.isPressed();
+ }
+
+ public void enableShutter(boolean enabled) {
+ if (mShutterButton != null) {
+ mShutterButton.setEnabled(enabled);
+ }
+ }
+
+ public void pressShutterButton() {
+ if (mShutterButton.isInTouchMode()) {
+ mShutterButton.requestFocusFromTouch();
+ } else {
+ mShutterButton.requestFocus();
+ }
+ mShutterButton.setPressed(true);
+ }
+
+ 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() {
+ if (mPieRenderer != null) {
+ mPieRenderer.setBlockFocus(true);
+ }
+ }
+
+ @Override
+ public void onZoomEnd() {
+ if (mPieRenderer != null) {
+ mPieRenderer.setBlockFocus(false);
+ }
+ }
+ }
+
+ @Override
+ public void onPieOpened(int centerX, int centerY) {
+ //TODO: mActivity.cancelActivityTouchHandling();
+ //TODO: mActivity.setSwipingEnabled(false);
+ if (mFaceView != null) {
+ mFaceView.setBlockDraw(true);
+ }
+ }
+
+ @Override
+ public void onPieClosed() {
+ //TODO: mActivity.setSwipingEnabled(true);
+ if (mFaceView != null) {
+ mFaceView.setBlockDraw(false);
+ }
+ }
+
+ public Object getSurfaceTexture() {
+ synchronized (mLock) {
+ if (mSurfaceTexture == null) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Unexpected interruption when waiting to get surface texture");
+ }
+ }
+ }
+ return mSurfaceTexture;
+ }
+
+ // Countdown timer
+
+ private void initializeCountDown() {
+ mActivity.getLayoutInflater().inflate(R.layout.count_down_to_capture,
+ (ViewGroup) mRootView, true);
+ mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture));
+ mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController);
+ }
+
+ public boolean isCountingDown() {
+ return mCountDownView != null && mCountDownView.isCountingDown();
+ }
+
+ public void cancelCountDown() {
+ if (mCountDownView == null) return;
+ mCountDownView.cancelCountDown();
+ }
+
+ public void startCountDown(int sec, boolean playSound) {
+ if (mCountDownView == null) initializeCountDown();
+ mCountDownView.startCountDown(sec, playSound);
+ }
+
+ public void showPreferencesToast() {
+ if (mNotSelectableToast == null) {
+ String str = mActivity.getResources().getString(R.string.not_selectable_in_scene_mode);
+ mNotSelectableToast = Toast.makeText(mActivity, str, Toast.LENGTH_SHORT);
+ }
+ mNotSelectableToast.show();
+ }
+
+ public void onPause() {
+ cancelCountDown();
+
+ // Clear UI.
+ collapseCameraControls();
+ if (mFaceView != null) mFaceView.clear();
+
+ mPreviewWidth = 0;
+ mPreviewHeight = 0;
+ }
+
+ // focus UI implementation
+
+ private FocusIndicator getFocusIndicator() {
+ return (mFaceView != null && mFaceView.faceExists()) ? mFaceView : mPieRenderer;
+ }
+
+ @Override
+ public boolean hasFaces() {
+ return (mFaceView != null && mFaceView.faceExists());
+ }
+
+ public void clearFaces() {
+ if (mFaceView != null) mFaceView.clear();
+ }
+
+ @Override
+ public void clearFocus() {
+ FocusIndicator indicator = getFocusIndicator();
+ if (indicator != null) indicator.clear();
+ }
+
+ @Override
+ public void setFocusPosition(int x, int y) {
+ mPieRenderer.setFocus(x, y);
+ }
+
+ @Override
+ public void onFocusStarted() {
+ getFocusIndicator().showStart();
+ }
+
+ @Override
+ public void onFocusSucceeded(boolean timeout) {
+ getFocusIndicator().showSuccess(timeout);
+ }
+
+ @Override
+ public void onFocusFailed(boolean timeout) {
+ getFocusIndicator().showFail(timeout);
+ }
+
+ @Override
+ public void pauseFaceDetection() {
+ if (mFaceView != null) mFaceView.pause();
+ }
+
+ @Override
+ public void resumeFaceDetection() {
+ if (mFaceView != null) mFaceView.resume();
+ }
+
+ public void onStartFaceDetection(int orientation, boolean mirror) {
+ mFaceView.clear();
+ mFaceView.setVisibility(View.VISIBLE);
+ mFaceView.setDisplayOrientation(orientation);
+ mFaceView.setMirror(mirror);
+ mFaceView.resume();
+ }
+
+ @Override
+ public void onFaceDetection(Face[] faces, android.hardware.Camera camera) {
+ mFaceView.setFaces(faces);
+ }
+
+ @Override
+ public void onSwipe(int direction) {
+ if (direction == PreviewGestures.DIR_UP) {
+ openMenu();
+ }
+ }
+}
diff --git a/src/com/android/camera/NewPreviewGestures.java b/src/com/android/camera/NewPreviewGestures.java
new file mode 100644
index 000000000..2718e55ae
--- /dev/null
+++ b/src/com/android/camera/NewPreviewGestures.java
@@ -0,0 +1,358 @@
+package com.android.camera;
+
+/*
+ * 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.
+ */
+
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+import com.android.camera.PreviewGestures.SwipeListener;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.gallery3d.R;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class NewPreviewGestures
+ implements ScaleGestureDetector.OnScaleGestureListener {
+
+ private static final String TAG = "CAM_gestures";
+
+ private static final long TIMEOUT_PIE = 200;
+ private static final int MSG_PIE = 1;
+ private static final int MODE_NONE = 0;
+ private static final int MODE_PIE = 1;
+ private static final int MODE_ZOOM = 2;
+ private static final int MODE_MODULE = 3;
+ private static final int MODE_ALL = 4;
+ private static final int MODE_SWIPE = 5;
+
+ public static final int DIR_UP = 0;
+ public static final int DIR_DOWN = 1;
+ public static final int DIR_LEFT = 2;
+ public static final int DIR_RIGHT = 3;
+
+ private NewCameraActivity mActivity;
+ private SingleTapListener mTapListener;
+ private CancelEventListener mCancelEventListener;
+ private RenderOverlay mOverlay;
+ private PieRenderer mPie;
+ private ZoomRenderer mZoom;
+ private MotionEvent mDown;
+ private MotionEvent mCurrent;
+ private ScaleGestureDetector mScale;
+ private List<View> mReceivers;
+ private int mMode;
+ private int mSlop;
+ private int mTapTimeout;
+ private boolean mEnabled;
+ private boolean mZoomOnly;
+ private int mOrientation;
+ private int[] mLocation;
+ private SwipeListener mSwipeListener;
+
+ private Handler mHandler = new Handler() {
+ public void handleMessage(Message msg) {
+ if (msg.what == MSG_PIE) {
+ mMode = MODE_PIE;
+ openPie();
+ cancelActivityTouchHandling(mDown);
+ }
+ }
+ };
+
+ public interface SingleTapListener {
+ public void onSingleTapUp(View v, int x, int y);
+ }
+
+ public interface CancelEventListener {
+ public void onTouchEventCancelled(MotionEvent cancelEvent);
+ }
+
+ interface SwipeListener {
+ public void onSwipe(int direction);
+ }
+
+ public NewPreviewGestures(NewCameraActivity ctx, SingleTapListener tapListener,
+ ZoomRenderer zoom, PieRenderer pie, SwipeListener swipe) {
+ mActivity = ctx;
+ mTapListener = tapListener;
+ mPie = pie;
+ mZoom = zoom;
+ mMode = MODE_ALL;
+ mScale = new ScaleGestureDetector(ctx, this);
+ mSlop = (int) ctx.getResources().getDimension(R.dimen.pie_touch_slop);
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+ mEnabled = true;
+ mLocation = new int[2];
+ mSwipeListener = swipe;
+ }
+
+ public void setCancelEventListener(CancelEventListener listener) {
+ mCancelEventListener = listener;
+ }
+
+ public void setRenderOverlay(RenderOverlay overlay) {
+ mOverlay = overlay;
+ }
+
+ public void setOrientation(int orientation) {
+ mOrientation = orientation;
+ }
+
+ public void setEnabled(boolean enabled) {
+ mEnabled = enabled;
+ if (!enabled) {
+ cancelPie();
+ }
+ }
+
+ public void setZoomOnly(boolean zoom) {
+ mZoomOnly = zoom;
+ }
+
+ public void addTouchReceiver(View v) {
+ if (mReceivers == null) {
+ mReceivers = new ArrayList<View>();
+ }
+ mReceivers.add(v);
+ }
+
+ public void clearTouchReceivers() {
+ if (mReceivers != null) {
+ mReceivers.clear();
+ }
+ }
+
+ public boolean dispatchTouch(MotionEvent m) {
+ if (!mEnabled) {
+ return false;
+ }
+ mCurrent = m;
+ if (MotionEvent.ACTION_DOWN == m.getActionMasked()) {
+ if (checkReceivers(m)) {
+ mMode = MODE_MODULE;
+ return false;
+ } else {
+ mMode = MODE_ALL;
+ mDown = MotionEvent.obtain(m);
+ if (mPie != null && mPie.showsItems()) {
+ mMode = MODE_PIE;
+ return sendToPie(m);
+ }
+ if (mPie != null && !mZoomOnly) {
+ mHandler.sendEmptyMessageDelayed(MSG_PIE, TIMEOUT_PIE);
+ }
+ if (mZoom != null) {
+ mScale.onTouchEvent(m);
+ }
+ // make sure this is ok
+ return false;
+ }
+ } else if (mMode == MODE_NONE) {
+ return false;
+ } else if (mMode == MODE_SWIPE) {
+ if (MotionEvent.ACTION_UP == m.getActionMasked()) {
+ mSwipeListener.onSwipe(getSwipeDirection(m));
+ }
+ return true;
+ } else if (mMode == MODE_PIE) {
+ if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
+ sendToPie(makeCancelEvent(m));
+ if (mZoom != null) {
+ onScaleBegin(mScale);
+ }
+ } else {
+ return sendToPie(m);
+ }
+ return true;
+ } else if (mMode == MODE_ZOOM) {
+ mScale.onTouchEvent(m);
+ if (!mScale.isInProgress() && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
+ mMode = MODE_NONE;
+ onScaleEnd(mScale);
+ }
+ return true;
+ } else if (mMode == MODE_MODULE) {
+ return false;
+ } else {
+ // didn't receive down event previously;
+ // assume module wasn't initialzed and ignore this event.
+ if (mDown == null) {
+ return true;
+ }
+ if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) {
+ if (!mZoomOnly) {
+ cancelPie();
+ sendToPie(makeCancelEvent(m));
+ }
+ if (mZoom != null) {
+ mScale.onTouchEvent(m);
+ onScaleBegin(mScale);
+ }
+ } else if ((mMode == MODE_ZOOM) && !mScale.isInProgress()
+ && MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) {
+ // user initiated and stopped zoom gesture without zooming
+ mScale.onTouchEvent(m);
+ onScaleEnd(mScale);
+ }
+ // not zoom or pie mode and no timeout yet
+ if (mZoom != null) {
+ boolean res = mScale.onTouchEvent(m);
+ if (mScale.isInProgress()) {
+ cancelPie();
+ cancelActivityTouchHandling(m);
+ return res;
+ }
+ }
+ if (MotionEvent.ACTION_UP == m.getActionMasked()) {
+ cancelPie();
+ cancelActivityTouchHandling(m);
+ // must have been tap
+ if (m.getEventTime() - mDown.getEventTime() < mTapTimeout) {
+ mTapListener.onSingleTapUp(null,
+ (int) mDown.getX() - mOverlay.getWindowPositionX(),
+ (int) mDown.getY() - mOverlay.getWindowPositionY());
+ return true;
+ } else {
+ return false;
+ }
+ } else if (MotionEvent.ACTION_MOVE == m.getActionMasked()) {
+ if ((Math.abs(m.getX() - mDown.getX()) > mSlop)
+ || Math.abs(m.getY() - mDown.getY()) > mSlop) {
+ // moved too far and no timeout yet, no focus or pie
+ cancelPie();
+ int dir = getSwipeDirection(m);
+ if (dir == DIR_LEFT) {
+ mMode = MODE_MODULE;
+ return false;
+ } else {
+ cancelActivityTouchHandling(m);
+ mMode = MODE_NONE;
+ }
+ }
+ }
+ return false;
+ }
+ }
+
+ private boolean checkReceivers(MotionEvent m) {
+ if (mReceivers != null) {
+ for (View receiver : mReceivers) {
+ if (isInside(m, receiver)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // left tests for finger moving right to left
+ private int getSwipeDirection(MotionEvent m) {
+ float dx = 0;
+ float dy = 0;
+ switch (mOrientation) {
+ case 0:
+ dx = m.getX() - mDown.getX();
+ dy = m.getY() - mDown.getY();
+ break;
+ case 90:
+ dx = - (m.getY() - mDown.getY());
+ dy = m.getX() - mDown.getX();
+ break;
+ case 180:
+ dx = -(m.getX() - mDown.getX());
+ dy = m.getY() - mDown.getY();
+ break;
+ case 270:
+ dx = m.getY() - mDown.getY();
+ dy = m.getX() - mDown.getX();
+ break;
+ }
+ if (dx < 0 && (Math.abs(dy) / -dx < 2)) return DIR_LEFT;
+ if (dx > 0 && (Math.abs(dy) / dx < 2)) return DIR_RIGHT;
+ if (dy > 0) return DIR_DOWN;
+ return DIR_UP;
+ }
+
+ private boolean isInside(MotionEvent evt, View v) {
+ v.getLocationInWindow(mLocation);
+ return (v.getVisibility() == View.VISIBLE
+ && evt.getX() >= mLocation[0] && evt.getX() < mLocation[0] + v.getWidth()
+ && evt.getY() >= mLocation[1] && evt.getY() < mLocation[1] + v.getHeight());
+ }
+
+ public void cancelActivityTouchHandling(MotionEvent m) {
+ if (mCancelEventListener != null) {
+ mCancelEventListener.onTouchEventCancelled(makeCancelEvent(m));
+ }
+ }
+
+ private MotionEvent makeCancelEvent(MotionEvent m) {
+ MotionEvent c = MotionEvent.obtain(m);
+ c.setAction(MotionEvent.ACTION_CANCEL);
+ return c;
+ }
+
+ private void openPie() {
+ mDown.offsetLocation(-mOverlay.getWindowPositionX(),
+ -mOverlay.getWindowPositionY());
+ mOverlay.directDispatchTouch(mDown, mPie);
+ }
+
+ private void cancelPie() {
+ mHandler.removeMessages(MSG_PIE);
+ }
+
+ private boolean sendToPie(MotionEvent m) {
+ m.offsetLocation(-mOverlay.getWindowPositionX(),
+ -mOverlay.getWindowPositionY());
+ return mOverlay.directDispatchTouch(m, mPie);
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ return mZoom.onScale(detector);
+ }
+
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ if (mMode != MODE_ZOOM) {
+ mMode = MODE_ZOOM;
+ cancelActivityTouchHandling(mCurrent);
+ }
+ if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
+ return mZoom.onScaleBegin(detector);
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) {
+ mZoom.onScaleEnd(detector);
+ }
+ }
+}
+
diff --git a/src/com/android/camera/NewVideoMenu.java b/src/com/android/camera/NewVideoMenu.java
new file mode 100644
index 000000000..1038628b5
--- /dev/null
+++ b/src/com/android/camera/NewVideoMenu.java
@@ -0,0 +1,209 @@
+/*
+ * 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.app.Activity;
+import android.content.Context;
+import android.view.LayoutInflater;
+
+import com.android.camera.ui.AbstractSettingPopup;
+import com.android.camera.ui.ListPrefSettingPopup;
+import com.android.camera.ui.MoreSettingPopup;
+import com.android.camera.ui.PieItem;
+import com.android.camera.ui.PieItem.OnClickListener;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.TimeIntervalPopup;
+import com.android.gallery3d.R;
+
+public class NewVideoMenu extends PieController
+ implements MoreSettingPopup.Listener,
+ ListPrefSettingPopup.Listener,
+ TimeIntervalPopup.Listener {
+
+ private static String TAG = "CAM_VideoMenu";
+ private static final int POS_WB = 1;
+ private static final int POS_SET = 2;
+ private static final int POS_FLASH = 3;
+ private static final int POS_SWITCH = 4;
+
+ private NewVideoUI mUI;
+ private String[] mOtherKeys;
+ private AbstractSettingPopup mPopup;
+
+ private static final int POPUP_NONE = 0;
+ private static final int POPUP_FIRST_LEVEL = 1;
+ private static final int POPUP_SECOND_LEVEL = 2;
+ private int mPopupStatus;
+ private NewCameraActivity mActivity;
+
+ public NewVideoMenu(NewCameraActivity activity, NewVideoUI ui, PieRenderer pie) {
+ super(activity, pie);
+ mUI = ui;
+ mActivity = activity;
+ }
+
+
+ public void initialize(PreferenceGroup group) {
+ super.initialize(group);
+ mPopup = null;
+ mPopupStatus = POPUP_NONE;
+ PieItem item = null;
+ // white balance
+ if (group.findPreference(CameraSettings.KEY_WHITE_BALANCE) != null) {
+ item = makeItem(CameraSettings.KEY_WHITE_BALANCE);
+ mRenderer.addItem(item);
+ }
+ // settings popup
+ mOtherKeys = new String[] {
+ CameraSettings.KEY_VIDEO_EFFECT,
+ CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
+ CameraSettings.KEY_VIDEO_QUALITY,
+ CameraSettings.KEY_RECORD_LOCATION
+ };
+ item = makeItem(R.drawable.ic_settings_holo_light);
+ item.setLabel(mActivity.getResources().getString(R.string.camera_menu_settings_label));
+ item.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(PieItem item) {
+ if (mPopup == null || mPopupStatus != POPUP_FIRST_LEVEL) {
+ initializePopup();
+ mPopupStatus = POPUP_FIRST_LEVEL;
+ }
+ mUI.showPopup(mPopup);
+ }
+ });
+ mRenderer.addItem(item);
+ // camera switcher
+ if (group.findPreference(CameraSettings.KEY_CAMERA_ID) != null) {
+ item = makeItem(R.drawable.ic_switch_back);
+ IconListPreference lpref = (IconListPreference) group.findPreference(
+ CameraSettings.KEY_CAMERA_ID);
+ item.setLabel(lpref.getLabel());
+ item.setImageResource(mActivity,
+ ((IconListPreference) lpref).getIconIds()
+ [lpref.findIndexOfValue(lpref.getValue())]);
+
+ final PieItem fitem = item;
+ item.setOnClickListener(new OnClickListener() {
+
+ @Override
+ public void onClick(PieItem item) {
+ // Find the index of next camera.
+ ListPreference pref =
+ mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+ if (pref != null) {
+ int index = pref.findIndexOfValue(pref.getValue());
+ CharSequence[] values = pref.getEntryValues();
+ index = (index + 1) % values.length;
+ int newCameraId = Integer.parseInt((String) values[index]);
+ fitem.setImageResource(mActivity,
+ ((IconListPreference) pref).getIconIds()[index]);
+ fitem.setLabel(pref.getLabel());
+ mListener.onCameraPickerClicked(newCameraId);
+ }
+ }
+ });
+ mRenderer.addItem(item);
+ }
+ // flash
+ if (group.findPreference(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE) != null) {
+ item = makeItem(CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE);
+ mRenderer.addItem(item);
+ }
+ }
+
+ @Override
+ public void reloadPreferences() {
+ super.reloadPreferences();
+ if (mPopup != null) {
+ mPopup.reloadPreference();
+ }
+ }
+
+ @Override
+ public void overrideSettings(final String ... keyvalues) {
+ super.overrideSettings(keyvalues);
+ if (mPopup == null || mPopupStatus != POPUP_FIRST_LEVEL) {
+ mPopupStatus = POPUP_FIRST_LEVEL;
+ initializePopup();
+ }
+ ((MoreSettingPopup) mPopup).overrideSettings(keyvalues);
+ }
+
+ @Override
+ // Hit when an item in the second-level popup gets selected
+ public void onListPrefChanged(ListPreference pref) {
+ if (mPopup != null) {
+ if (mPopupStatus == POPUP_SECOND_LEVEL) {
+ mUI.dismissPopup(true);
+ }
+ }
+ super.onSettingChanged(pref);
+ }
+
+ protected void initializePopup() {
+ LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ MoreSettingPopup popup = (MoreSettingPopup) inflater.inflate(
+ R.layout.more_setting_popup, null, false);
+ popup.setSettingChangedListener(this);
+ popup.initialize(mPreferenceGroup, mOtherKeys);
+ if (mActivity.isSecureCamera()) {
+ // Prevent location preference from getting changed in secure camera mode
+ popup.setPreferenceEnabled(CameraSettings.KEY_RECORD_LOCATION, false);
+ }
+ mPopup = popup;
+ }
+
+ public void popupDismissed(boolean topPopupOnly) {
+ // if the 2nd level popup gets dismissed
+ if (mPopupStatus == POPUP_SECOND_LEVEL) {
+ initializePopup();
+ mPopupStatus = POPUP_FIRST_LEVEL;
+ if (topPopupOnly) mUI.showPopup(mPopup);
+ }
+ }
+
+ @Override
+ // Hit when an item in the first-level popup gets selected, then bring up
+ // the second-level popup
+ public void onPreferenceClicked(ListPreference pref) {
+ if (mPopupStatus != POPUP_FIRST_LEVEL) return;
+
+ LayoutInflater inflater = (LayoutInflater) mActivity.getSystemService(
+ Context.LAYOUT_INFLATER_SERVICE);
+
+ if (CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL.equals(pref.getKey())) {
+ TimeIntervalPopup timeInterval = (TimeIntervalPopup) inflater.inflate(
+ R.layout.time_interval_popup, null, false);
+ timeInterval.initialize((IconListPreference) pref);
+ timeInterval.setSettingChangedListener(this);
+ 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);
+ mUI.dismissPopup(true);
+ mPopup = basic;
+ }
+ mUI.showPopup(mPopup);
+ mPopupStatus = POPUP_SECOND_LEVEL;
+ }
+}
diff --git a/src/com/android/camera/NewVideoModule.java b/src/com/android/camera/NewVideoModule.java
new file mode 100644
index 000000000..54e3373e6
--- /dev/null
+++ b/src/com/android/camera/NewVideoModule.java
@@ -0,0 +1,2352 @@
+/*
+ * 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.annotation.TargetApi;
+import android.app.Activity;
+import android.content.ActivityNotFoundException;
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.SharedPreferences.Editor;
+import android.content.res.Configuration;
+import android.graphics.Bitmap;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.CameraInfo;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.PictureCallback;
+import android.hardware.Camera.Size;
+import android.location.Location;
+import android.media.CamcorderProfile;
+import android.media.CameraProfile;
+import android.media.MediaRecorder;
+import android.net.Uri;
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ParcelFileDescriptor;
+import android.os.SystemClock;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Video;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MotionEvent;
+import android.view.OrientationEventListener;
+import android.view.Surface;
+import android.view.View;
+import android.view.WindowManager;
+import android.widget.Toast;
+
+import com.android.camera.CameraManager.CameraProxy;
+import com.android.camera.ui.PopupManager;
+import com.android.camera.ui.RotateTextToast;
+import com.android.gallery3d.R;
+import com.android.gallery3d.app.OrientationManager;
+import com.android.gallery3d.common.ApiHelper;
+import com.android.gallery3d.exif.ExifInterface;
+import com.android.gallery3d.util.AccessibilityUtils;
+import com.android.gallery3d.util.UsageStatistics;
+
+import java.io.File;
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+
+public class NewVideoModule implements NewCameraModule,
+ VideoController,
+ CameraPreference.OnPreferenceChangedListener,
+ ShutterButton.OnShutterButtonListener,
+ MediaRecorder.OnErrorListener,
+ MediaRecorder.OnInfoListener,
+ EffectsRecorder.EffectsListener {
+
+ private static final String TAG = "CAM_VideoModule";
+
+ // We number the request code from 1000 to avoid collision with Gallery.
+ private static final int REQUEST_EFFECT_BACKDROPPER = 1000;
+
+ private static final int CHECK_DISPLAY_ROTATION = 3;
+ private static final int CLEAR_SCREEN_DELAY = 4;
+ private static final int UPDATE_RECORD_TIME = 5;
+ private static final int ENABLE_SHUTTER_BUTTON = 6;
+ private static final int SHOW_TAP_TO_SNAPSHOT_TOAST = 7;
+ private static final int SWITCH_CAMERA = 8;
+ private static final int SWITCH_CAMERA_START_ANIMATION = 9;
+ private static final int HIDE_SURFACE_VIEW = 10;
+
+ private static final int SCREEN_DELAY = 2 * 60 * 1000;
+
+ private static final long SHUTTER_BUTTON_TIMEOUT = 500L; // 500ms
+
+ /**
+ * An unpublished intent flag requesting to start recording straight away
+ * and return as soon as recording is stopped.
+ * TODO: consider publishing by moving into MediaStore.
+ */
+ private static final String EXTRA_QUICK_CAPTURE =
+ "android.intent.extra.quickCapture";
+
+ private static final int MIN_THUMB_SIZE = 64;
+ // module fields
+ private NewCameraActivity mActivity;
+ private boolean mPaused;
+ private int mCameraId;
+ private Parameters mParameters;
+
+ private Boolean mCameraOpened = false;
+ private boolean mIsInReviewMode;
+ private boolean mSnapshotInProgress = false;
+
+ private static final String EFFECT_BG_FROM_GALLERY = "gallery";
+
+ private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
+
+ private ComboPreferences mPreferences;
+ private PreferenceGroup mPreferenceGroup;
+
+ private boolean mIsVideoCaptureIntent;
+ private boolean mQuickCapture;
+
+ private MediaRecorder mMediaRecorder;
+ private EffectsRecorder mEffectsRecorder;
+ private boolean mEffectsDisplayResult;
+
+ private int mEffectType = EffectsRecorder.EFFECT_NONE;
+ private Object mEffectParameter = null;
+ private String mEffectUriFromGallery = null;
+ private String mPrefVideoEffectDefault;
+ private boolean mResetEffect = true;
+
+ private boolean mSwitchingCamera;
+ private boolean mMediaRecorderRecording = false;
+ private long mRecordingStartTime;
+ private boolean mRecordingTimeCountsDown = false;
+ private long mOnResumeTime;
+ // The video file that the hardware camera is about to record into
+ // (or is recording into.)
+ private String mVideoFilename;
+ private ParcelFileDescriptor mVideoFileDescriptor;
+
+ // The video file that has already been recorded, and that is being
+ // examined by the user.
+ private String mCurrentVideoFilename;
+ private Uri mCurrentVideoUri;
+ private ContentValues mCurrentVideoValues;
+
+ private CamcorderProfile mProfile;
+
+ // The video duration limit. 0 menas no limit.
+ private int mMaxVideoDurationInMs;
+
+ // Time Lapse parameters.
+ private boolean mCaptureTimeLapse = false;
+ // Default 0. If it is larger than 0, the camcorder is in time lapse mode.
+ private int mTimeBetweenTimeLapseFrameCaptureMs = 0;
+
+ boolean mPreviewing = false; // True if preview is started.
+ // The display rotation in degrees. This is only valid when mPreviewing is
+ // true.
+ private int mDisplayRotation;
+ private int mCameraDisplayOrientation;
+
+ private int mDesiredPreviewWidth;
+ private int mDesiredPreviewHeight;
+ private ContentResolver mContentResolver;
+
+ private LocationManager mLocationManager;
+ private OrientationManager mOrientationManager;
+
+ private VideoNamer mVideoNamer;
+ private Surface mSurface;
+ private int mPendingSwitchCameraId;
+ private boolean mOpenCameraFail;
+ private boolean mCameraDisabled;
+ private final Handler mHandler = new MainHandler();
+ private NewVideoUI mUI;
+ private CameraProxy mCameraDevice;
+
+ // 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 boolean mRestoreFlash; // This is used to check if we need to restore the flash
+ // status when going back from gallery.
+
+ private MediaSaveService.OnMediaSavedListener mOnMediaSavedListener =
+ new MediaSaveService.OnMediaSavedListener() {
+ @Override
+ public void onMediaSaved(Uri uri) {
+ if (uri != null) {
+ Util.broadcastNewPicture(mActivity, uri);
+ }
+ }
+ };
+
+
+ protected class CameraOpenThread extends Thread {
+ @Override
+ public void run() {
+ openCamera();
+ }
+ }
+
+ private void openCamera() {
+ try {
+ synchronized(mCameraOpened) {
+ if (!mCameraOpened) {
+ mCameraDevice = Util.openCamera(mActivity, mCameraId);
+ mCameraOpened = true;
+ }
+ }
+ mParameters = mCameraDevice.getParameters();
+ } catch (CameraHardwareException e) {
+ mOpenCameraFail = true;
+ } catch (CameraDisabledException e) {
+ mCameraDisabled = true;
+ }
+ }
+
+ // This Handler is used to post message back onto the main thread of the
+ // application
+ private class MainHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+
+ case ENABLE_SHUTTER_BUTTON:
+ mUI.enableShutter(true);
+ break;
+
+ case CLEAR_SCREEN_DELAY: {
+ mActivity.getWindow().clearFlags(
+ WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ break;
+ }
+
+ case UPDATE_RECORD_TIME: {
+ updateRecordingTime();
+ break;
+ }
+
+ case CHECK_DISPLAY_ROTATION: {
+ // Restart the preview if display rotation has changed.
+ // Sometimes this happens when the device is held upside
+ // down and camera app is opened. Rotation animation will
+ // take some time and the rotation value we have got may be
+ // wrong. Framework does not have a callback for this now.
+ if ((Util.getDisplayRotation(mActivity) != mDisplayRotation)
+ && !mMediaRecorderRecording && !mSwitchingCamera) {
+ startPreview();
+ }
+ if (SystemClock.uptimeMillis() - mOnResumeTime < 5000) {
+ mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+ }
+ break;
+ }
+
+ case SHOW_TAP_TO_SNAPSHOT_TOAST: {
+ showTapToSnapshotToast();
+ break;
+ }
+
+ case SWITCH_CAMERA: {
+ switchCamera();
+ break;
+ }
+
+ case SWITCH_CAMERA_START_ANIMATION: {
+ //TODO:
+ //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera();
+
+ // Enable all camera controls.
+ mSwitchingCamera = false;
+ break;
+ }
+
+ default:
+ Log.v(TAG, "Unhandled message: " + msg.what);
+ break;
+ }
+ }
+ }
+
+ private BroadcastReceiver mReceiver = null;
+
+ private class MyBroadcastReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (action.equals(Intent.ACTION_MEDIA_EJECT)) {
+ stopVideoRecording();
+ } else if (action.equals(Intent.ACTION_MEDIA_SCANNER_STARTED)) {
+ Toast.makeText(mActivity,
+ mActivity.getResources().getString(R.string.wait), Toast.LENGTH_LONG).show();
+ }
+ }
+ }
+
+ private String createName(long dateTaken) {
+ Date date = new Date(dateTaken);
+ SimpleDateFormat dateFormat = new SimpleDateFormat(
+ mActivity.getString(R.string.video_file_name_format));
+
+ return dateFormat.format(date);
+ }
+
+ private int getPreferredCameraId(ComboPreferences preferences) {
+ int intentCameraId = Util.getCameraFacingIntentExtras(mActivity);
+ if (intentCameraId != -1) {
+ // Testing purpose. Launch a specific camera through the intent
+ // extras.
+ return intentCameraId;
+ } else {
+ return CameraSettings.readPreferredCameraId(preferences);
+ }
+ }
+
+ private void initializeSurfaceView() {
+ if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16
+ mUI.initializeSurfaceView();
+ }
+ }
+
+ @Override
+ public void init(NewCameraActivity activity, View root) {
+ mActivity = activity;
+ mUI = new NewVideoUI(activity, this, root);
+ mPreferences = new ComboPreferences(mActivity);
+ CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal());
+ mCameraId = getPreferredCameraId(mPreferences);
+
+ mPreferences.setLocalId(mActivity, mCameraId);
+ CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+
+ mPrefVideoEffectDefault = mActivity.getString(R.string.pref_video_effect_default);
+ resetEffect();
+ mOrientationManager = new OrientationManager(mActivity);
+
+ /*
+ * To reduce startup time, we start the preview in another thread.
+ * We make sure the preview is started at the end of onCreate.
+ */
+ CameraOpenThread cameraOpenThread = new CameraOpenThread();
+ cameraOpenThread.start();
+
+ mContentResolver = mActivity.getContentResolver();
+
+ // Surface texture is from camera screen nail and startPreview needs it.
+ // This must be done before startPreview.
+ mIsVideoCaptureIntent = isVideoCaptureIntent();
+ initializeSurfaceView();
+
+ // Make sure camera device is opened.
+ try {
+ cameraOpenThread.join();
+ if (mOpenCameraFail) {
+ Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+ return;
+ } else if (mCameraDisabled) {
+ Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+ return;
+ }
+ } catch (InterruptedException ex) {
+ // ignore
+ }
+
+ readVideoPreferences();
+ mUI.setPrefChangedListener(this);
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ startPreview();
+ }
+ }).start();
+
+ mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false);
+ mLocationManager = new LocationManager(mActivity, null);
+
+ mUI.setOrientationIndicator(0, false);
+ setDisplayOrientation();
+
+ mUI.showTimeLapseUI(mCaptureTimeLapse);
+ initializeVideoSnapshot();
+ resizeForPreviewAspectRatio();
+
+ initializeVideoControl();
+ mPendingSwitchCameraId = -1;
+ 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);
+ mCameraDevice.setParameters(mParameters);
+
+ Log.v(TAG, "Video snapshot start");
+ mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc));
+ showVideoSnapshotUI(true);
+ mSnapshotInProgress = true;
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+ UsageStatistics.ACTION_CAPTURE_DONE, "VideoSnapshot");
+ }
+
+ @Override
+ public void onStop() {}
+
+ private void loadCameraPreferences() {
+ CameraSettings settings = new CameraSettings(mActivity, mParameters,
+ mCameraId, CameraHolder.instance().getCameraInfo());
+ // Remove the video quality preference setting when the quality is given in the intent.
+ mPreferenceGroup = filterPreferenceScreenByIntent(
+ settings.getPreferenceGroup(R.xml.video_preferences));
+ }
+
+ private void initializeVideoControl() {
+ loadCameraPreferences();
+ mUI.initializePopup(mPreferenceGroup);
+ if (effectsActive()) {
+ mUI.overrideSettings(
+ CameraSettings.KEY_VIDEO_QUALITY,
+ Integer.toString(getLowVideoQuality()));
+ }
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+ private static int getLowVideoQuality() {
+ if (ApiHelper.HAS_FINE_RESOLUTION_QUALITY_LEVELS) {
+ return CamcorderProfile.QUALITY_480P;
+ } else {
+ return CamcorderProfile.QUALITY_LOW;
+ }
+ }
+
+
+ @Override
+ public void onOrientationChanged(int orientation) {
+ // We keep the last known orientation. So if the user first orient
+ // the camera then point the camera to floor or sky, we still have
+ // the correct orientation.
+ if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) return;
+ int newOrientation = Util.roundOrientation(orientation, mOrientation);
+
+ if (mOrientation != newOrientation) {
+ mOrientation = newOrientation;
+ // The input of effects recorder is affected by
+ // android.hardware.Camera.setDisplayOrientation. Its value only
+ // compensates the camera orientation (no Display.getRotation).
+ // So the orientation hint here should only consider sensor
+ // orientation.
+ if (effectsActive()) {
+ mEffectsRecorder.setOrientationHint(mOrientation);
+ }
+ }
+
+ // Show the toast after getting the first orientation changed.
+ if (mHandler.hasMessages(SHOW_TAP_TO_SNAPSHOT_TOAST)) {
+ mHandler.removeMessages(SHOW_TAP_TO_SNAPSHOT_TOAST);
+ showTapToSnapshotToast();
+ }
+ }
+
+ private void startPlayVideoActivity() {
+ Intent intent = new Intent(Intent.ACTION_VIEW);
+ intent.setDataAndType(mCurrentVideoUri, convertOutputFormatToMimeType(mProfile.fileFormat));
+ try {
+ mActivity.startActivity(intent);
+ } catch (ActivityNotFoundException ex) {
+ Log.e(TAG, "Couldn't view video " + mCurrentVideoUri, ex);
+ }
+ }
+
+ @OnClickAttr
+ public void onReviewPlayClicked(View v) {
+ startPlayVideoActivity();
+ }
+
+ @OnClickAttr
+ public void onReviewDoneClicked(View v) {
+ mIsInReviewMode = false;
+ doReturnToCaller(true);
+ }
+
+ @OnClickAttr
+ public void onReviewCancelClicked(View v) {
+ mIsInReviewMode = false;
+ stopVideoRecording();
+ doReturnToCaller(false);
+ }
+
+ @Override
+ public boolean isInReviewMode() {
+ return mIsInReviewMode;
+ }
+
+ private void onStopVideoRecording() {
+ mEffectsDisplayResult = true;
+ boolean recordFail = stopVideoRecording();
+ if (mIsVideoCaptureIntent) {
+ if (!effectsActive()) {
+ if (mQuickCapture) {
+ doReturnToCaller(!recordFail);
+ } else if (!recordFail) {
+ showCaptureResult();
+ }
+ }
+ } else if (!recordFail){
+ // Start capture animation.
+ if (!mPaused && ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+ // The capture animation is disabled on ICS because we use SurfaceView
+ // for preview during recording. When the recording is done, we switch
+ // back to use SurfaceTexture for preview and we need to stop then start
+ // the preview. This will cause the preview flicker since the preview
+ // will not be continuous for a short period of time.
+ // TODO: need to get the capture animation to work
+ // ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
+ }
+ }
+ }
+
+ public void onProtectiveCurtainClick(View v) {
+ // Consume clicks
+ }
+
+ @Override
+ public void onShutterButtonClick() {
+ if (mUI.collapseCameraControls() || mSwitchingCamera) return;
+
+ boolean stop = mMediaRecorderRecording;
+
+ if (stop) {
+ onStopVideoRecording();
+ } else {
+ startVideoRecording();
+ }
+ mUI.enableShutter(false);
+
+ // Keep the shutter button disabled when in video capture intent
+ // mode and recording is stopped. It'll be re-enabled when
+ // re-take button is clicked.
+ if (!(mIsVideoCaptureIntent && stop)) {
+ mHandler.sendEmptyMessageDelayed(
+ ENABLE_SHUTTER_BUTTON, SHUTTER_BUTTON_TIMEOUT);
+ }
+ }
+
+ @Override
+ public void onShutterButtonFocus(boolean pressed) {
+ mUI.setShutterPressed(pressed);
+ }
+
+ private void readVideoPreferences() {
+ // The preference stores values from ListPreference and is thus string type for all values.
+ // We need to convert it to int manually.
+ String defaultQuality = CameraSettings.getDefaultVideoQuality(mCameraId,
+ mActivity.getResources().getString(R.string.pref_video_quality_default));
+ String videoQuality =
+ mPreferences.getString(CameraSettings.KEY_VIDEO_QUALITY,
+ defaultQuality);
+ int quality = Integer.valueOf(videoQuality);
+
+ // Set video quality.
+ Intent intent = mActivity.getIntent();
+ if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
+ int extraVideoQuality =
+ intent.getIntExtra(MediaStore.EXTRA_VIDEO_QUALITY, 0);
+ if (extraVideoQuality > 0) {
+ quality = CamcorderProfile.QUALITY_HIGH;
+ } else { // 0 is mms.
+ quality = CamcorderProfile.QUALITY_LOW;
+ }
+ }
+
+ // Set video duration limit. The limit is read from the preference,
+ // unless it is specified in the intent.
+ if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
+ int seconds =
+ intent.getIntExtra(MediaStore.EXTRA_DURATION_LIMIT, 0);
+ mMaxVideoDurationInMs = 1000 * seconds;
+ } else {
+ mMaxVideoDurationInMs = CameraSettings.getMaxVideoDuration(mActivity);
+ }
+
+ // Set effect
+ mEffectType = CameraSettings.readEffectType(mPreferences);
+ if (mEffectType != EffectsRecorder.EFFECT_NONE) {
+ mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
+ // Set quality to be no higher than 480p.
+ CamcorderProfile profile = CamcorderProfile.get(mCameraId, quality);
+ if (profile.videoFrameHeight > 480) {
+ quality = getLowVideoQuality();
+ }
+ } else {
+ mEffectParameter = null;
+ }
+ // Read time lapse recording interval.
+ if (ApiHelper.HAS_TIME_LAPSE_RECORDING) {
+ String frameIntervalStr = mPreferences.getString(
+ CameraSettings.KEY_VIDEO_TIME_LAPSE_FRAME_INTERVAL,
+ mActivity.getString(R.string.pref_video_time_lapse_frame_interval_default));
+ mTimeBetweenTimeLapseFrameCaptureMs = Integer.parseInt(frameIntervalStr);
+ mCaptureTimeLapse = (mTimeBetweenTimeLapseFrameCaptureMs != 0);
+ }
+ // TODO: This should be checked instead directly +1000.
+ if (mCaptureTimeLapse) quality += 1000;
+ mProfile = CamcorderProfile.get(mCameraId, quality);
+ getDesiredPreviewSize();
+ }
+
+ private void writeDefaultEffectToPrefs() {
+ ComboPreferences.Editor editor = mPreferences.edit();
+ editor.putString(CameraSettings.KEY_VIDEO_EFFECT,
+ mActivity.getString(R.string.pref_video_effect_default));
+ editor.apply();
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+ private void getDesiredPreviewSize() {
+ mParameters = mCameraDevice.getParameters();
+ if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) {
+ if (mParameters.getSupportedVideoSizes() == null || effectsActive()) {
+ mDesiredPreviewWidth = mProfile.videoFrameWidth;
+ mDesiredPreviewHeight = mProfile.videoFrameHeight;
+ } else { // Driver supports separates outputs for preview and video.
+ List<Size> sizes = mParameters.getSupportedPreviewSizes();
+ Size preferred = mParameters.getPreferredPreviewSizeForVideo();
+ int product = preferred.width * preferred.height;
+ Iterator<Size> it = sizes.iterator();
+ // Remove the preview sizes that are not preferred.
+ while (it.hasNext()) {
+ Size size = it.next();
+ if (size.width * size.height > product) {
+ it.remove();
+ }
+ }
+ Size optimalSize = Util.getOptimalPreviewSize(mActivity, sizes,
+ (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
+ mDesiredPreviewWidth = optimalSize.width;
+ mDesiredPreviewHeight = optimalSize.height;
+ }
+ } else {
+ mDesiredPreviewWidth = mProfile.videoFrameWidth;
+ mDesiredPreviewHeight = mProfile.videoFrameHeight;
+ }
+ mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
+ Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
+ ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
+ }
+
+ private void resizeForPreviewAspectRatio() {
+ mUI.setAspectRatio(
+ (double) mProfile.videoFrameWidth / mProfile.videoFrameHeight);
+ }
+
+ @Override
+ public void installIntentFilter() {
+ // install an intent filter to receive SD card related events.
+ IntentFilter intentFilter =
+ new IntentFilter(Intent.ACTION_MEDIA_EJECT);
+ intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
+ intentFilter.addDataScheme("file");
+ mReceiver = new MyBroadcastReceiver();
+ mActivity.registerReceiver(mReceiver, intentFilter);
+ }
+
+ @Override
+ public void onResumeBeforeSuper() {
+ mPaused = false;
+ }
+
+ @Override
+ public void onResumeAfterSuper() {
+ if (mOpenCameraFail || mCameraDisabled)
+ return;
+ mUI.enableShutter(false);
+ mZoomValue = 0;
+
+ showVideoSnapshotUI(false);
+
+ if (!mPreviewing) {
+ resetEffect();
+ openCamera();
+ if (mOpenCameraFail) {
+ Util.showErrorAndFinish(mActivity,
+ R.string.cannot_connect_camera);
+ return;
+ } else if (mCameraDisabled) {
+ Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+ return;
+ }
+ readVideoPreferences();
+ resizeForPreviewAspectRatio();
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ startPreview();
+ }
+ }).start();
+ } else {
+ // preview already started
+ mUI.enableShutter(true);
+ }
+
+ // Initializing it here after the preview is started.
+ mUI.initializeZoom(mParameters);
+
+ keepScreenOnAwhile();
+
+ // Initialize location service.
+ boolean recordLocation = RecordLocationPreference.get(mPreferences,
+ mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+
+ if (mPreviewing) {
+ mOnResumeTime = SystemClock.uptimeMillis();
+ mHandler.sendEmptyMessageDelayed(CHECK_DISPLAY_ROTATION, 100);
+ }
+ // Dismiss open menu if exists.
+ PopupManager.getInstance(mActivity).notifyShowPopup(null);
+
+ mVideoNamer = new VideoNamer();
+ UsageStatistics.onContentViewChanged(
+ UsageStatistics.COMPONENT_CAMERA, "VideoModule");
+ }
+
+ private void setDisplayOrientation() {
+ mDisplayRotation = Util.getDisplayRotation(mActivity);
+ mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId);
+ // Change the camera display orientation
+ if (mCameraDevice != null) {
+ mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+ }
+ }
+
+ @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 || mCameraDevice == null) return index;
+ // Set zoom parameters asynchronously
+ mParameters.setZoom(mZoomValue);
+ mCameraDevice.setParameters(mParameters);
+ Parameters p = mCameraDevice.getParameters();
+ if (p != null) return p.getZoom();
+ return index;
+ }
+ private void startPreview() {
+ Log.v(TAG, "startPreview");
+
+ mCameraDevice.setErrorCallback(mErrorCallback);
+ if (mPreviewing == true) {
+ stopPreview();
+ if (effectsActive() && mEffectsRecorder != null) {
+ mEffectsRecorder.release();
+ mEffectsRecorder = null;
+ }
+ }
+
+ setDisplayOrientation();
+ mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation);
+ setCameraParameters();
+
+ try {
+ if (!effectsActive()) {
+ SurfaceTexture surfaceTexture = mUI.getSurfaceTexture();
+ if (surfaceTexture == null) {
+ return; // The texture has been destroyed (pause, etc)
+ }
+ mCameraDevice.setPreviewTextureAsync(surfaceTexture);
+ mCameraDevice.startPreviewAsync();
+ mPreviewing = true;
+ onPreviewStarted();
+ } else {
+ initializeEffectsPreview();
+ mEffectsRecorder.startPreview();
+ mPreviewing = true;
+ onPreviewStarted();
+ }
+ } catch (Throwable ex) {
+ closeCamera();
+ throw new RuntimeException("startPreview failed", ex);
+ } finally {
+ mActivity.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mOpenCameraFail) {
+ Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
+ } else if (mCameraDisabled) {
+ Util.showErrorAndFinish(mActivity, R.string.camera_disabled);
+ }
+ }
+ });
+ }
+
+ }
+
+ private void onPreviewStarted() {
+ mUI.enableShutter(true);
+ }
+
+ @Override
+ public void stopPreview() {
+ if (!mPreviewing) return;
+ mCameraDevice.stopPreview();
+ mPreviewing = false;
+ }
+
+ // Closing the effects out. Will shut down the effects graph.
+ private void closeEffects() {
+ Log.v(TAG, "Closing effects");
+ mEffectType = EffectsRecorder.EFFECT_NONE;
+ if (mEffectsRecorder == null) {
+ Log.d(TAG, "Effects are already closed. Nothing to do");
+ return;
+ }
+ // This call can handle the case where the camera is already released
+ // after the recording has been stopped.
+ mEffectsRecorder.release();
+ mEffectsRecorder = null;
+ }
+
+ // By default, we want to close the effects as well with the camera.
+ private void closeCamera() {
+ closeCamera(true);
+ }
+
+ // In certain cases, when the effects are active, we may want to shutdown
+ // only the camera related parts, and handle closing the effects in the
+ // effectsUpdate callback.
+ // For example, in onPause, we want to make the camera available to
+ // outside world immediately, however, want to wait till the effects
+ // callback to shut down the effects. In such a case, we just disconnect
+ // the effects from the camera by calling disconnectCamera. That way
+ // the effects can handle that when shutting down.
+ //
+ // @param closeEffectsAlso - indicates whether we want to close the
+ // effects also along with the camera.
+ private void closeCamera(boolean closeEffectsAlso) {
+ Log.v(TAG, "closeCamera");
+ if (mCameraDevice == null) {
+ Log.d(TAG, "already stopped.");
+ return;
+ }
+
+ if (mEffectsRecorder != null) {
+ // Disconnect the camera from effects so that camera is ready to
+ // be released to the outside world.
+ mEffectsRecorder.disconnectCamera();
+ }
+ if (closeEffectsAlso) closeEffects();
+ mCameraDevice.setZoomChangeListener(null);
+ mCameraDevice.setErrorCallback(null);
+ synchronized(mCameraOpened) {
+ if (mCameraOpened) {
+ CameraHolder.instance().release();
+ }
+ mCameraOpened = false;
+ }
+ mCameraDevice = null;
+ mPreviewing = false;
+ mSnapshotInProgress = false;
+ }
+
+ private void releasePreviewResources() {
+ if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+ mUI.hideSurfaceView();
+ }
+ }
+
+ @Override
+ public void onPauseBeforeSuper() {
+ mPaused = true;
+
+ if (mMediaRecorderRecording) {
+ // Camera will be released in onStopVideoRecording.
+ onStopVideoRecording();
+ } else {
+ closeCamera();
+ if (!effectsActive()) releaseMediaRecorder();
+ }
+ if (effectsActive()) {
+ // If the effects are active, make sure we tell the graph that the
+ // surfacetexture is not valid anymore. Disconnect the graph from
+ // the display. This should be done before releasing the surface
+ // texture.
+ mEffectsRecorder.disconnectDisplay();
+ } else {
+ // Close the file descriptor and clear the video namer only if the
+ // effects are not active. If effects are active, we need to wait
+ // till we get the callback from the Effects that the graph is done
+ // recording. That also needs a change in the stopVideoRecording()
+ // call to not call closeCamera if the effects are active, because
+ // that will close down the effects are well, thus making this if
+ // condition invalid.
+ closeVideoFileDescriptor();
+ clearVideoNamer();
+ }
+
+ releasePreviewResources();
+
+ if (mReceiver != null) {
+ mActivity.unregisterReceiver(mReceiver);
+ mReceiver = null;
+ }
+ resetScreenOn();
+
+ if (mLocationManager != null) mLocationManager.recordLocation(false);
+
+ mHandler.removeMessages(CHECK_DISPLAY_ROTATION);
+ mHandler.removeMessages(SWITCH_CAMERA);
+ mHandler.removeMessages(SWITCH_CAMERA_START_ANIMATION);
+ mPendingSwitchCameraId = -1;
+ mSwitchingCamera = false;
+ // Call onPause after stopping video recording. So the camera can be
+ // released as soon as possible.
+ }
+
+ @Override
+ public void onPauseAfterSuper() {
+ }
+
+ @Override
+ public void onUserInteraction() {
+ if (!mMediaRecorderRecording && !mActivity.isFinishing()) {
+ keepScreenOnAwhile();
+ }
+ }
+
+ @Override
+ public boolean onBackPressed() {
+ if (mPaused) return true;
+ if (mMediaRecorderRecording) {
+ onStopVideoRecording();
+ return true;
+ } else if (mUI.hidePieRenderer()) {
+ return true;
+ } else {
+ return mUI.removeTopLevelPopup();
+ }
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ // Do not handle any key if the activity is paused.
+ if (mPaused) {
+ return true;
+ }
+
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CAMERA:
+ if (event.getRepeatCount() == 0) {
+ mUI.clickShutter();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ if (event.getRepeatCount() == 0) {
+ mUI.clickShutter();
+ return true;
+ }
+ break;
+ case KeyEvent.KEYCODE_MENU:
+ if (mMediaRecorderRecording) return true;
+ break;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_CAMERA:
+ mUI.pressShutter(false);
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public boolean isVideoCaptureIntent() {
+ String action = mActivity.getIntent().getAction();
+ return (MediaStore.ACTION_VIDEO_CAPTURE.equals(action));
+ }
+
+ private void doReturnToCaller(boolean valid) {
+ Intent resultIntent = new Intent();
+ int resultCode;
+ if (valid) {
+ resultCode = Activity.RESULT_OK;
+ resultIntent.setData(mCurrentVideoUri);
+ } else {
+ resultCode = Activity.RESULT_CANCELED;
+ }
+ mActivity.setResultEx(resultCode, resultIntent);
+ mActivity.finish();
+ }
+
+ private void cleanupEmptyFile() {
+ if (mVideoFilename != null) {
+ File f = new File(mVideoFilename);
+ if (f.length() == 0 && f.delete()) {
+ Log.v(TAG, "Empty video file deleted: " + mVideoFilename);
+ mVideoFilename = null;
+ }
+ }
+ }
+
+ private void setupMediaRecorderPreviewDisplay() {
+ // Nothing to do here if using SurfaceTexture.
+ 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();
+ 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
+ // will set the orientation correctly when showing the texture.
+ // Gallery will handle the orientation for the preview. For
+ // SurfaceView we will have to take everything into account so the
+ // display rotation is considered.
+ mCameraDevice.setDisplayOrientation(
+ Util.getDisplayOrientation(mDisplayRotation, mCameraId));
+ mCameraDevice.startPreviewAsync();
+ mPreviewing = true;
+ mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface());
+ }
+ }
+
+ // Prepares media recorder.
+ private void initializeRecorder() {
+ Log.v(TAG, "initializeRecorder");
+ // If the mCameraDevice is null, then this activity is going to finish
+ if (mCameraDevice == null) return;
+
+ if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+ // Set the SurfaceView to visible so the surface gets created.
+ // surfaceCreated() is called immediately when the visibility is
+ // changed to visible. Thus, mSurfaceViewReady should become true
+ // right after calling setVisibility().
+ mUI.showSurfaceView();
+ }
+
+ Intent intent = mActivity.getIntent();
+ Bundle myExtras = intent.getExtras();
+
+ long requestedSizeLimit = 0;
+ closeVideoFileDescriptor();
+ if (mIsVideoCaptureIntent && myExtras != null) {
+ Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+ if (saveUri != null) {
+ try {
+ mVideoFileDescriptor =
+ mContentResolver.openFileDescriptor(saveUri, "rw");
+ mCurrentVideoUri = saveUri;
+ } catch (java.io.FileNotFoundException ex) {
+ // invalid uri
+ Log.e(TAG, ex.toString());
+ }
+ }
+ requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
+ }
+ mMediaRecorder = new MediaRecorder();
+
+ setupMediaRecorderPreviewDisplay();
+ // Unlock the camera object before passing it to media recorder.
+ mCameraDevice.unlock();
+ mCameraDevice.waitDone();
+ mMediaRecorder.setCamera(mCameraDevice.getCamera());
+ if (!mCaptureTimeLapse) {
+ mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER);
+ }
+ mMediaRecorder.setVideoSource(MediaRecorder.VideoSource.CAMERA);
+ mMediaRecorder.setProfile(mProfile);
+ mMediaRecorder.setMaxDuration(mMaxVideoDurationInMs);
+ if (mCaptureTimeLapse) {
+ double fps = 1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs;
+ setCaptureRate(mMediaRecorder, fps);
+ }
+
+ setRecordLocation();
+
+ // Set output file.
+ // Try Uri in the intent first. If it doesn't exist, use our own
+ // instead.
+ if (mVideoFileDescriptor != null) {
+ mMediaRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
+ } else {
+ generateVideoFilename(mProfile.fileFormat);
+ mMediaRecorder.setOutputFile(mVideoFilename);
+ }
+
+ // Set maximum file size.
+ long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
+ if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
+ maxFileSize = requestedSizeLimit;
+ }
+
+ try {
+ mMediaRecorder.setMaxFileSize(maxFileSize);
+ } catch (RuntimeException exception) {
+ // We are going to ignore failure of setMaxFileSize here, as
+ // a) The composer selected may simply not support it, or
+ // b) The underlying media framework may not handle 64-bit range
+ // on the size restriction.
+ }
+
+ // See android.hardware.Camera.Parameters.setRotation for
+ // documentation.
+ // Note that mOrientation here is the device orientation, which is the opposite of
+ // what activity.getWindowManager().getDefaultDisplay().getRotation() would return,
+ // which is the orientation the graphics need to rotate in order to render correctly.
+ int rotation = 0;
+ if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
+ CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+ if (info.facing == CameraInfo.CAMERA_FACING_FRONT) {
+ rotation = (info.orientation - mOrientation + 360) % 360;
+ } else { // back-facing camera
+ rotation = (info.orientation + mOrientation) % 360;
+ }
+ }
+ mMediaRecorder.setOrientationHint(rotation);
+
+ try {
+ mMediaRecorder.prepare();
+ } catch (IOException e) {
+ Log.e(TAG, "prepare failed for " + mVideoFilename, e);
+ releaseMediaRecorder();
+ throw new RuntimeException(e);
+ }
+
+ mMediaRecorder.setOnErrorListener(this);
+ mMediaRecorder.setOnInfoListener(this);
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB)
+ private static void setCaptureRate(MediaRecorder recorder, double fps) {
+ recorder.setCaptureRate(fps);
+ }
+
+ @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
+ private void setRecordLocation() {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ Location loc = mLocationManager.getCurrentLocation();
+ if (loc != null) {
+ mMediaRecorder.setLocation((float) loc.getLatitude(),
+ (float) loc.getLongitude());
+ }
+ }
+ }
+
+ private void initializeEffectsPreview() {
+ Log.v(TAG, "initializeEffectsPreview");
+ // If the mCameraDevice is null, then this activity is going to finish
+ if (mCameraDevice == null) return;
+
+ boolean inLandscape = (mActivity.getResources().getConfiguration().orientation
+ == Configuration.ORIENTATION_LANDSCAPE);
+
+ CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
+
+ mEffectsDisplayResult = false;
+ mEffectsRecorder = new EffectsRecorder(mActivity);
+
+ // TODO: Confirm none of the following need to go to initializeEffectsRecording()
+ // and none of these change even when the preview is not refreshed.
+ mEffectsRecorder.setCameraDisplayOrientation(mCameraDisplayOrientation);
+ mEffectsRecorder.setCamera(mCameraDevice);
+ mEffectsRecorder.setCameraFacing(info.facing);
+ mEffectsRecorder.setProfile(mProfile);
+ mEffectsRecorder.setEffectsListener(this);
+ mEffectsRecorder.setOnInfoListener(this);
+ mEffectsRecorder.setOnErrorListener(this);
+
+ // The input of effects recorder is affected by
+ // android.hardware.Camera.setDisplayOrientation. Its value only
+ // compensates the camera orientation (no Display.getRotation). So the
+ // orientation hint here should only consider sensor orientation.
+ int orientation = 0;
+ if (mOrientation != OrientationEventListener.ORIENTATION_UNKNOWN) {
+ orientation = mOrientation;
+ }
+ mEffectsRecorder.setOrientationHint(orientation);
+
+ mEffectsRecorder.setPreviewSurfaceTexture(mUI.getSurfaceTexture(),
+ mUI.getPreviewWidth(), mUI.getPreviewHeight());
+
+ if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
+ ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
+ mEffectsRecorder.setEffect(mEffectType, mEffectUriFromGallery);
+ } else {
+ mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
+ }
+ }
+
+ private void initializeEffectsRecording() {
+ Log.v(TAG, "initializeEffectsRecording");
+
+ Intent intent = mActivity.getIntent();
+ Bundle myExtras = intent.getExtras();
+
+ long requestedSizeLimit = 0;
+ closeVideoFileDescriptor();
+ if (mIsVideoCaptureIntent && myExtras != null) {
+ Uri saveUri = (Uri) myExtras.getParcelable(MediaStore.EXTRA_OUTPUT);
+ if (saveUri != null) {
+ try {
+ mVideoFileDescriptor =
+ mContentResolver.openFileDescriptor(saveUri, "rw");
+ mCurrentVideoUri = saveUri;
+ } catch (java.io.FileNotFoundException ex) {
+ // invalid uri
+ Log.e(TAG, ex.toString());
+ }
+ }
+ requestedSizeLimit = myExtras.getLong(MediaStore.EXTRA_SIZE_LIMIT);
+ }
+
+ mEffectsRecorder.setProfile(mProfile);
+ // important to set the capture rate to zero if not timelapsed, since the
+ // effectsrecorder object does not get created again for each recording
+ // session
+ if (mCaptureTimeLapse) {
+ mEffectsRecorder.setCaptureRate((1000 / (double) mTimeBetweenTimeLapseFrameCaptureMs));
+ } else {
+ mEffectsRecorder.setCaptureRate(0);
+ }
+
+ // Set output file
+ if (mVideoFileDescriptor != null) {
+ mEffectsRecorder.setOutputFile(mVideoFileDescriptor.getFileDescriptor());
+ } else {
+ generateVideoFilename(mProfile.fileFormat);
+ mEffectsRecorder.setOutputFile(mVideoFilename);
+ }
+
+ // Set maximum file size.
+ long maxFileSize = mActivity.getStorageSpace() - Storage.LOW_STORAGE_THRESHOLD;
+ if (requestedSizeLimit > 0 && requestedSizeLimit < maxFileSize) {
+ maxFileSize = requestedSizeLimit;
+ }
+ mEffectsRecorder.setMaxFileSize(maxFileSize);
+ mEffectsRecorder.setMaxDuration(mMaxVideoDurationInMs);
+ }
+
+
+ private void releaseMediaRecorder() {
+ Log.v(TAG, "Releasing media recorder.");
+ if (mMediaRecorder != null) {
+ cleanupEmptyFile();
+ mMediaRecorder.reset();
+ mMediaRecorder.release();
+ mMediaRecorder = null;
+ }
+ mVideoFilename = null;
+ }
+
+ private void releaseEffectsRecorder() {
+ Log.v(TAG, "Releasing effects recorder.");
+ if (mEffectsRecorder != null) {
+ cleanupEmptyFile();
+ mEffectsRecorder.release();
+ mEffectsRecorder = null;
+ }
+ mEffectType = EffectsRecorder.EFFECT_NONE;
+ mVideoFilename = null;
+ }
+
+ private void generateVideoFilename(int outputFileFormat) {
+ long dateTaken = System.currentTimeMillis();
+ String title = createName(dateTaken);
+ // Used when emailing.
+ String filename = title + convertOutputFormatToFileExt(outputFileFormat);
+ String mime = convertOutputFormatToMimeType(outputFileFormat);
+ String path = Storage.DIRECTORY + '/' + filename;
+ String tmpPath = path + ".tmp";
+ mCurrentVideoValues = new ContentValues(7);
+ mCurrentVideoValues.put(Video.Media.TITLE, title);
+ mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename);
+ mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken);
+ mCurrentVideoValues.put(Video.Media.MIME_TYPE, mime);
+ mCurrentVideoValues.put(Video.Media.DATA, path);
+ mCurrentVideoValues.put(Video.Media.RESOLUTION,
+ Integer.toString(mProfile.videoFrameWidth) + "x" +
+ Integer.toString(mProfile.videoFrameHeight));
+ Location loc = mLocationManager.getCurrentLocation();
+ if (loc != null) {
+ mCurrentVideoValues.put(Video.Media.LATITUDE, loc.getLatitude());
+ mCurrentVideoValues.put(Video.Media.LONGITUDE, loc.getLongitude());
+ }
+ mVideoNamer.prepareUri(mContentResolver, mCurrentVideoValues);
+ mVideoFilename = tmpPath;
+ Log.v(TAG, "New video filename: " + mVideoFilename);
+ }
+
+ private boolean addVideoToMediaStore() {
+ boolean fail = false;
+ if (mVideoFileDescriptor == null) {
+ mCurrentVideoValues.put(Video.Media.SIZE,
+ new File(mCurrentVideoFilename).length());
+ long duration = SystemClock.uptimeMillis() - mRecordingStartTime;
+ if (duration > 0) {
+ if (mCaptureTimeLapse) {
+ duration = getTimeLapseVideoLength(duration);
+ }
+ mCurrentVideoValues.put(Video.Media.DURATION, duration);
+ } else {
+ Log.w(TAG, "Video duration <= 0 : " + duration);
+ }
+ try {
+ mCurrentVideoUri = mVideoNamer.getUri();
+ //TODO: mActivity.addSecureAlbumItemIfNeeded(true, mCurrentVideoUri);
+
+ // Rename the video file to the final name. This avoids other
+ // apps reading incomplete data. We need to do it after the
+ // above mVideoNamer.getUri() call, so we are certain that the
+ // previous insert to MediaProvider is completed.
+ String finalName = mCurrentVideoValues.getAsString(
+ Video.Media.DATA);
+ if (new File(mCurrentVideoFilename).renameTo(new File(finalName))) {
+ mCurrentVideoFilename = finalName;
+ }
+
+ mContentResolver.update(mCurrentVideoUri, mCurrentVideoValues
+ , null, null);
+ mActivity.sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO,
+ mCurrentVideoUri));
+ } catch (Exception e) {
+ // We failed to insert into the database. This can happen if
+ // the SD card is unmounted.
+ Log.e(TAG, "failed to add video to media store", e);
+ mCurrentVideoUri = null;
+ mCurrentVideoFilename = null;
+ fail = true;
+ } finally {
+ Log.v(TAG, "Current video URI: " + mCurrentVideoUri);
+ }
+ }
+ mCurrentVideoValues = null;
+ return fail;
+ }
+
+ private void deleteCurrentVideo() {
+ // Remove the video and the uri if the uri is not passed in by intent.
+ if (mCurrentVideoFilename != null) {
+ deleteVideoFile(mCurrentVideoFilename);
+ mCurrentVideoFilename = null;
+ if (mCurrentVideoUri != null) {
+ mContentResolver.delete(mCurrentVideoUri, null, null);
+ mCurrentVideoUri = null;
+ }
+ }
+ mActivity.updateStorageSpaceAndHint();
+ }
+
+ private void deleteVideoFile(String fileName) {
+ Log.v(TAG, "Deleting video " + fileName);
+ File f = new File(fileName);
+ if (!f.delete()) {
+ Log.v(TAG, "Could not delete " + fileName);
+ }
+ }
+
+ private PreferenceGroup filterPreferenceScreenByIntent(
+ PreferenceGroup screen) {
+ Intent intent = mActivity.getIntent();
+ if (intent.hasExtra(MediaStore.EXTRA_VIDEO_QUALITY)) {
+ CameraSettings.removePreferenceFromScreen(screen,
+ CameraSettings.KEY_VIDEO_QUALITY);
+ }
+
+ if (intent.hasExtra(MediaStore.EXTRA_DURATION_LIMIT)) {
+ CameraSettings.removePreferenceFromScreen(screen,
+ CameraSettings.KEY_VIDEO_QUALITY);
+ }
+ return screen;
+ }
+
+ // from MediaRecorder.OnErrorListener
+ @Override
+ public void onError(MediaRecorder mr, int what, int extra) {
+ Log.e(TAG, "MediaRecorder error. what=" + what + ". extra=" + extra);
+ if (what == MediaRecorder.MEDIA_RECORDER_ERROR_UNKNOWN) {
+ // We may have run out of space on the sdcard.
+ stopVideoRecording();
+ mActivity.updateStorageSpaceAndHint();
+ }
+ }
+
+ // from MediaRecorder.OnInfoListener
+ @Override
+ public void onInfo(MediaRecorder mr, int what, int extra) {
+ if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) {
+ if (mMediaRecorderRecording) onStopVideoRecording();
+ } else if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED) {
+ if (mMediaRecorderRecording) onStopVideoRecording();
+
+ // Show the toast.
+ Toast.makeText(mActivity, R.string.video_reach_size_limit,
+ Toast.LENGTH_LONG).show();
+ }
+ }
+
+ /*
+ * Make sure we're not recording music playing in the background, ask the
+ * MediaPlaybackService to pause playback.
+ */
+ private void pauseAudioPlayback() {
+ // Shamelessly copied from MediaPlaybackService.java, which
+ // should be public, but isn't.
+ Intent i = new Intent("com.android.music.musicservicecommand");
+ i.putExtra("command", "pause");
+
+ mActivity.sendBroadcast(i);
+ }
+
+ // For testing.
+ public boolean isRecording() {
+ return mMediaRecorderRecording;
+ }
+
+ private void startVideoRecording() {
+ Log.v(TAG, "startVideoRecording");
+ // TODO: mActivity.setSwipingEnabled(false);
+
+ mActivity.updateStorageSpaceAndHint();
+ if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) {
+ Log.v(TAG, "Storage issue, ignore the start request");
+ return;
+ }
+
+ mCurrentVideoUri = null;
+ if (effectsActive()) {
+ initializeEffectsRecording();
+ if (mEffectsRecorder == null) {
+ Log.e(TAG, "Fail to initialize effect recorder");
+ return;
+ }
+ } else {
+ initializeRecorder();
+ if (mMediaRecorder == null) {
+ Log.e(TAG, "Fail to initialize media recorder");
+ return;
+ }
+ }
+
+ pauseAudioPlayback();
+
+ if (effectsActive()) {
+ try {
+ mEffectsRecorder.startRecording();
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Could not start effects recorder. ", e);
+ releaseEffectsRecorder();
+ return;
+ }
+ } else {
+ try {
+ mMediaRecorder.start(); // Recording is now started
+ } catch (RuntimeException e) {
+ Log.e(TAG, "Could not start media recorder. ", e);
+ releaseMediaRecorder();
+ // If start fails, frameworks will not lock the camera for us.
+ mCameraDevice.lock();
+ return;
+ }
+ }
+
+ // Make sure the video recording has started before announcing
+ // this in accessibility.
+ AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
+ mActivity.getString(R.string.video_recording_started));
+
+ // The parameters might have been altered by MediaRecorder already.
+ // We need to force mCameraDevice to refresh before getting it.
+ mCameraDevice.refreshParameters();
+ // The parameters may have been changed by MediaRecorder upon starting
+ // recording. We need to alter the parameters if we support camcorder
+ // zoom. To reduce latency when setting the parameters during zoom, we
+ // update mParameters here once.
+ if (ApiHelper.HAS_ZOOM_WHEN_RECORDING) {
+ mParameters = mCameraDevice.getParameters();
+ }
+
+ mUI.enableCameraControls(false);
+
+ mMediaRecorderRecording = true;
+ mOrientationManager.lockOrientation();
+ mRecordingStartTime = SystemClock.uptimeMillis();
+ mUI.showRecordingUI(true, mParameters.isZoomSupported());
+
+ updateRecordingTime();
+ keepScreenOn();
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+ UsageStatistics.ACTION_CAPTURE_START, "Video");
+ }
+
+ private void showCaptureResult() {
+ mIsInReviewMode = true;
+ Bitmap bitmap = null;
+ if (mVideoFileDescriptor != null) {
+ bitmap = Thumbnail.createVideoThumbnailBitmap(mVideoFileDescriptor.getFileDescriptor(),
+ mDesiredPreviewWidth);
+ } else if (mCurrentVideoFilename != null) {
+ bitmap = Thumbnail.createVideoThumbnailBitmap(mCurrentVideoFilename,
+ mDesiredPreviewWidth);
+ }
+ if (bitmap != null) {
+ // MetadataRetriever already rotates the thumbnail. We should rotate
+ // it to match the UI orientation (and mirror if it is front-facing camera).
+ CameraInfo[] info = CameraHolder.instance().getCameraInfo();
+ boolean mirror = (info[mCameraId].facing == CameraInfo.CAMERA_FACING_FRONT);
+ bitmap = Util.rotateAndMirror(bitmap, 0, mirror);
+ mUI.showReviewImage(bitmap);
+ }
+
+ mUI.showReviewControls();
+ mUI.enableCameraControls(false);
+ mUI.showTimeLapseUI(false);
+ }
+
+ private void hideAlert() {
+ mUI.enableCameraControls(true);
+ mUI.hideReviewUI();
+ if (mCaptureTimeLapse) {
+ mUI.showTimeLapseUI(true);
+ }
+ }
+
+ private boolean stopVideoRecording() {
+ Log.v(TAG, "stopVideoRecording");
+ //TODO: mUI.setSwipingEnabled(true);
+ mUI.showSwitcher();
+
+ boolean fail = false;
+ if (mMediaRecorderRecording) {
+ boolean shouldAddToMediaStoreNow = false;
+
+ try {
+ if (effectsActive()) {
+ // This is asynchronous, so we can't add to media store now because thumbnail
+ // may not be ready. In such case addVideoToMediaStore is called later
+ // through a callback from the MediaEncoderFilter to EffectsRecorder,
+ // and then to the VideoModule.
+ mEffectsRecorder.stopRecording();
+ } else {
+ mMediaRecorder.setOnErrorListener(null);
+ mMediaRecorder.setOnInfoListener(null);
+ mMediaRecorder.stop();
+ shouldAddToMediaStoreNow = true;
+ }
+ mCurrentVideoFilename = mVideoFilename;
+ Log.v(TAG, "stopVideoRecording: Setting current video filename: "
+ + mCurrentVideoFilename);
+ AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(),
+ mActivity.getString(R.string.video_recording_stopped));
+ } catch (RuntimeException e) {
+ Log.e(TAG, "stop fail", e);
+ if (mVideoFilename != null) deleteVideoFile(mVideoFilename);
+ fail = true;
+ }
+ mMediaRecorderRecording = false;
+ mOrientationManager.unlockOrientation();
+
+ // If the activity is paused, this means activity is interrupted
+ // during recording. Release the camera as soon as possible because
+ // face unlock or other applications may need to use the camera.
+ // However, if the effects are active, then we can only release the
+ // camera and cannot release the effects recorder since that will
+ // stop the graph. It is possible to separate out the Camera release
+ // part and the effects release part. However, the effects recorder
+ // does hold on to the camera, hence, it needs to be "disconnected"
+ // from the camera in the closeCamera call.
+ if (mPaused) {
+ // Closing only the camera part if effects active. Effects will
+ // be closed in the callback from effects.
+ boolean closeEffects = !effectsActive();
+ closeCamera(closeEffects);
+ }
+
+ mUI.showRecordingUI(false, mParameters.isZoomSupported());
+ if (!mIsVideoCaptureIntent) {
+ mUI.enableCameraControls(true);
+ }
+ // The orientation was fixed during video recording. Now make it
+ // reflect the device orientation as video recording is stopped.
+ mUI.setOrientationIndicator(0, true);
+ keepScreenOnAwhile();
+ if (shouldAddToMediaStoreNow) {
+ if (addVideoToMediaStore()) fail = true;
+ }
+ }
+ // always release media recorder if no effects running
+ if (!effectsActive()) {
+ releaseMediaRecorder();
+ if (!mPaused) {
+ mCameraDevice.lock();
+ mCameraDevice.waitDone();
+ if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) {
+ stopPreview();
+ mUI.hideSurfaceView();
+ // Switch back to use SurfaceTexture for preview.
+ startPreview();
+ }
+ }
+ }
+ // Update the parameters here because the parameters might have been altered
+ // by MediaRecorder.
+ if (!mPaused) mParameters = mCameraDevice.getParameters();
+ UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA,
+ fail ? UsageStatistics.ACTION_CAPTURE_FAIL :
+ UsageStatistics.ACTION_CAPTURE_DONE, "Video",
+ SystemClock.uptimeMillis() - mRecordingStartTime);
+ return fail;
+ }
+
+ private void resetScreenOn() {
+ mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ private void keepScreenOnAwhile() {
+ mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY);
+ }
+
+ private void keepScreenOn() {
+ mHandler.removeMessages(CLEAR_SCREEN_DELAY);
+ mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ private static String millisecondToTimeString(long milliSeconds, boolean displayCentiSeconds) {
+ long seconds = milliSeconds / 1000; // round down to compute seconds
+ long minutes = seconds / 60;
+ long hours = minutes / 60;
+ long remainderMinutes = minutes - (hours * 60);
+ long remainderSeconds = seconds - (minutes * 60);
+
+ StringBuilder timeStringBuilder = new StringBuilder();
+
+ // Hours
+ if (hours > 0) {
+ if (hours < 10) {
+ timeStringBuilder.append('0');
+ }
+ timeStringBuilder.append(hours);
+
+ timeStringBuilder.append(':');
+ }
+
+ // Minutes
+ if (remainderMinutes < 10) {
+ timeStringBuilder.append('0');
+ }
+ timeStringBuilder.append(remainderMinutes);
+ timeStringBuilder.append(':');
+
+ // Seconds
+ if (remainderSeconds < 10) {
+ timeStringBuilder.append('0');
+ }
+ timeStringBuilder.append(remainderSeconds);
+
+ // Centi seconds
+ if (displayCentiSeconds) {
+ timeStringBuilder.append('.');
+ long remainderCentiSeconds = (milliSeconds - seconds * 1000) / 10;
+ if (remainderCentiSeconds < 10) {
+ timeStringBuilder.append('0');
+ }
+ timeStringBuilder.append(remainderCentiSeconds);
+ }
+
+ return timeStringBuilder.toString();
+ }
+
+ private long getTimeLapseVideoLength(long deltaMs) {
+ // For better approximation calculate fractional number of frames captured.
+ // This will update the video time at a higher resolution.
+ double numberOfFrames = (double) deltaMs / mTimeBetweenTimeLapseFrameCaptureMs;
+ return (long) (numberOfFrames / mProfile.videoFrameRate * 1000);
+ }
+
+ private void updateRecordingTime() {
+ if (!mMediaRecorderRecording) {
+ return;
+ }
+ long now = SystemClock.uptimeMillis();
+ long delta = now - mRecordingStartTime;
+
+ // Starting a minute before reaching the max duration
+ // limit, we'll countdown the remaining time instead.
+ boolean countdownRemainingTime = (mMaxVideoDurationInMs != 0
+ && delta >= mMaxVideoDurationInMs - 60000);
+
+ long deltaAdjusted = delta;
+ if (countdownRemainingTime) {
+ deltaAdjusted = Math.max(0, mMaxVideoDurationInMs - deltaAdjusted) + 999;
+ }
+ String text;
+
+ long targetNextUpdateDelay;
+ if (!mCaptureTimeLapse) {
+ text = millisecondToTimeString(deltaAdjusted, false);
+ targetNextUpdateDelay = 1000;
+ } else {
+ // The length of time lapse video is different from the length
+ // of the actual wall clock time elapsed. Display the video length
+ // only in format hh:mm:ss.dd, where dd are the centi seconds.
+ text = millisecondToTimeString(getTimeLapseVideoLength(delta), true);
+ targetNextUpdateDelay = mTimeBetweenTimeLapseFrameCaptureMs;
+ }
+
+ mUI.setRecordingTime(text);
+
+ if (mRecordingTimeCountsDown != countdownRemainingTime) {
+ // Avoid setting the color on every update, do it only
+ // when it needs changing.
+ mRecordingTimeCountsDown = countdownRemainingTime;
+
+ int color = mActivity.getResources().getColor(countdownRemainingTime
+ ? R.color.recording_time_remaining_text
+ : R.color.recording_time_elapsed_text);
+
+ mUI.setRecordingTimeTextColor(color);
+ }
+
+ long actualNextUpdateDelay = targetNextUpdateDelay - (delta % targetNextUpdateDelay);
+ mHandler.sendEmptyMessageDelayed(
+ UPDATE_RECORD_TIME, actualNextUpdateDelay);
+ }
+
+ private static boolean isSupported(String value, List<String> supported) {
+ return supported == null ? false : supported.indexOf(value) >= 0;
+ }
+
+ @SuppressWarnings("deprecation")
+ private void setCameraParameters() {
+ mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
+ mParameters.setPreviewFrameRate(mProfile.videoFrameRate);
+
+ // Set flash mode.
+ String flashMode;
+ if (mUI.isVisible()) {
+ flashMode = mPreferences.getString(
+ CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE,
+ mActivity.getString(R.string.pref_camera_video_flashmode_default));
+ } else {
+ flashMode = Parameters.FLASH_MODE_OFF;
+ }
+ List<String> supportedFlash = mParameters.getSupportedFlashModes();
+ if (isSupported(flashMode, supportedFlash)) {
+ mParameters.setFlashMode(flashMode);
+ } else {
+ flashMode = mParameters.getFlashMode();
+ if (flashMode == null) {
+ flashMode = mActivity.getString(
+ R.string.pref_camera_flashmode_no_flash);
+ }
+ }
+
+ // Set white balance parameter.
+ String whiteBalance = mPreferences.getString(
+ CameraSettings.KEY_WHITE_BALANCE,
+ mActivity.getString(R.string.pref_camera_whitebalance_default));
+ if (isSupported(whiteBalance,
+ mParameters.getSupportedWhiteBalance())) {
+ mParameters.setWhiteBalance(whiteBalance);
+ } else {
+ whiteBalance = mParameters.getWhiteBalance();
+ if (whiteBalance == null) {
+ whiteBalance = Parameters.WHITE_BALANCE_AUTO;
+ }
+ }
+
+ // Set zoom.
+ if (mParameters.isZoomSupported()) {
+ mParameters.setZoom(mZoomValue);
+ }
+
+ // Set continuous autofocus.
+ List<String> supportedFocus = mParameters.getSupportedFocusModes();
+ if (isSupported(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO, supportedFocus)) {
+ mParameters.setFocusMode(Parameters.FOCUS_MODE_CONTINUOUS_VIDEO);
+ }
+
+ mParameters.set(Util.RECORDING_HINT, Util.TRUE);
+
+ // Enable video stabilization. Convenience methods not available in API
+ // level <= 14
+ String vstabSupported = mParameters.get("video-stabilization-supported");
+ if ("true".equals(vstabSupported)) {
+ mParameters.set("video-stabilization", "true");
+ }
+
+ // Set picture size.
+ // The logic here is different from the logic in still-mode camera.
+ // There we determine the preview size based on the picture size, but
+ // here we determine the picture size based on the preview size.
+ List<Size> supported = mParameters.getSupportedPictureSizes();
+ Size optimalSize = Util.getOptimalVideoSnapshotPictureSize(supported,
+ (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
+ Size original = mParameters.getPictureSize();
+ if (!original.equals(optimalSize)) {
+ mParameters.setPictureSize(optimalSize.width, optimalSize.height);
+ }
+ Log.v(TAG, "Video snapshot size is " + optimalSize.width + "x" +
+ optimalSize.height);
+
+ // Set JPEG quality.
+ int jpegQuality = CameraProfile.getJpegEncodingQualityParameter(mCameraId,
+ CameraProfile.QUALITY_HIGH);
+ mParameters.setJpegQuality(jpegQuality);
+
+ mCameraDevice.setParameters(mParameters);
+ // Keep preview size up to date.
+ mParameters = mCameraDevice.getParameters();
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ switch (requestCode) {
+ case REQUEST_EFFECT_BACKDROPPER:
+ if (resultCode == Activity.RESULT_OK) {
+ // onActivityResult() runs before onResume(), so this parameter will be
+ // seen by startPreview from onResume()
+ mEffectUriFromGallery = data.getData().toString();
+ Log.v(TAG, "Received URI from gallery: " + mEffectUriFromGallery);
+ mResetEffect = false;
+ } else {
+ mEffectUriFromGallery = null;
+ Log.w(TAG, "No URI from gallery");
+ mResetEffect = true;
+ }
+ break;
+ }
+ }
+
+ @Override
+ public void onEffectsUpdate(int effectId, int effectMsg) {
+ Log.v(TAG, "onEffectsUpdate. Effect Message = " + effectMsg);
+ if (effectMsg == EffectsRecorder.EFFECT_MSG_EFFECTS_STOPPED) {
+ // Effects have shut down. Hide learning message if any,
+ // and restart regular preview.
+ checkQualityAndStartPreview();
+ } else if (effectMsg == EffectsRecorder.EFFECT_MSG_RECORDING_DONE) {
+ // This follows the codepath from onStopVideoRecording.
+ if (mEffectsDisplayResult && !addVideoToMediaStore()) {
+ if (mIsVideoCaptureIntent) {
+ if (mQuickCapture) {
+ doReturnToCaller(true);
+ } else {
+ showCaptureResult();
+ }
+ }
+ }
+ mEffectsDisplayResult = false;
+ // In onPause, these were not called if the effects were active. We
+ // had to wait till the effects recording is complete to do this.
+ if (mPaused) {
+ closeVideoFileDescriptor();
+ clearVideoNamer();
+ }
+ } else if (effectMsg == EffectsRecorder.EFFECT_MSG_PREVIEW_RUNNING) {
+ // Enable the shutter button once the preview is complete.
+ 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.
+ if (mPaused) {
+ Log.v(TAG, "OnEffectsUpdate: closing effects if activity paused");
+ closeEffects();
+ }
+ }
+
+ public void onCancelBgTraining(View v) {
+ // Write default effect out to shared prefs
+ writeDefaultEffectToPrefs();
+ // Tell VideoCamer to re-init based on new shared pref values.
+ onSharedPreferenceChanged();
+ }
+
+ @Override
+ public synchronized void onEffectsError(Exception exception, String fileName) {
+ // TODO: Eventually we may want to show the user an error dialog, and then restart the
+ // camera and encoder gracefully. For now, we just delete the file and bail out.
+ if (fileName != null && new File(fileName).exists()) {
+ deleteVideoFile(fileName);
+ }
+ try {
+ if (Class.forName("android.filterpacks.videosink.MediaRecorderStopException")
+ .isInstance(exception)) {
+ Log.w(TAG, "Problem recoding video file. Removing incomplete file.");
+ return;
+ }
+ } catch (ClassNotFoundException ex) {
+ Log.w(TAG, ex);
+ }
+ throw new RuntimeException("Error during recording!", exception);
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ Log.v(TAG, "onConfigurationChanged");
+ setDisplayOrientation();
+ }
+
+ @Override
+ public void onOverriddenPreferencesClicked() {
+ }
+
+ @Override
+ // TODO: Delete this after old camera code is removed
+ public void onRestorePreferencesClicked() {
+ }
+
+ private boolean effectsActive() {
+ return (mEffectType != EffectsRecorder.EFFECT_NONE);
+ }
+
+ @Override
+ public void onSharedPreferenceChanged() {
+ // ignore the events after "onPause()" or preview has not started yet
+ if (mPaused) return;
+ synchronized (mPreferences) {
+ // If mCameraDevice is not ready then we can set the parameter in
+ // startPreview().
+ if (mCameraDevice == null) return;
+
+ boolean recordLocation = RecordLocationPreference.get(
+ mPreferences, mContentResolver);
+ mLocationManager.recordLocation(recordLocation);
+
+ // Check if the current effects selection has changed
+ if (updateEffectSelection()) return;
+
+ readVideoPreferences();
+ mUI.showTimeLapseUI(mCaptureTimeLapse);
+ // We need to restart the preview if preview size is changed.
+ Size size = mParameters.getPreviewSize();
+ if (size.width != mDesiredPreviewWidth
+ || size.height != mDesiredPreviewHeight) {
+ if (!effectsActive()) {
+ stopPreview();
+ } else {
+ mEffectsRecorder.release();
+ mEffectsRecorder = null;
+ }
+ resizeForPreviewAspectRatio();
+ startPreview(); // Parameters will be set in startPreview().
+ } else {
+ setCameraParameters();
+ }
+ mUI.updateOnScreenIndicators(mParameters);
+ }
+ }
+
+ protected void setCameraId(int cameraId) {
+ ListPreference pref = mPreferenceGroup.findPreference(CameraSettings.KEY_CAMERA_ID);
+ pref.setValue("" + cameraId);
+ }
+
+ private void switchCamera() {
+ if (mPaused) return;
+
+ Log.d(TAG, "Start to switch camera.");
+ mCameraId = mPendingSwitchCameraId;
+ mPendingSwitchCameraId = -1;
+ setCameraId(mCameraId);
+
+ closeCamera();
+ mUI.collapseCameraControls();
+ // Restart the camera and initialize the UI. From onCreate.
+ mPreferences.setLocalId(mActivity, mCameraId);
+ CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
+ openCamera();
+ readVideoPreferences();
+ startPreview();
+ initializeVideoSnapshot();
+ resizeForPreviewAspectRatio();
+ initializeVideoControl();
+
+ // From onResume
+ mUI.initializeZoom(mParameters);
+ mUI.setOrientationIndicator(0, false);
+
+ // Start switch camera animation. Post a message because
+ // onFrameAvailable from the old camera may already exist.
+ mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION);
+ mUI.updateOnScreenIndicators(mParameters);
+ }
+
+ // Preview texture has been copied. Now camera can be released and the
+ // animation can be started.
+ @Override
+ public void onPreviewTextureCopied() {
+ mHandler.sendEmptyMessage(SWITCH_CAMERA);
+ }
+
+ @Override
+ public void onCaptureTextureCopied() {
+ }
+
+ private boolean updateEffectSelection() {
+ int previousEffectType = mEffectType;
+ Object previousEffectParameter = mEffectParameter;
+ mEffectType = CameraSettings.readEffectType(mPreferences);
+ mEffectParameter = CameraSettings.readEffectParameter(mPreferences);
+
+ if (mEffectType == previousEffectType) {
+ if (mEffectType == EffectsRecorder.EFFECT_NONE) return false;
+ if (mEffectParameter.equals(previousEffectParameter)) return false;
+ }
+ Log.v(TAG, "New effect selection: " + mPreferences.getString(
+ CameraSettings.KEY_VIDEO_EFFECT, "none"));
+
+ if (mEffectType == EffectsRecorder.EFFECT_NONE) {
+ // Stop effects and return to normal preview
+ mEffectsRecorder.stopPreview();
+ mPreviewing = false;
+ return true;
+ }
+ if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER &&
+ ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) {
+ // Request video from gallery to use for background
+ Intent i = new Intent(Intent.ACTION_PICK);
+ i.setDataAndType(Video.Media.EXTERNAL_CONTENT_URI,
+ "video/*");
+ i.putExtra(Intent.EXTRA_LOCAL_ONLY, true);
+ mActivity.startActivityForResult(i, REQUEST_EFFECT_BACKDROPPER);
+ return true;
+ }
+ if (previousEffectType == EffectsRecorder.EFFECT_NONE) {
+ // Stop regular preview and start effects.
+ stopPreview();
+ checkQualityAndStartPreview();
+ } else {
+ // Switch currently running effect
+ mEffectsRecorder.setEffect(mEffectType, mEffectParameter);
+ }
+ return true;
+ }
+
+ // Verifies that the current preview view size is correct before starting
+ // preview. If not, resets the surface texture and resizes the view.
+ private void checkQualityAndStartPreview() {
+ readVideoPreferences();
+ mUI.showTimeLapseUI(mCaptureTimeLapse);
+ Size size = mParameters.getPreviewSize();
+ if (size.width != mDesiredPreviewWidth
+ || size.height != mDesiredPreviewHeight) {
+ resizeForPreviewAspectRatio();
+ }
+ // Start up preview again
+ startPreview();
+ }
+
+ @Override
+ public boolean dispatchTouchEvent(MotionEvent m) {
+ if (mSwitchingCamera) return true;
+ return mUI.dispatchTouchEvent(m);
+ }
+
+ private void initializeVideoSnapshot() {
+ if (mParameters == null) return;
+ if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
+ // Show the tap to focus toast if this is the first start.
+ if (mPreferences.getBoolean(
+ CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) {
+ // Delay the toast for one second to wait for orientation.
+ mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000);
+ }
+ }
+ }
+
+ void showVideoSnapshotUI(boolean enabled) {
+ if (mParameters == null) return;
+ if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) {
+ if (enabled) {
+ // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation);
+ } else {
+ mUI.showPreviewBorder(enabled);
+ }
+ mUI.enableShutter(!enabled);
+ }
+ }
+
+ @Override
+ public void updateCameraAppView() {
+ if (!mPreviewing || mParameters.getFlashMode() == null) return;
+
+ // When going to and back from gallery, we need to turn off/on the flash.
+ if (!mUI.isVisible()) {
+ if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) {
+ mRestoreFlash = false;
+ return;
+ }
+ mRestoreFlash = true;
+ setCameraParameters();
+ } else if (mRestoreFlash) {
+ mRestoreFlash = false;
+ setCameraParameters();
+ }
+ }
+
+ @Override
+ public void onFullScreenChanged(boolean full) {
+ mUI.onFullScreenChanged(full);
+ }
+
+ private final class JpegPictureCallback implements PictureCallback {
+ Location mLocation;
+
+ public JpegPictureCallback(Location loc) {
+ mLocation = loc;
+ }
+
+ @Override
+ public void onPictureTaken(byte [] jpegData, android.hardware.Camera camera) {
+ Log.v(TAG, "onPictureTaken");
+ mSnapshotInProgress = false;
+ showVideoSnapshotUI(false);
+ storeImage(jpegData, mLocation);
+ }
+ }
+
+ private void storeImage(final byte[] data, Location loc) {
+ long dateTaken = System.currentTimeMillis();
+ String title = Util.createJpegName(dateTaken);
+ ExifInterface exif = Exif.getExif(data);
+ int orientation = Exif.getOrientation(exif);
+ Size s = mParameters.getPictureSize();
+ mActivity.getMediaSaveService().addImage(
+ data, title, dateTaken, loc, s.width, s.height, orientation,
+ exif, mOnMediaSavedListener, mContentResolver);
+ }
+
+ private boolean resetEffect() {
+ if (mResetEffect) {
+ String value = mPreferences.getString(CameraSettings.KEY_VIDEO_EFFECT,
+ mPrefVideoEffectDefault);
+ if (!mPrefVideoEffectDefault.equals(value)) {
+ writeDefaultEffectToPrefs();
+ return true;
+ }
+ }
+ mResetEffect = true;
+ return false;
+ }
+
+ private String convertOutputFormatToMimeType(int outputFileFormat) {
+ if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
+ return "video/mp4";
+ }
+ return "video/3gpp";
+ }
+
+ private String convertOutputFormatToFileExt(int outputFileFormat) {
+ if (outputFileFormat == MediaRecorder.OutputFormat.MPEG_4) {
+ return ".mp4";
+ }
+ return ".3gp";
+ }
+
+ private void closeVideoFileDescriptor() {
+ if (mVideoFileDescriptor != null) {
+ try {
+ mVideoFileDescriptor.close();
+ } catch (IOException e) {
+ Log.e(TAG, "Fail to close fd", e);
+ }
+ mVideoFileDescriptor = null;
+ }
+ }
+
+ private void showTapToSnapshotToast() {
+ new RotateTextToast(mActivity, R.string.video_snapshot_hint, 0)
+ .show();
+ // Clear the preference.
+ Editor editor = mPreferences.edit();
+ editor.putBoolean(CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, false);
+ editor.apply();
+ }
+
+ private void clearVideoNamer() {
+ if (mVideoNamer != null) {
+ mVideoNamer.finish();
+ mVideoNamer = null;
+ }
+ }
+
+ private static class VideoNamer extends Thread {
+ private boolean mRequestPending;
+ private ContentResolver mResolver;
+ private ContentValues mValues;
+ private boolean mStop;
+ private Uri mUri;
+
+ // Runs in main thread
+ public VideoNamer() {
+ start();
+ }
+
+ // Runs in main thread
+ public synchronized void prepareUri(
+ ContentResolver resolver, ContentValues values) {
+ mRequestPending = true;
+ mResolver = resolver;
+ mValues = new ContentValues(values);
+ notifyAll();
+ }
+
+ // Runs in main thread
+ public synchronized Uri getUri() {
+ // wait until the request is done.
+ while (mRequestPending) {
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ // ignore.
+ }
+ }
+ Uri uri = mUri;
+ mUri = null;
+ return uri;
+ }
+
+ // Runs in namer thread
+ @Override
+ public synchronized void run() {
+ while (true) {
+ if (mStop) break;
+ if (!mRequestPending) {
+ try {
+ wait();
+ } catch (InterruptedException ex) {
+ // ignore.
+ }
+ continue;
+ }
+ cleanOldUri();
+ generateUri();
+ mRequestPending = false;
+ notifyAll();
+ }
+ cleanOldUri();
+ }
+
+ // Runs in main thread
+ public synchronized void finish() {
+ mStop = true;
+ notifyAll();
+ }
+
+ // Runs in namer thread
+ private void generateUri() {
+ Uri videoTable = Uri.parse("content://media/external/video/media");
+ mUri = mResolver.insert(videoTable, mValues);
+ }
+
+ // Runs in namer thread
+ private void cleanOldUri() {
+ if (mUri == null) return;
+ mResolver.delete(mUri, null, null);
+ mUri = null;
+ }
+ }
+
+ @Override
+ public boolean updateStorageHintOnResume() {
+ return true;
+ }
+
+ // required by OnPreferenceChangedListener
+ @Override
+ public void onCameraPickerClicked(int cameraId) {
+ if (mPaused || mPendingSwitchCameraId != -1) return;
+
+ mPendingSwitchCameraId = cameraId;
+ Log.d(TAG, "Start to copy texture.");
+ // We need to keep a preview frame for the animation before
+ // releasing the camera. This will trigger onPreviewTextureCopied.
+ // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture();
+ // Disable all camera controls.
+ mSwitchingCamera = true;
+
+ }
+
+ @Override
+ public boolean needsSwitcher() {
+ return !mIsVideoCaptureIntent;
+ }
+
+ @Override
+ public boolean needsPieMenu() {
+ return true;
+ }
+
+ @Override
+ public void onShowSwitcherPopup() {
+ mUI.onShowSwitcherPopup();
+ }
+
+ @Override
+ public void onMediaSaveServiceConnected(MediaSaveService s) {
+ // do nothing.
+ }
+}
diff --git a/src/com/android/camera/NewVideoUI.java b/src/com/android/camera/NewVideoUI.java
new file mode 100644
index 000000000..a14dae3d3
--- /dev/null
+++ b/src/com/android/camera/NewVideoUI.java
@@ -0,0 +1,724 @@
+/*
+ * 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.graphics.Matrix;
+import android.graphics.SurfaceTexture;
+import android.hardware.Camera.Parameters;
+import android.hardware.Camera.Size;
+import android.os.Handler;
+import android.os.Message;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.SurfaceHolder;
+import android.view.SurfaceView;
+import android.view.TextureView;
+import android.view.TextureView.SurfaceTextureListener;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnLayoutChangeListener;
+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.CameraSwitcher;
+import com.android.camera.ui.PieRenderer;
+import com.android.camera.ui.RenderOverlay;
+import com.android.camera.ui.RotateLayout;
+import com.android.camera.ui.ZoomRenderer;
+import com.android.camera.ui.CameraSwitcher.CameraSwitchListener;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.ApiHelper;
+
+import java.util.List;
+
+public class NewVideoUI implements PieRenderer.PieListener,
+ NewPreviewGestures.SingleTapListener,
+ NewPreviewGestures.SwipeListener, SurfaceTextureListener,
+ SurfaceHolder.Callback {
+ private final static String TAG = "CAM_VideoUI";
+ private static final int UPDATE_TRANSFORM_MATRIX = 1;
+ // module fields
+ private NewCameraActivity mActivity;
+ private View mRootView;
+ private TextureView mTextureView;
+ // 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 CameraSwitcher mSwitcher;
+ private TextView mRecordingTimeView;
+ private LinearLayout mLabelsLinearLayout;
+ private View mTimeLapseLabel;
+ private RenderOverlay mRenderOverlay;
+ private PieRenderer mPieRenderer;
+ private NewVideoMenu mVideoMenu;
+ private View mCameraControls;
+ private AbstractSettingPopup mPopup;
+ private ZoomRenderer mZoomRenderer;
+ private NewPreviewGestures mGestures;
+ private View mMenuButton;
+ private View mBlocker;
+ private View mOnScreenIndicators;
+ private ImageView mFlashIndicator;
+ private RotateLayout mRecordingTimeRect;
+ private final Object mLock = new Object();
+ private SurfaceTexture mSurfaceTexture;
+ private VideoController mController;
+ private int mZoomMax;
+ private List<Integer> mZoomRatios;
+
+ private SurfaceView mSurfaceView = null;
+ private int mPreviewWidth = 0;
+ private int mPreviewHeight = 0;
+ private float mSurfaceTextureUncroppedWidth;
+ private float mSurfaceTextureUncroppedHeight;
+ private float mAspectRatio = 4f / 3f;
+ private Matrix mMatrix = null;
+ private final Handler mHandler = new Handler() {
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case UPDATE_TRANSFORM_MATRIX:
+ setTransformMatrix(mPreviewWidth, mPreviewHeight);
+ break;
+ default:
+ break;
+ }
+ }
+ };
+ private OnLayoutChangeListener mLayoutListener = new OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right,
+ int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ int width = right - left;
+ int height = bottom - top;
+ // Full-screen screennail
+ int w = width;
+ int h = height;
+ if (Util.getDisplayRotation(mActivity) % 180 != 0) {
+ w = height;
+ h = width;
+ }
+ if (mPreviewWidth != width || mPreviewHeight != height) {
+ mPreviewWidth = width;
+ mPreviewHeight = height;
+ onScreenSizeChanged(width, height, w, h);
+ }
+ }
+ };
+
+ public NewVideoUI(NewCameraActivity activity, VideoController controller, View parent) {
+ mActivity = activity;
+ mController = controller;
+ mRootView = parent;
+ mActivity.getLayoutInflater().inflate(R.layout.new_video_module, (ViewGroup) mRootView, true);
+ mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content);
+ mTextureView.setSurfaceTextureListener(this);
+ mRootView.addOnLayoutChangeListener(mLayoutListener);
+ mShutterButton = (ShutterButton) mRootView.findViewById(R.id.shutter_button);
+ mSwitcher = (CameraSwitcher) mRootView.findViewById(R.id.camera_switcher);
+ mSwitcher.setCurrentIndex(1);
+ mSwitcher.setSwitchListener((CameraSwitchListener) mActivity);
+ initializeMiscControls();
+ initializeControlByIntent();
+ initializeOverlay();
+ }
+
+
+ public void initializeSurfaceView() {
+ mSurfaceView = new SurfaceView(mActivity);
+ ((ViewGroup) mRootView).addView(mSurfaceView, 0);
+ mSurfaceView.getHolder().addCallback(this);
+ }
+
+ private void initializeControlByIntent() {
+ mBlocker = mActivity.findViewById(R.id.blocker);
+ mMenuButton = mActivity.findViewById(R.id.menu);
+ mMenuButton.setOnClickListener(new OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (mPieRenderer != null) {
+ mPieRenderer.showInCenter();
+ }
+ }
+ });
+
+ mCameraControls = mActivity.findViewById(R.id.camera_controls);
+ mOnScreenIndicators = mActivity.findViewById(R.id.on_screen_indicators);
+ mFlashIndicator = (ImageView) mActivity.findViewById(R.id.menu_flash_indicator);
+ if (mController.isVideoCaptureIntent()) {
+ hideSwitcher();
+ mActivity.getLayoutInflater().inflate(R.layout.review_module_control, (ViewGroup) mCameraControls);
+ // 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 void setPreviewSize(int width, int height) {
+ if (width == 0 || height == 0) {
+ Log.w(TAG, "Preview size should not be 0.");
+ return;
+ }
+ if (width > height) {
+ mAspectRatio = (float) width / height;
+ } else {
+ mAspectRatio = (float) height / width;
+ }
+ mHandler.sendEmptyMessage(UPDATE_TRANSFORM_MATRIX);
+ }
+
+ public int getPreviewWidth() {
+ return mPreviewWidth;
+ }
+
+ public int getPreviewHeight() {
+ return mPreviewHeight;
+ }
+
+ public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) {
+ setTransformMatrix(width, height);
+ }
+
+ private void setTransformMatrix(int width, int height) {
+ mMatrix = mTextureView.getTransform(mMatrix);
+ int orientation = Util.getDisplayRotation(mActivity);
+ float scaleX = 1f, scaleY = 1f;
+ float scaledTextureWidth, scaledTextureHeight;
+ if (width > height) {
+ scaledTextureWidth = Math.max(width,
+ (int) (height * mAspectRatio));
+ scaledTextureHeight = Math.max(height,
+ (int)(width / mAspectRatio));
+ } else {
+ scaledTextureWidth = Math.max(width,
+ (int) (height / mAspectRatio));
+ scaledTextureHeight = Math.max(height,
+ (int) (width * mAspectRatio));
+ }
+
+ if (mSurfaceTextureUncroppedWidth != scaledTextureWidth ||
+ mSurfaceTextureUncroppedHeight != scaledTextureHeight) {
+ mSurfaceTextureUncroppedWidth = scaledTextureWidth;
+ mSurfaceTextureUncroppedHeight = scaledTextureHeight;
+ }
+ scaleX = scaledTextureWidth / width;
+ scaleY = scaledTextureHeight / height;
+ mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2);
+ mTextureView.setTransform(mMatrix);
+
+ if (mSurfaceView != null && mSurfaceView.getVisibility() == View.VISIBLE) {
+ LayoutParams lp = (LayoutParams) mSurfaceView.getLayoutParams();
+ lp.width = (int) mSurfaceTextureUncroppedWidth;
+ lp.height = (int) mSurfaceTextureUncroppedHeight;
+ lp.gravity = Gravity.CENTER;
+ mSurfaceView.requestLayout();
+ }
+ }
+
+ public void hideUI() {
+ mCameraControls.setVisibility(View.INVISIBLE);
+ hideSwitcher();
+ mShutterButton.setVisibility(View.GONE);
+ }
+
+ public void showUI() {
+ mCameraControls.setVisibility(View.VISIBLE);
+ showSwitcher();
+ mShutterButton.setVisibility(View.VISIBLE);
+ }
+
+ public void hideSwitcher() {
+ mSwitcher.closePopup();
+ mSwitcher.setVisibility(View.INVISIBLE);
+ }
+
+ public void showSwitcher() {
+ mSwitcher.setVisibility(View.VISIBLE);
+ }
+
+ 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 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 mSurfaceView.getHolder();
+ }
+
+ public void hideSurfaceView() {
+ mSurfaceView.setVisibility(View.GONE);
+ mTextureView.setVisibility(View.VISIBLE);
+ setTransformMatrix(mPreviewWidth, mPreviewHeight);
+ }
+
+ public void showSurfaceView() {
+ mSurfaceView.setVisibility(View.VISIBLE);
+ mTextureView.setVisibility(View.GONE);
+ setTransformMatrix(mPreviewWidth, mPreviewHeight);
+ }
+
+ private void initializeOverlay() {
+ mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay);
+ if (mPieRenderer == null) {
+ mPieRenderer = new PieRenderer(mActivity);
+ mVideoMenu = new NewVideoMenu(mActivity, this, mPieRenderer);
+ mPieRenderer.setPieListener(this);
+ }
+ mRenderOverlay.addRenderer(mPieRenderer);
+ if (mZoomRenderer == null) {
+ mZoomRenderer = new ZoomRenderer(mActivity);
+ }
+ mRenderOverlay.addRenderer(mZoomRenderer);
+ if (mGestures == null) {
+ mGestures = new NewPreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, this);
+ }
+ mGestures.setRenderOverlay(mRenderOverlay);
+ mGestures.clearTouchReceivers();
+ mGestures.addTouchReceiver(mMenuButton);
+ 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() {
+ mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image);
+ 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);
+ }
+ }
+
+ private void openMenu() {
+ if (mPieRenderer != null) {
+ mPieRenderer.showInCenter();
+ }
+ }
+
+ public void showPopup(AbstractSettingPopup popup) {
+ 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) {
+ 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;
+ }
+
+ // disable preview gestures after shutter is pressed
+ public void setShutterPressed(boolean pressed) {
+ if (mGestures == null) return;
+ mGestures.setEnabled(!pressed);
+ }
+
+ public void enableShutter(boolean enable) {
+ if (mShutterButton != null) {
+ mShutterButton.setEnabled(enable);
+ }
+ }
+
+ // PieListener
+ @Override
+ public void onPieOpened(int centerX, int centerY) {
+ // TODO: mActivity.cancelActivityTouchHandling();
+ // mActivity.setSwipingEnabled(false);
+ }
+
+ @Override
+ public void onPieClosed() {
+ // TODO: mActivity.setSwipingEnabled(true);
+ }
+
+ public void showPreviewBorder(boolean enable) {
+ // TODO: 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);
+ }
+
+ public void showRecordingUI(boolean recording, boolean zoomSupported) {
+ mMenuButton.setVisibility(recording ? View.GONE : View.VISIBLE);
+ mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE);
+ if (recording) {
+ mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording);
+ 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);
+ 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);
+ mMenuButton.setVisibility(View.GONE);
+ mOnScreenIndicators.setVisibility(View.GONE);
+ }
+
+ public void hideReviewUI() {
+ mReviewImage.setVisibility(View.GONE);
+ mShutterButton.setEnabled(true);
+ mMenuButton.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 (mMenuButton != null) {
+ mMenuButton.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 View getShutterButton() {
+ return mShutterButton;
+ }
+
+ // Gestures and touch events
+
+ public boolean dispatchTouchEvent(MotionEvent m) {
+ if (mPopup != null || mSwitcher.showsPopup()) {
+ boolean handled = mRootView.dispatchTouchEvent(m);
+ if (!handled && mPopup != null) {
+ dismissPopup(false);
+ }
+ return handled;
+ } else if (mGestures != null && mRenderOverlay != null) {
+ if (mGestures.dispatchTouch(m)) {
+ return true;
+ } else {
+ return mRootView.dispatchTouchEvent(m);
+ }
+ }
+ return true;
+ }
+ public void setRecordingTime(String text) {
+ mRecordingTimeView.setText(text);
+ }
+
+ public void setRecordingTimeTextColor(int color) {
+ mRecordingTimeView.setTextColor(color);
+ }
+
+ public boolean isVisible() {
+ return mTextureView.getVisibility() == View.VISIBLE;
+ }
+
+ 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() {
+ }
+ }
+
+ @Override
+ public void onSwipe(int direction) {
+ if (direction == PreviewGestures.DIR_UP) {
+ openMenu();
+ }
+ }
+
+ public SurfaceTexture getSurfaceTexture() {
+ synchronized (mLock) {
+ if (mSurfaceTexture == null) {
+ try {
+ mLock.wait();
+ } catch (InterruptedException e) {
+ Log.w(TAG, "Unexpected interruption when waiting to get surface texture");
+ }
+ }
+ }
+ return mSurfaceTexture;
+ }
+
+ // SurfaceTexture callbacks
+ @Override
+ public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) {
+ synchronized (mLock) {
+ mSurfaceTexture = surface;
+ mLock.notifyAll();
+ }
+ }
+
+ @Override
+ public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) {
+ mSurfaceTexture = null;
+ mController.stopPreview();
+ Log.d(TAG, "surfaceTexture is destroyed");
+ return true;
+ }
+
+ @Override
+ public void onSurfaceTextureSizeChanged(SurfaceTexture surface, int width, int height) {
+ }
+
+ @Override
+ public void onSurfaceTextureUpdated(SurfaceTexture surface) {
+ }
+
+ // SurfaceHolder callbacks
+ @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");
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ Log.v(TAG, "Surface destroyed");
+ mController.stopPreview();
+ }
+}
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
new file mode 100644
index 000000000..2bce1b4fd
--- /dev/null
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -0,0 +1,341 @@
+/*
+ * 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.data;
+
+import android.content.ContentResolver;
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.drawable.Drawable;
+import android.os.AsyncTask;
+import android.provider.MediaStore;
+import android.provider.MediaStore.Images;
+import android.provider.MediaStore.Video;
+import android.util.Log;
+import android.view.View;
+
+import com.android.camera.Storage;
+import com.android.camera.ui.FilmStripView;
+import com.android.camera.ui.FilmStripView.ImageData;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * A FilmStrip.DataProvider that provide data in the camera folder.
+ *
+ * The given view for camera preview won't be added until the preview info
+ * has been set by setCameraPreviewInfo(int, int).
+ */
+public class CameraDataAdapter implements FilmStripView.DataAdapter {
+ private static final String TAG = CameraDataAdapter.class.getSimpleName();
+
+ private static final int DEFAULT_DECODE_SIZE = 3000;
+ private static final String[] CAMERA_PATH = { Storage.DIRECTORY + "%" };
+
+ private List<LocalData> mImages;
+
+ private Listener mListener;
+ private View mCameraPreviewView;
+ private Drawable mPlaceHolder;
+
+ private int mSuggestedWidth = DEFAULT_DECODE_SIZE;
+ private int mSuggestedHeight = DEFAULT_DECODE_SIZE;
+
+ public CameraDataAdapter(Drawable placeHolder) {
+ mPlaceHolder = placeHolder;
+ }
+
+ public void setCameraPreviewInfo(View cameraPreview, int width, int height) {
+ mCameraPreviewView = cameraPreview;
+ addOrReplaceCameraData(buildCameraImageData(width, height));
+ }
+
+ public void requestLoad(ContentResolver resolver) {
+ QueryTask qtask = new QueryTask();
+ qtask.execute(resolver);
+ }
+
+ @Override
+ public int getTotalNumber() {
+ if (mImages == null) {
+ return 0;
+ }
+ return mImages.size();
+ }
+
+ @Override
+ public ImageData getImageData(int id) {
+ if (mImages == null || id >= mImages.size() || id < 0) {
+ return null;
+ }
+ return mImages.get(id);
+ }
+
+ @Override
+ public void suggestDecodeSize(int w, int h) {
+ if (w <= 0 || h <= 0) {
+ mSuggestedWidth = mSuggestedHeight = DEFAULT_DECODE_SIZE;
+ } else {
+ mSuggestedWidth = (w < DEFAULT_DECODE_SIZE ? w : DEFAULT_DECODE_SIZE);
+ mSuggestedHeight = (h < DEFAULT_DECODE_SIZE ? h : DEFAULT_DECODE_SIZE);
+ }
+ }
+
+ @Override
+ public View getView(Context c, int dataID) {
+ if (mImages == null) {
+ return null;
+ }
+ if (dataID >= mImages.size() || dataID < 0) {
+ return null;
+ }
+
+ return mImages.get(dataID).getView(
+ c, mSuggestedWidth, mSuggestedHeight, mPlaceHolder);
+ }
+
+ @Override
+ public void setListener(Listener listener) {
+ mListener = listener;
+ if (mImages != null) {
+ mListener.onDataLoaded();
+ }
+ }
+
+ public void removeData(int dataID) {
+ if (dataID >= mImages.size()) return;
+ LocalData d = mImages.remove(dataID);
+ mListener.onDataRemoved(dataID, d);
+ }
+
+ private LocalData buildCameraImageData(int width, int height) {
+ LocalData d = new CameraPreviewData(width, height);
+ return d;
+ }
+
+ private void addOrReplaceCameraData(LocalData data) {
+ if (mImages == null) {
+ mImages = new ArrayList<LocalData>();
+ }
+ if (mImages.size() == 0) {
+ // No data at all.
+ mImages.add(0, data);
+ if (mListener != null) {
+ mListener.onDataLoaded();
+ }
+ return;
+ }
+
+ LocalData first = mImages.get(0);
+ if (first.getType() == ImageData.TYPE_CAMERA_PREVIEW) {
+ // Replace the old camera data.
+ mImages.set(0, data);
+ if (mListener != null) {
+ mListener.onDataUpdated(new UpdateReporter() {
+ @Override
+ public boolean isDataRemoved(int id) {
+ return false;
+ }
+
+ @Override
+ public boolean isDataUpdated(int id) {
+ if (id == 0) {
+ return true;
+ }
+ return false;
+ }
+ });
+ }
+ } else {
+ // Add a new camera data.
+ mImages.add(0, data);
+ if (mListener != null) {
+ mListener.onDataLoaded();
+ }
+ }
+ }
+
+ private class QueryTask extends AsyncTask<ContentResolver, Void, List<LocalData>> {
+ @Override
+ protected List<LocalData> doInBackground(ContentResolver... resolver) {
+ List<LocalData> l = new ArrayList<LocalData>();
+ // Photos
+ Cursor c = resolver[0].query(
+ Images.Media.EXTERNAL_CONTENT_URI,
+ LocalData.Photo.QUERY_PROJECTION,
+ MediaStore.Images.Media.DATA + " like ? ", CAMERA_PATH,
+ LocalData.Photo.QUERY_ORDER);
+ if (c != null && c.moveToFirst()) {
+ // build up the list.
+ while (true) {
+ LocalData data = LocalData.Photo.buildFromCursor(c);
+ if (data != null) {
+ l.add(data);
+ } else {
+ Log.e(TAG, "Error loading data:"
+ + c.getString(LocalData.Photo.COL_DATA));
+ }
+ if (c.isLast()) {
+ break;
+ }
+ c.moveToNext();
+ }
+ }
+ if (c != null) {
+ c.close();
+ }
+
+ c = resolver[0].query(
+ Video.Media.EXTERNAL_CONTENT_URI,
+ LocalData.Video.QUERY_PROJECTION,
+ MediaStore.Video.Media.DATA + " like ? ", CAMERA_PATH,
+ LocalData.Video.QUERY_ORDER);
+ if (c != null && c.moveToFirst()) {
+ // build up the list.
+ c.moveToFirst();
+ while (true) {
+ LocalData data = LocalData.Video.buildFromCursor(c);
+ if (data != null) {
+ l.add(data);
+ Log.v(TAG, "video data added:" + data);
+ } else {
+ Log.e(TAG, "Error loading data:"
+ + c.getString(LocalData.Video.COL_DATA));
+ }
+ if (!c.isLast()) {
+ c.moveToNext();
+ } else {
+ break;
+ }
+ }
+ }
+ if (c != null) {
+ c.close();
+ }
+
+ if (l.size() == 0) return null;
+
+ Collections.sort(l, new LocalData.NewestFirstComparator());
+ return l;
+ }
+
+ @Override
+ protected void onPostExecute(List<LocalData> l) {
+ boolean changed = (l != mImages);
+ LocalData cameraData = null;
+ if (mImages != null && mImages.size() > 0) {
+ cameraData = mImages.get(0);
+ if (cameraData.getType() != ImageData.TYPE_CAMERA_PREVIEW) {
+ cameraData = null;
+ }
+ }
+
+ mImages = l;
+ if (cameraData != null) {
+ // camera view exists, so we make sure at least 1 data is in the list.
+ if (mImages == null) {
+ mImages = new ArrayList<LocalData>();
+ }
+ mImages.add(0, cameraData);
+ if (mListener != null) {
+ // Only the camera data is not changed, everything else is changed.
+ mListener.onDataUpdated(new UpdateReporter() {
+ @Override
+ public boolean isDataRemoved(int id) {
+ return false;
+ }
+
+ @Override
+ public boolean isDataUpdated(int id) {
+ if (id == 0) return false;
+ return true;
+ }
+ });
+ }
+ } else {
+ // both might be null.
+ if (changed) {
+ mListener.onDataLoaded();
+ }
+ }
+ }
+ }
+
+ private class CameraPreviewData implements LocalData {
+ private int width;
+ private int height;
+
+ CameraPreviewData(int w, int h) {
+ width = w;
+ height = h;
+ }
+
+ @Override
+ public long getDateTaken() {
+ // This value is used for sorting.
+ return -1;
+ }
+
+ @Override
+ public long getDateModified() {
+ // This value might be used for sorting.
+ return -1;
+ }
+
+ @Override
+ public String getTitle() {
+ return "";
+ }
+
+ @Override
+ public int getWidth() {
+ return width;
+ }
+
+ @Override
+ public int getHeight() {
+ return height;
+ }
+
+ @Override
+ public int getType() {
+ return ImageData.TYPE_CAMERA_PREVIEW;
+ }
+
+ @Override
+ public boolean isActionSupported(int action) {
+ return false;
+ }
+
+ @Override
+ public View getView(Context c, int width, int height, Drawable placeHolder) {
+ return mCameraPreviewView;
+ }
+
+ @Override
+ public void prepare() {
+ // do nothing.
+ }
+
+ @Override
+ public void recycle() {
+ // do nothing.
+ }
+ }
+
+}
diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java
new file mode 100644
index 000000000..1f60160a9
--- /dev/null
+++ b/src/com/android/camera/data/LocalData.java
@@ -0,0 +1,440 @@
+/*
+ * 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.data;
+
+import android.content.Context;
+import android.database.Cursor;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Matrix;
+import android.graphics.drawable.Drawable;
+import android.media.MediaMetadataRetriever;
+import android.os.AsyncTask;
+import android.provider.MediaStore.Images.ImageColumns;
+import android.provider.MediaStore.Video;
+import android.provider.MediaStore.Video.VideoColumns;
+import android.util.Log;
+import android.view.View;
+import android.widget.ImageView;
+
+import com.android.camera.ui.FilmStripView;
+
+import java.util.Comparator;
+import java.util.Date;
+
+/* An abstract interface that represents the local media data. Also implements
+ * Comparable interface so we can sort in DataAdapter.
+ */
+abstract interface LocalData extends FilmStripView.ImageData {
+ static final String TAG = "LocalData";
+
+ abstract View getView(Context c, int width, int height, Drawable placeHolder);
+ abstract long getDateTaken();
+ abstract long getDateModified();
+ abstract String getTitle();
+
+ static class NewestFirstComparator implements Comparator<LocalData> {
+
+ private static int compare(long v1, long v2) {
+ if (v1 == -1) {
+ if (v2 == -1) return 0;
+ return -1;
+ }
+ if (v2 == -1) return 0;
+
+ return ((v1 > v2) ? 1 : ((v1 < v2) ? -1 : 0));
+ }
+
+ @Override
+ public int compare(LocalData d1, LocalData d2) {
+ int cmp = compare(d1.getDateTaken(), d2.getDateTaken());
+ if (cmp == 0) {
+ cmp = compare(d1.getDateModified(), d2.getDateModified());
+ }
+ if (cmp == 0) {
+ cmp = d1.getTitle().compareTo(d2.getTitle());
+ }
+ return cmp;
+ }
+ }
+
+ /*
+ * A base class for all the local media files. The bitmap is loaded in background
+ * thread. Subclasses should implement their own background loading thread by
+ * subclassing BitmapLoadTask and overriding doInBackground() to return a bitmap.
+ */
+ abstract static class LocalMediaData implements LocalData {
+ protected long id;
+ protected String title;
+ protected String mimeType;
+ protected long dateTaken;
+ protected long dateModified;
+ protected String path;
+ // width and height should be adjusted according to orientation.
+ protected int width;
+ protected int height;
+
+ // true if this data has a corresponding visible view.
+ protected Boolean mUsing = false;
+
+ @Override
+ public long getDateTaken() {
+ return dateTaken;
+ }
+
+ @Override
+ public long getDateModified() {
+ return dateModified;
+ }
+
+ @Override
+ public String getTitle() {
+ return new String(title);
+ }
+
+ @Override
+ public int getWidth() {
+ return width;
+ }
+
+ @Override
+ public int getHeight() {
+ return height;
+ }
+
+ @Override
+ public boolean isActionSupported(int action) {
+ return false;
+ }
+
+ @Override
+ public View getView(Context c,
+ int decodeWidth, int decodeHeight, Drawable placeHolder) {
+ ImageView v = new ImageView(c);
+ v.setImageDrawable(placeHolder);
+
+ v.setScaleType(ImageView.ScaleType.FIT_XY);
+ BitmapLoadTask task = getBitmapLoadTask(v, decodeWidth, decodeHeight);
+ task.execute();
+ return v;
+ }
+
+ @Override
+ public void prepare() {
+ synchronized (mUsing) {
+ mUsing = true;
+ }
+ }
+
+ @Override
+ public void recycle() {
+ synchronized (mUsing) {
+ mUsing = false;
+ }
+ }
+
+ protected boolean isUsing() {
+ synchronized (mUsing) {
+ return mUsing;
+ }
+ }
+
+ @Override
+ public abstract int getType();
+
+ protected abstract BitmapLoadTask getBitmapLoadTask(
+ ImageView v, int decodeWidth, int decodeHeight);
+
+ /*
+ * An AsyncTask class that loads the bitmap in the background thread.
+ * Sub-classes should implement their own "protected Bitmap doInBackground(Void... )"
+ */
+ protected abstract class BitmapLoadTask extends AsyncTask<Void, Void, Bitmap> {
+ protected ImageView mView;
+
+ protected BitmapLoadTask(ImageView v) {
+ mView = v;
+ }
+
+ @Override
+ protected void onPostExecute(Bitmap bitmap) {
+ if (!isUsing()) return;
+ if (bitmap == null) {
+ Log.e(TAG, "Failed decoding bitmap for file:" + path);
+ return;
+ }
+ mView.setScaleType(ImageView.ScaleType.FIT_XY);
+ mView.setImageBitmap(bitmap);
+ }
+ }
+ }
+
+ static class Photo extends LocalMediaData {
+ public static final int COL_ID = 0;
+ public static final int COL_TITLE = 1;
+ public static final int COL_MIME_TYPE = 2;
+ public static final int COL_DATE_TAKEN = 3;
+ public static final int COL_DATE_MODIFIED = 4;
+ public static final int COL_DATA = 5;
+ public static final int COL_ORIENTATION = 6;
+ public static final int COL_WIDTH = 7;
+ public static final int COL_HEIGHT = 8;
+
+ static final String QUERY_ORDER = ImageColumns.DATE_TAKEN + " DESC, "
+ + ImageColumns._ID + " DESC";
+ static final String[] QUERY_PROJECTION = {
+ ImageColumns._ID, // 0, int
+ ImageColumns.TITLE, // 1, string
+ ImageColumns.MIME_TYPE, // 2, string
+ ImageColumns.DATE_TAKEN, // 3, int
+ ImageColumns.DATE_MODIFIED, // 4, int
+ ImageColumns.DATA, // 5, string
+ ImageColumns.ORIENTATION, // 6, int, 0, 90, 180, 270
+ ImageColumns.WIDTH, // 7, int
+ ImageColumns.HEIGHT, // 8, int
+ };
+
+ private static final int mSupportedAction =
+ FilmStripView.ImageData.ACTION_DEMOTE
+ | FilmStripView.ImageData.ACTION_PROMOTE;
+
+ // 32K buffer.
+ private static final byte[] DECODE_TEMP_STORAGE = new byte[32 * 1024];
+
+ // from MediaStore, can only be 0, 90, 180, 270;
+ public int orientation;
+
+ static Photo buildFromCursor(Cursor c) {
+ Photo d = new Photo();
+ d.id = c.getLong(COL_ID);
+ d.title = c.getString(COL_TITLE);
+ d.mimeType = c.getString(COL_MIME_TYPE);
+ d.dateTaken = c.getLong(COL_DATE_TAKEN);
+ d.dateModified = c.getLong(COL_DATE_MODIFIED);
+ d.path = c.getString(COL_DATA);
+ d.orientation = c.getInt(COL_ORIENTATION);
+ d.width = c.getInt(COL_WIDTH);
+ d.height = c.getInt(COL_HEIGHT);
+ if (d.width <= 0 || d.height <= 0) {
+ Log.v(TAG, "warning! zero dimension for "
+ + d.path + ":" + d.width + "x" + d.height);
+ BitmapFactory.Options opts = decodeDimension(d.path);
+ if (opts != null) {
+ d.width = opts.outWidth;
+ d.height = opts.outHeight;
+ } else {
+ Log.v(TAG, "warning! dimension decode failed for " + d.path);
+ Bitmap b = BitmapFactory.decodeFile(d.path);
+ if (b == null) {
+ return null;
+ }
+ d.width = b.getWidth();
+ d.height = b.getHeight();
+ }
+ }
+ if (d.orientation == 90 || d.orientation == 270) {
+ int b = d.width;
+ d.width = d.height;
+ d.height = b;
+ }
+ return d;
+ }
+
+ @Override
+ public String toString() {
+ return "Photo:" + ",data=" + path + ",mimeType=" + mimeType
+ + "," + width + "x" + height + ",orientation=" + orientation
+ + ",date=" + new Date(dateTaken);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_PHOTO;
+ }
+
+ @Override
+ public boolean isActionSupported(int action) {
+ return ((action & mSupportedAction) != 0);
+ }
+
+ @Override
+ protected BitmapLoadTask getBitmapLoadTask(
+ ImageView v, int decodeWidth, int decodeHeight) {
+ return new PhotoBitmapLoadTask(v, decodeWidth, decodeHeight);
+ }
+
+ private static BitmapFactory.Options decodeDimension(String path) {
+ BitmapFactory.Options opts = new BitmapFactory.Options();
+ opts.inJustDecodeBounds = true;
+ Bitmap b = BitmapFactory.decodeFile(path, opts);
+ if (b == null) {
+ return null;
+ }
+ return opts;
+ }
+
+ private final class PhotoBitmapLoadTask extends BitmapLoadTask {
+ private int mDecodeWidth;
+ private int mDecodeHeight;
+
+ public PhotoBitmapLoadTask(ImageView v, int decodeWidth, int decodeHeight) {
+ super(v);
+ mDecodeWidth = decodeWidth;
+ mDecodeHeight = decodeHeight;
+ }
+
+ @Override
+ protected Bitmap doInBackground(Void... v) {
+ BitmapFactory.Options opts = null;
+ Bitmap b;
+ int sample = 1;
+ while (mDecodeWidth * sample < width
+ || mDecodeHeight * sample < height) {
+ sample *= 2;
+ }
+ opts = new BitmapFactory.Options();
+ opts.inSampleSize = sample;
+ opts.inTempStorage = DECODE_TEMP_STORAGE;
+ if (isCancelled() || !isUsing()) {
+ return null;
+ }
+ b = BitmapFactory.decodeFile(path, opts);
+ if (orientation != 0) {
+ if (isCancelled() || !isUsing()) {
+ return null;
+ }
+ Matrix m = new Matrix();
+ m.setRotate((float) orientation);
+ b = Bitmap.createBitmap(b, 0, 0, b.getWidth(), b.getHeight(), m, false);
+ }
+ return b;
+ }
+ }
+ }
+
+ static class Video extends LocalMediaData {
+ public static final int COL_ID = 0;
+ public static final int COL_TITLE = 1;
+ public static final int COL_MIME_TYPE = 2;
+ public static final int COL_DATE_TAKEN = 3;
+ public static final int COL_DATE_MODIFIED = 4;
+ public static final int COL_DATA = 5;
+ public static final int COL_WIDTH = 6;
+ public static final int COL_HEIGHT = 7;
+
+ private static final int mSupportedActions =
+ FilmStripView.ImageData.ACTION_DEMOTE
+ | FilmStripView.ImageData.ACTION_PROMOTE
+ | FilmStripView.ImageData.ACTION_PLAY;
+
+ static final String QUERY_ORDER = VideoColumns.DATE_TAKEN + " DESC, "
+ + VideoColumns._ID + " DESC";
+ static final String[] QUERY_PROJECTION = {
+ VideoColumns._ID, // 0, int
+ VideoColumns.TITLE, // 1, string
+ VideoColumns.MIME_TYPE, // 2, string
+ VideoColumns.DATE_TAKEN, // 3, int
+ VideoColumns.DATE_MODIFIED, // 4, int
+ VideoColumns.DATA, // 5, string
+ VideoColumns.WIDTH, // 6, int
+ VideoColumns.HEIGHT, // 7, int
+ VideoColumns.RESOLUTION
+ };
+
+ static Video buildFromCursor(Cursor c) {
+ Video d = new Video();
+ d.id = c.getLong(COL_ID);
+ d.title = c.getString(COL_TITLE);
+ d.mimeType = c.getString(COL_MIME_TYPE);
+ d.dateTaken = c.getLong(COL_DATE_TAKEN);
+ d.dateModified = c.getLong(COL_DATE_MODIFIED);
+ d.path = c.getString(COL_DATA);
+ d.width = c.getInt(COL_WIDTH);
+ d.height = c.getInt(COL_HEIGHT);
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(d.path);
+ String rotation = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION);
+ if (d.width == 0 || d.height == 0) {
+ d.width = Integer.parseInt(retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH));
+ d.height = Integer.parseInt(retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT));
+ }
+ retriever.release();
+ if (rotation.equals("90") || rotation.equals("270")) {
+ int b = d.width;
+ d.width = d.height;
+ d.height = b;
+ }
+ return d;
+ }
+
+ @Override
+ public String toString() {
+ return "Video:" + ",data=" + path + ",mimeType=" + mimeType
+ + "," + width + "x" + height + ",date=" + new Date(dateTaken);
+ }
+
+ @Override
+ public int getType() {
+ return TYPE_PHOTO;
+ }
+
+ @Override
+ public boolean isActionSupported(int action) {
+ return ((action & mSupportedActions) != 0);
+ }
+
+ @Override
+ protected BitmapLoadTask getBitmapLoadTask(
+ ImageView v, int decodeWidth, int decodeHeight) {
+ return new VideoBitmapLoadTask(v);
+ }
+
+ private final class VideoBitmapLoadTask extends BitmapLoadTask {
+
+ public VideoBitmapLoadTask(ImageView v) {
+ super(v);
+ }
+
+ @Override
+ protected Bitmap doInBackground(Void... v) {
+ if (isCancelled() || !isUsing()) {
+ return null;
+ }
+ android.media.MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(path);
+ byte[] data = retriever.getEmbeddedPicture();
+ Bitmap bitmap = null;
+ if (isCancelled() || !isUsing()) {
+ retriever.release();
+ return null;
+ }
+ if (data != null) {
+ bitmap = BitmapFactory.decodeByteArray(data, 0, data.length);
+ }
+ if (bitmap == null) {
+ bitmap = (Bitmap) retriever.getFrameAtTime();
+ }
+ retriever.release();
+ return bitmap;
+ }
+ }
+ }
+}
+
diff --git a/src/com/android/camera/ui/FaceView.java b/src/com/android/camera/ui/FaceView.java
index f4dd823d1..24150497c 100644
--- a/src/com/android/camera/ui/FaceView.java
+++ b/src/com/android/camera/ui/FaceView.java
@@ -33,13 +33,15 @@ import android.view.View;
import com.android.camera.CameraActivity;
import com.android.camera.CameraScreenNail;
+import com.android.camera.NewPhotoUI;
import com.android.camera.Util;
import com.android.gallery3d.R;
import com.android.gallery3d.common.ApiHelper;
@TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH)
public class FaceView extends View
- implements FocusIndicator, Rotatable {
+ implements FocusIndicator, Rotatable,
+ NewPhotoUI.SurfaceTextureSizeChangedListener {
private static final String TAG = "CAM FaceView";
private final boolean LOGV = false;
// The value for android.hardware.Camera.setDisplayOrientation.
@@ -95,6 +97,12 @@ public class FaceView extends View
mPaint.setStrokeWidth(res.getDimension(R.dimen.face_circle_stroke));
}
+ @Override
+ public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight) {
+ mUncroppedWidth = uncroppedWidth;
+ mUncroppedHeight = uncroppedHeight;
+ }
+
public void setFaces(Face[] faces) {
if (LOGV) Log.v(TAG, "Num of faces=" + faces.length);
if (mPause) return;
diff --git a/src/com/android/camera/ui/FilmStripGestureRecognizer.java b/src/com/android/camera/ui/FilmStripGestureRecognizer.java
new file mode 100644
index 000000000..f870b5829
--- /dev/null
+++ b/src/com/android/camera/ui/FilmStripGestureRecognizer.java
@@ -0,0 +1,112 @@
+/*
+ * 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.ui;
+
+import android.content.Context;
+import android.view.GestureDetector;
+import android.view.MotionEvent;
+import android.view.ScaleGestureDetector;
+
+// This class aggregates three gesture detectors: GestureDetector,
+// ScaleGestureDetector.
+public class FilmStripGestureRecognizer {
+ @SuppressWarnings("unused")
+ private static final String TAG = "FilmStripGestureRecognizer";
+
+ public interface Listener {
+ boolean onSingleTapUp(float x, float y);
+ boolean onDoubleTap(float x, float y);
+ boolean onScroll(float x, float y, float dx, float dy);
+ boolean onFling(float velocityX, float velocityY);
+ boolean onScaleBegin(float focusX, float focusY);
+ boolean onScale(float focusX, float focusY, float scale);
+ boolean onDown(float x, float y);
+ boolean onUp(float x, float y);
+ void onScaleEnd();
+ }
+
+ private final GestureDetector mGestureDetector;
+ private final ScaleGestureDetector mScaleDetector;
+ private final Listener mListener;
+
+ public FilmStripGestureRecognizer(Context context, Listener listener) {
+ mListener = listener;
+ mGestureDetector = new GestureDetector(context, new MyGestureListener(),
+ null, true /* ignoreMultitouch */);
+ mScaleDetector = new ScaleGestureDetector(
+ context, new MyScaleListener());
+ }
+
+ public void onTouchEvent(MotionEvent event) {
+ mGestureDetector.onTouchEvent(event);
+ mScaleDetector.onTouchEvent(event);
+ if (event.getAction() == MotionEvent.ACTION_UP) {
+ mListener.onUp(event.getX(), event.getY());
+ }
+ }
+
+ private class MyGestureListener
+ extends GestureDetector.SimpleOnGestureListener {
+ @Override
+ public boolean onSingleTapUp(MotionEvent e) {
+ return mListener.onSingleTapUp(e.getX(), e.getY());
+ }
+
+ @Override
+ public boolean onDoubleTap(MotionEvent e) {
+ return mListener.onDoubleTap(e.getX(), e.getY());
+ }
+
+ @Override
+ public boolean onScroll(
+ MotionEvent e1, MotionEvent e2, float dx, float dy) {
+ return mListener.onScroll(e2.getX(), e2.getY(), dx, dy);
+ }
+
+ @Override
+ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
+ float velocityY) {
+ return mListener.onFling(velocityX, velocityY);
+ }
+
+ @Override
+ public boolean onDown(MotionEvent e) {
+ mListener.onDown(e.getX(), e.getY());
+ return super.onDown(e);
+ }
+ }
+
+ private class MyScaleListener
+ extends ScaleGestureDetector.SimpleOnScaleGestureListener {
+ @Override
+ public boolean onScaleBegin(ScaleGestureDetector detector) {
+ return mListener.onScaleBegin(
+ detector.getFocusX(), detector.getFocusY());
+ }
+
+ @Override
+ public boolean onScale(ScaleGestureDetector detector) {
+ return mListener.onScale(detector.getFocusX(),
+ detector.getFocusY(), detector.getScaleFactor());
+ }
+
+ @Override
+ public void onScaleEnd(ScaleGestureDetector detector) {
+ mListener.onScaleEnd();
+ }
+ }
+}
diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java
new file mode 100644
index 000000000..265df00b1
--- /dev/null
+++ b/src/com/android/camera/ui/FilmStripView.java
@@ -0,0 +1,1004 @@
+/*
+ * 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.ui;
+
+import android.animation.Animator;
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.LinearInterpolator;
+import android.widget.Scroller;
+
+public class FilmStripView extends ViewGroup {
+ private static final String TAG = FilmStripView.class.getSimpleName();
+ private static final int BUFFER_SIZE = 5;
+ // Horizontal padding of children.
+ private static final int H_PADDING = 100;
+ // Duration to go back to the first.
+ private static final int DURATION_BACK_ANIM = 500;
+ private static final int DURATION_SCROLL_TO_FILMSTRIP = 350;
+ private static final int DURATION_GEOMETRY_ADJUST = 200;
+ private static final float FILM_STRIP_SCALE = 0.6f;
+ private static final float MAX_SCALE = 1f;
+
+ private Context mContext;
+ private FilmStripGestureRecognizer mGestureRecognizer;
+ private DataAdapter mDataAdapter;
+ private final Rect mDrawArea = new Rect();
+
+ private int mCurrentInfo;
+ private float mScale;
+ private GeometryAnimator mGeometryAnimator;
+ private LinearInterpolator mLinearInterpolator;
+ private int mCenterPosition = -1;
+ private ViewInfo[] mViewInfo = new ViewInfo[BUFFER_SIZE];
+
+ private Listener mListener;
+
+ // This is used to resolve the misalignment problem when the device
+ // orientation is changed. If the current item is in fullscreen, it might
+ // be shifted because mCenterPosition is not adjusted with the orientation.
+ // Set this to true when onSizeChanged is called to make sure we adjust
+ // mCenterPosition accordingly.
+ private boolean mAnchorPending;
+
+ public interface ImageData {
+ public static final int TYPE_NONE = 0;
+ public static final int TYPE_CAMERA_PREVIEW = 1;
+ public static final int TYPE_PHOTO = 2;
+ public static final int TYPE_VIDEO = 3;
+ public static final int TYPE_PHOTOSPHERE = 4;
+
+ // The actions are defined bit-wise so we can use bit operations like
+ // | and &.
+ public static final int ACTION_NONE = 0;
+ public static final int ACTION_PROMOTE = 1;
+ public static final int ACTION_DEMOTE = (1 << 1);
+ public static final int ACTION_PLAY = (1 << 2);
+
+ // SIZE_FULL means disgard the width or height when deciding the view size
+ // of this ImageData, just use full screen size.
+ public static final int SIZE_FULL = -2;
+
+ // The values returned by getWidth() and getHeight() will be used for layout.
+ public int getWidth();
+ public int getHeight();
+ public int getType();
+ public boolean isActionSupported(int action);
+
+ // prepare() should be called first time before using it.
+ public void prepare();
+
+ // recycle() should be called before we nullify the reference to this
+ // data.
+ public void recycle();
+ }
+
+ public interface DataAdapter {
+ public interface UpdateReporter {
+ public boolean isDataRemoved(int id);
+ public boolean isDataUpdated(int id);
+ }
+
+ public interface Listener {
+ // Called when the whole data loading is done. No any assumption
+ // on previous data.
+ public void onDataLoaded();
+ // Only some of the data is changed. The listener should check
+ // if any thing needs to be updated.
+ public void onDataUpdated(UpdateReporter reporter);
+ public void onDataInserted(int dataID, ImageData data);
+ public void onDataRemoved(int dataID, ImageData data);
+ }
+
+ public int getTotalNumber();
+ public View getView(Context context, int id);
+ public ImageData getImageData(int id);
+ public void suggestDecodeSize(int w, int h);
+
+ public void setListener(Listener listener);
+ }
+
+ public interface Listener {
+ public void onDataPromoted(int dataID);
+ public void onDataDemoted(int dataID);
+ }
+
+ // A helper class to tract and calculate the view coordination.
+ private static class ViewInfo {
+ private int mDataID;
+ // the position of the left of the view in the whole filmstrip.
+ private int mLeftPosition;
+ private View mView;
+
+ public ViewInfo(int id, View v) {
+ v.setPivotX(0f);
+ v.setPivotY(0f);
+ mDataID = id;
+ mView = v;
+ mLeftPosition = -1;
+ }
+
+ public int getID() {
+ return mDataID;
+ }
+
+ public void setID(int id) {
+ mDataID = id;
+ }
+
+ public void setLeftPosition(int pos) {
+ mLeftPosition = pos;
+ }
+
+ public int getLeftPosition() {
+ return mLeftPosition;
+ }
+
+ public float getTranslationY(float scale) {
+ return mView.getTranslationY() / scale;
+ }
+
+ public float getTranslationX(float scale) {
+ return mView.getTranslationX();
+ }
+
+ public void setTranslationY(float transY, float scale) {
+ mView.setTranslationY(transY * scale);
+ }
+
+ public void setTranslationX(float transX, float scale) {
+ mView.setTranslationX(transX * scale);
+ }
+
+ public int getCenterX() {
+ return mLeftPosition + mView.getWidth() / 2;
+ }
+
+ public View getView() {
+ return mView;
+ }
+
+ private void layoutAt(int left, int top) {
+ mView.layout(left, top, left + mView.getMeasuredWidth(),
+ top + mView.getMeasuredHeight());
+ }
+
+ public void layoutIn(Rect drawArea, int refCenter, float scale) {
+ // drawArea is where to layout in.
+ // refCenter is the absolute horizontal position of the center of drawArea.
+ int left = (int) (drawArea.centerX() + (mLeftPosition - refCenter) * scale);
+ int top = (int) (drawArea.centerY() - (mView.getMeasuredHeight() / 2) * scale);
+ layoutAt(left, top);
+ mView.setScaleX(scale);
+ mView.setScaleY(scale);
+ }
+ }
+
+ public FilmStripView(Context context) {
+ super(context);
+ init(context);
+ }
+
+ public FilmStripView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ init(context);
+ }
+
+ public FilmStripView(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init(context);
+ }
+
+ private void init(Context context) {
+ mCurrentInfo = (BUFFER_SIZE - 1) / 2;
+ // This is for positioning camera controller at the same place in
+ // different orientations.
+ setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
+ | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION);
+
+ setWillNotDraw(false);
+ mContext = context;
+ mScale = 1.0f;
+ mGeometryAnimator = new GeometryAnimator(context);
+ mLinearInterpolator = new LinearInterpolator();
+ mGestureRecognizer =
+ new FilmStripGestureRecognizer(context, new MyGestureReceiver());
+ }
+
+ public void setListener(Listener l) {
+ mListener = l;
+ }
+
+ public float getScale() {
+ return mScale;
+ }
+
+ public boolean isAnchoredTo(int id) {
+ if (mViewInfo[mCurrentInfo].getID() == id
+ && mViewInfo[mCurrentInfo].getCenterX() == mCenterPosition) {
+ return true;
+ }
+ return false;
+ }
+
+ public int getCurrentType() {
+ if (mDataAdapter == null) return ImageData.TYPE_NONE;
+ ViewInfo curr = mViewInfo[mCurrentInfo];
+ if (curr == null) return ImageData.TYPE_NONE;
+ return mDataAdapter.getImageData(curr.getID()).getType();
+ }
+
+ @Override
+ public void onDraw(Canvas c) {
+ if (mGeometryAnimator.hasNewGeometry()) {
+ layoutChildren();
+ }
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+
+ int boundWidth = MeasureSpec.getSize(widthMeasureSpec);
+ int boundHeight = MeasureSpec.getSize(heightMeasureSpec);
+ if (mDataAdapter != null) {
+ mDataAdapter.suggestDecodeSize(boundWidth / 2, boundHeight / 2);
+ }
+
+ int wMode = View.MeasureSpec.EXACTLY;
+ int hMode = View.MeasureSpec.EXACTLY;
+
+ for (int i = 0; i < mViewInfo.length; i++) {
+ ViewInfo info = mViewInfo[i];
+ if (mViewInfo[i] == null) continue;
+
+ int imageWidth = mDataAdapter.getImageData(info.getID()).getWidth();
+ int imageHeight = mDataAdapter.getImageData(info.getID()).getHeight();
+ if (imageWidth == ImageData.SIZE_FULL) imageWidth = boundWidth;
+ if (imageHeight == ImageData.SIZE_FULL) imageHeight = boundHeight;
+
+ int scaledWidth = boundWidth;
+ int scaledHeight = boundHeight;
+
+ if (imageWidth * scaledHeight > scaledWidth * imageHeight) {
+ scaledHeight = imageHeight * scaledWidth / imageWidth;
+ } else {
+ scaledWidth = imageWidth * scaledHeight / imageHeight;
+ }
+ mViewInfo[i].getView().measure(
+ View.MeasureSpec.makeMeasureSpec(scaledWidth, wMode)
+ , View.MeasureSpec.makeMeasureSpec(scaledHeight, hMode));
+ }
+ setMeasuredDimension(boundWidth, boundHeight);
+ }
+
+ private int findTheNearestView(int pointX) {
+
+ int nearest = 0;
+ // find the first non-null ViewInfo.
+ for (; nearest < BUFFER_SIZE
+ && (mViewInfo[nearest] == null || mViewInfo[nearest].getLeftPosition() == -1);
+ nearest++);
+ // no existing available ViewInfo
+ if (nearest == BUFFER_SIZE) return -1;
+ int min = Math.abs(pointX - mViewInfo[nearest].getCenterX());
+
+ for (int infoID = nearest + 1;
+ infoID < BUFFER_SIZE && mViewInfo[infoID] != null; infoID++) {
+ // not measured yet.
+ if (mViewInfo[infoID].getLeftPosition() == -1) continue;
+
+ int c = mViewInfo[infoID].getCenterX();
+ int dist = Math.abs(pointX - c);
+ if (dist < min) {
+ min = dist;
+ nearest = infoID;
+ }
+ }
+ return nearest;
+ }
+
+ private ViewInfo buildInfoFromData(int dataID) {
+ ImageData data = mDataAdapter.getImageData(dataID);
+ if (data == null) return null;
+ data.prepare();
+ View v = mDataAdapter.getView(mContext, dataID);
+ if (v == null) return null;
+ ViewInfo info = new ViewInfo(dataID, v);
+ addView(info.getView());
+ return info;
+ }
+
+ private void removeInfo(int infoID) {
+ if (infoID >= mViewInfo.length || mViewInfo[infoID] == null) return;
+
+ removeView(mViewInfo[infoID].getView());
+ mDataAdapter.getImageData(mViewInfo[infoID].getID()).recycle();
+ mViewInfo[infoID] = null;
+ }
+
+ // We try to keep the one closest to the center of the screen at position mCurrentInfo.
+ private void stepIfNeeded() {
+ int nearest = findTheNearestView(mCenterPosition);
+ // no change made.
+ if (nearest == -1 || nearest == mCurrentInfo) return;
+
+ int adjust = nearest - mCurrentInfo;
+ if (adjust > 0) {
+ for (int k = 0; k < adjust; k++) {
+ removeInfo(k);
+ }
+ for (int k = 0; k + adjust < BUFFER_SIZE; k++) {
+ mViewInfo[k] = mViewInfo[k + adjust];
+ }
+ for (int k = BUFFER_SIZE - adjust; k < BUFFER_SIZE; k++) {
+ mViewInfo[k] = null;
+ if (mViewInfo[k - 1] != null) {
+ mViewInfo[k] = buildInfoFromData(mViewInfo[k - 1].getID() + 1);
+ }
+ }
+ } else {
+ for (int k = BUFFER_SIZE - 1; k >= BUFFER_SIZE + adjust; k--) {
+ removeInfo(k);
+ }
+ for (int k = BUFFER_SIZE - 1; k + adjust >= 0; k--) {
+ mViewInfo[k] = mViewInfo[k + adjust];
+ }
+ for (int k = -1 - adjust; k >= 0; k--) {
+ mViewInfo[k] = null;
+ if (mViewInfo[k + 1] != null) {
+ mViewInfo[k] = buildInfoFromData(mViewInfo[k + 1].getID() - 1);
+ }
+ }
+ }
+ }
+
+ // Don't go beyond the bound.
+ private void adjustCenterPosition() {
+ ViewInfo curr = mViewInfo[mCurrentInfo];
+ if (curr == null) return;
+
+ if (curr.getID() == 0 && mCenterPosition < curr.getCenterX()) {
+ mCenterPosition = curr.getCenterX();
+ mGeometryAnimator.stopScroll();
+ }
+ if (curr.getID() == mDataAdapter.getTotalNumber() - 1
+ && mCenterPosition > curr.getCenterX()) {
+ mCenterPosition = curr.getCenterX();
+ mGeometryAnimator.stopScroll();
+ }
+ }
+
+ private void layoutChildren() {
+ if (mAnchorPending) {
+ mCenterPosition = mViewInfo[mCurrentInfo].getCenterX();
+ mAnchorPending = false;
+ }
+
+ if (mGeometryAnimator.hasNewGeometry()) {
+ mCenterPosition = mGeometryAnimator.getNewPosition();
+ mScale = mGeometryAnimator.getNewScale();
+ }
+
+ adjustCenterPosition();
+
+ mViewInfo[mCurrentInfo].layoutIn(mDrawArea, mCenterPosition, mScale);
+
+ // images on the left
+ for (int infoID = mCurrentInfo - 1; infoID >= 0; infoID--) {
+ ViewInfo curr = mViewInfo[infoID];
+ if (curr != null) {
+ ViewInfo next = mViewInfo[infoID + 1];
+ curr.setLeftPosition(
+ next.getLeftPosition() - curr.getView().getMeasuredWidth() - H_PADDING);
+ curr.layoutIn(mDrawArea, mCenterPosition, mScale);
+ }
+ }
+
+ // images on the right
+ for (int infoID = mCurrentInfo + 1; infoID < BUFFER_SIZE; infoID++) {
+ ViewInfo curr = mViewInfo[infoID];
+ if (curr != null) {
+ ViewInfo prev = mViewInfo[infoID - 1];
+ curr.setLeftPosition(
+ prev.getLeftPosition() + prev.getView().getMeasuredWidth() + H_PADDING);
+ curr.layoutIn(mDrawArea, mCenterPosition, mScale);
+ }
+ }
+
+ stepIfNeeded();
+ invalidate();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ if (mViewInfo[mCurrentInfo] == null) return;
+
+ mDrawArea.left = l;
+ mDrawArea.top = t;
+ mDrawArea.right = r;
+ mDrawArea.bottom = b;
+
+ layoutChildren();
+ }
+
+ @Override
+ protected void onSizeChanged(int w, int h, int oldw, int oldh) {
+ if (w == oldw && h == oldh) return;
+ if (mViewInfo[mCurrentInfo] != null && mScale == 1f
+ && isAnchoredTo(mViewInfo[mCurrentInfo].getID())) {
+ mAnchorPending = true;
+ }
+ }
+
+ private void animateViewBack(View v) {
+ v.animate().translationX(0).alpha(1f).setDuration(200).start();
+ }
+
+ private void updateRemoval(int removedInfo, final ImageData data) {
+ final View removedView = mViewInfo[removedInfo].getView();
+ final int offsetX = (int) (removedView.getMeasuredWidth() + H_PADDING);
+
+ for (int i = removedInfo + 1; i < BUFFER_SIZE; i++) {
+ if (mViewInfo[i] != null) {
+ mViewInfo[i].setID(mViewInfo[i].getID() - 1);
+ mViewInfo[i].setLeftPosition(mViewInfo[i].getLeftPosition() - offsetX);
+ }
+ }
+
+ if (removedInfo >= mCurrentInfo
+ && mViewInfo[removedInfo].getID() < mDataAdapter.getTotalNumber() - 1) {
+ // fill the removed info by left shift when the current one or anyone on the
+ // right is removed, and there's more data on the right available.
+ for (int i = removedInfo; i < BUFFER_SIZE - 1; i++) {
+ mViewInfo[i] = mViewInfo[i + 1];
+ if (mViewInfo[i] != null) {
+ mViewInfo[i].setTranslationX(offsetX, mScale);
+ animateViewBack(mViewInfo[i].getView());
+ }
+ }
+
+ // pull data out from the DataAdapter for the last one.
+ int curr = BUFFER_SIZE - 1;
+ int prev = curr - 1;
+ if (mViewInfo[prev] != null) {
+ mViewInfo[curr] = buildInfoFromData(mViewInfo[prev].getID() + 1);
+ }
+ } else {
+ mCenterPosition -= offsetX;
+ // fill the removed place by right shift
+ for (int i = removedInfo; i > 0; i--) {
+ mViewInfo[i] = mViewInfo[i - 1];
+ if (mViewInfo[i] != null) {
+ mViewInfo[i].setLeftPosition(mViewInfo[i].getLeftPosition() - offsetX);
+ mViewInfo[i].setTranslationX(-offsetX, mScale);
+ animateViewBack(mViewInfo[i].getView());
+ }
+ }
+
+ // pull data out from the DataAdapter for the first one.
+ int curr = 0;
+ int next = curr + 1;
+ if (mViewInfo[next] != null) {
+ mViewInfo[curr] = buildInfoFromData(mViewInfo[next].getID() - 1);
+ }
+ }
+
+ int transY = getHeight() / 8;
+ if (removedView.getTranslationY() < 0) {
+ transY = -transY;
+ }
+ removedView.animate()
+ .alpha(0f)
+ .translationYBy(transY)
+ .setInterpolator(mLinearInterpolator)
+ .setDuration(200)
+ .withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ removeView(removedView);
+ data.recycle();
+ }
+ })
+ .start();
+ layoutChildren();
+ }
+
+ public void setDataAdapter(DataAdapter adapter) {
+ mDataAdapter = adapter;
+ mDataAdapter.suggestDecodeSize(getMeasuredWidth(), getMeasuredHeight());
+ mDataAdapter.setListener(new DataAdapter.Listener() {
+ @Override
+ public void onDataLoaded() {
+ reload();
+ }
+
+ @Override
+ public void onDataUpdated(DataAdapter.UpdateReporter reporter) {
+ update(reporter);
+ }
+
+ @Override
+ public void onDataInserted(int dataID, ImageData data) {
+ }
+
+ @Override
+ public void onDataRemoved(int dataID, ImageData data) {
+ int removedInfo = 0;
+ for (; removedInfo < BUFFER_SIZE; removedInfo++) {
+ if (mViewInfo[removedInfo] != null
+ && mViewInfo[removedInfo].getID() == dataID) break;
+ }
+ if (removedInfo == BUFFER_SIZE) return;
+ updateRemoval(removedInfo, data);
+ }
+ });
+ }
+
+ public boolean isInCameraFullscreen() {
+ return (isAnchoredTo(0) && mScale == 1f
+ && getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW);
+ }
+
+ @Override
+ public boolean onInterceptTouchEvent(MotionEvent ev) {
+ if (isInCameraFullscreen()) return false;
+ return true;
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent ev) {
+ mGestureRecognizer.onTouchEvent(ev);
+ return true;
+ }
+
+ private void updateViewInfo(int infoID) {
+ ViewInfo info = mViewInfo[infoID];
+ removeView(info.getView());
+ mViewInfo[infoID] = buildInfoFromData(info.getID());
+ }
+
+ // Some of the data is changed.
+ private void update(DataAdapter.UpdateReporter reporter) {
+ // No data yet.
+ if (mViewInfo[mCurrentInfo] == null) {
+ reload();
+ return;
+ }
+
+ // Check the current one.
+ ViewInfo curr = mViewInfo[mCurrentInfo];
+ int dataID = curr.getID();
+ if (reporter.isDataRemoved(dataID)) {
+ mCenterPosition = -1;
+ reload();
+ return;
+ }
+ if (reporter.isDataUpdated(dataID)) {
+ updateViewInfo(mCurrentInfo);
+ }
+
+ // Check left
+ for (int i = mCurrentInfo - 1; i >= 0; i--) {
+ curr = mViewInfo[i];
+ if (curr != null) {
+ dataID = curr.getID();
+ if (reporter.isDataRemoved(dataID) || reporter.isDataUpdated(dataID)) {
+ updateViewInfo(i);
+ }
+ } else {
+ ViewInfo next = mViewInfo[i + 1];
+ if (next != null) {
+ mViewInfo[i] = buildInfoFromData(next.getID() - 1);
+ }
+ }
+ }
+
+ // Check right
+ for (int i = mCurrentInfo + 1; i < BUFFER_SIZE; i++) {
+ curr = mViewInfo[i];
+ if (curr != null) {
+ dataID = curr.getID();
+ if (reporter.isDataRemoved(dataID) || reporter.isDataUpdated(dataID)) {
+ updateViewInfo(i);
+ }
+ } else {
+ ViewInfo prev = mViewInfo[i - 1];
+ if (prev != null) {
+ mViewInfo[i] = buildInfoFromData(prev.getID() + 1);
+ }
+ }
+ }
+ }
+
+ // The whole data might be totally different. Flush all and load from the start.
+ private void reload() {
+ removeAllViews();
+ int dataNumber = mDataAdapter.getTotalNumber();
+ if (dataNumber == 0) return;
+
+ int currentData = 0;
+ int currentLeft = 0;
+ mViewInfo[mCurrentInfo] = buildInfoFromData(currentData);
+ mViewInfo[mCurrentInfo].setLeftPosition(currentLeft);
+ if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW
+ && currentLeft == 0) {
+ // we are in camera mode by default.
+ mGeometryAnimator.lockPosition(currentLeft);
+ }
+ for (int i = 1; mCurrentInfo + i < BUFFER_SIZE || mCurrentInfo - i >= 0; i++) {
+ int infoID = mCurrentInfo + i;
+ if (infoID < BUFFER_SIZE && mViewInfo[infoID - 1] != null) {
+ mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID - 1].getID() + 1);
+ }
+ infoID = mCurrentInfo - i;
+ if (infoID >= 0 && mViewInfo[infoID + 1] != null) {
+ mViewInfo[infoID] = buildInfoFromData(mViewInfo[infoID + 1].getID() - 1);
+ }
+ }
+ layoutChildren();
+ }
+
+ private void promoteData(int infoID, int dataID) {
+ if (mListener != null) {
+ mListener.onDataPromoted(dataID);
+ }
+ }
+
+ private void demoteData(int infoID, int dataID) {
+ if (mListener != null) {
+ mListener.onDataDemoted(dataID);
+ }
+ }
+
+ // GeometryAnimator controls all the geometry animations. It passively
+ // tells the geometry information on demand.
+ private class GeometryAnimator implements
+ ValueAnimator.AnimatorUpdateListener,
+ Animator.AnimatorListener {
+
+ private ValueAnimator mScaleAnimator;
+ private boolean mHasNewScale;
+ private float mNewScale;
+
+ private Scroller mScroller;
+ private boolean mHasNewPosition;
+ private DecelerateInterpolator mDecelerateInterpolator;
+
+ private boolean mCanStopScroll;
+ private boolean mCanStopScale;
+
+ private boolean mIsPositionLocked;
+ private int mLockedPosition;
+
+ private Runnable mPostAction;
+
+ GeometryAnimator(Context context) {
+ mScroller = new Scroller(context);
+ mHasNewPosition = false;
+ mScaleAnimator = new ValueAnimator();
+ mScaleAnimator.addUpdateListener(GeometryAnimator.this);
+ mScaleAnimator.addListener(GeometryAnimator.this);
+ mDecelerateInterpolator = new DecelerateInterpolator();
+ mCanStopScroll = true;
+ mCanStopScale = true;
+ mHasNewScale = false;
+ }
+
+ boolean hasNewGeometry() {
+ mHasNewPosition = mScroller.computeScrollOffset();
+ if (!mHasNewPosition) {
+ mCanStopScroll = true;
+ }
+ // If the position is locked, then we always return true to force
+ // the position value to use the locked value.
+ return (mHasNewPosition || mHasNewScale || mIsPositionLocked);
+ }
+
+ // Always call hasNewGeometry() before getting the new scale value.
+ float getNewScale() {
+ if (!mHasNewScale) return mScale;
+ mHasNewScale = false;
+ return mNewScale;
+ }
+
+ // Always call hasNewGeometry() before getting the new position value.
+ int getNewPosition() {
+ if (mIsPositionLocked) return mLockedPosition;
+ if (!mHasNewPosition) return mCenterPosition;
+ return mScroller.getCurrX();
+ }
+
+ void lockPosition(int pos) {
+ mIsPositionLocked = true;
+ mLockedPosition = pos;
+ }
+
+ void unlockPosition() {
+ if (mIsPositionLocked) {
+ // only when the position is previously locked we set the current
+ // position to make it consistent.
+ mCenterPosition = mLockedPosition;
+ mIsPositionLocked = false;
+ }
+ }
+
+ void fling(int velocityX, int minX, int maxX) {
+ if (!stopScroll() || mIsPositionLocked) return;
+ mScroller.fling(mCenterPosition, 0, velocityX, 0, minX, maxX, 0, 0);
+ }
+
+ boolean stopScroll() {
+ if (!mCanStopScroll) return false;
+ mScroller.forceFinished(true);
+ mHasNewPosition = false;
+ return true;
+ }
+
+ boolean stopScale() {
+ if (!mCanStopScale) return false;
+ mScaleAnimator.cancel();
+ mHasNewScale = false;
+ return true;
+ }
+
+ void stop() {
+ stopScroll();
+ stopScale();
+ }
+
+ void scrollTo(int position, int duration, boolean interruptible) {
+ if (!stopScroll() || mIsPositionLocked) return;
+ mCanStopScroll = interruptible;
+ stopScroll();
+ mScroller.startScroll(mCenterPosition, 0, position - mCenterPosition,
+ 0, duration);
+ }
+
+ void scrollTo(int position, int duration) {
+ scrollTo(position, duration, true);
+ }
+
+ void scaleTo(float scale, int duration, boolean interruptible) {
+ if (!stopScale()) return;
+ mCanStopScale = interruptible;
+ mScaleAnimator.setDuration(duration);
+ mScaleAnimator.setFloatValues(mScale, scale);
+ mScaleAnimator.setInterpolator(mDecelerateInterpolator);
+ mScaleAnimator.start();
+ mHasNewScale = true;
+ }
+
+ void scaleTo(float scale, int duration) {
+ scaleTo(scale, duration, true);
+ }
+
+ void setPostAction(Runnable act) {
+ mPostAction = act;
+ }
+
+ @Override
+ public void onAnimationUpdate(ValueAnimator animation) {
+ mHasNewScale = true;
+ mNewScale = (Float) animation.getAnimatedValue();
+ layoutChildren();
+ }
+
+ @Override
+ public void onAnimationStart(Animator anim) {
+ }
+
+ @Override
+ public void onAnimationEnd(Animator anim) {
+ if (mPostAction != null) {
+ mPostAction.run();
+ mPostAction = null;
+ }
+ mCanStopScale = true;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator anim) {
+ mPostAction = null;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator anim) {
+ }
+ }
+
+ private class MyGestureReceiver implements FilmStripGestureRecognizer.Listener {
+ // Indicating the current trend of scaling is up (>1) or down (<1).
+ private float mScaleTrend;
+
+ @Override
+ public boolean onSingleTapUp(float x, float y) {
+ return false;
+ }
+
+ @Override
+ public boolean onDoubleTap(float x, float y) {
+ return false;
+ }
+
+ @Override
+ public boolean onDown(float x, float y) {
+ mGeometryAnimator.stop();
+ return true;
+ }
+
+ @Override
+ public boolean onUp(float x, float y) {
+ float halfH = getHeight() / 2;
+ for (int i = 0; i < BUFFER_SIZE; i++) {
+ if (mViewInfo[i] == null) continue;
+ float transY = mViewInfo[i].getTranslationY(mScale);
+ if (transY == 0) continue;
+ int id = mViewInfo[i].getID();
+
+ if (mDataAdapter.getImageData(id)
+ .isActionSupported(ImageData.ACTION_DEMOTE)
+ && transY > halfH) {
+ demoteData(i, id);
+ } else if (mDataAdapter.getImageData(id)
+ .isActionSupported(ImageData.ACTION_PROMOTE)
+ && transY < -halfH) {
+ promoteData(i, id);
+ } else {
+ mViewInfo[i].getView().animate()
+ .translationY(0f)
+ .alpha(1f)
+ .setDuration(200)
+ .start();
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public boolean onScroll(float x, float y, float dx, float dy) {
+ if (Math.abs(dx) > Math.abs(dy)) {
+ int deltaX = (int) (dx / mScale);
+ if (deltaX > 0 && isInCameraFullscreen()) {
+ mGeometryAnimator.unlockPosition();
+ mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+ }
+ mCenterPosition += deltaX;
+ } else {
+ // Vertical part. Promote or demote.
+ //int scaledDeltaY = (int) (dy * mScale);
+ int hit = 0;
+ Rect hitRect = new Rect();
+ for (; hit < BUFFER_SIZE; hit++) {
+ if (mViewInfo[hit] == null) continue;
+ mViewInfo[hit].getView().getHitRect(hitRect);
+ if (hitRect.contains((int) x, (int) y)) break;
+ }
+ if (hit == BUFFER_SIZE) return false;
+
+ ImageData data = mDataAdapter.getImageData(mViewInfo[hit].getID());
+ float transY = mViewInfo[hit].getTranslationY(mScale) - dy / mScale;
+ if (!data.isActionSupported(ImageData.ACTION_DEMOTE) && transY > 0f) {
+ transY = 0f;
+ }
+ if (!data.isActionSupported(ImageData.ACTION_PROMOTE) && transY < 0f) {
+ transY = 0f;
+ }
+ mViewInfo[hit].setTranslationY(transY, mScale);
+ }
+
+ layoutChildren();
+ return true;
+ }
+
+ @Override
+ public boolean onFling(float velocityX, float velocityY) {
+ if (Math.abs(velocityX) > Math.abs(velocityY)) {
+ float scaledVelocityX = velocityX / mScale;
+ if (isInCameraFullscreen() && scaledVelocityX < 0) {
+ mGeometryAnimator.unlockPosition();
+ mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+ }
+ ViewInfo info = mViewInfo[mCurrentInfo];
+ int w = getWidth();
+ if (info == null) return true;
+ mGeometryAnimator.fling((int) -scaledVelocityX,
+ // estimation of possible length on the left
+ info.getLeftPosition() - info.getID() * w * 2,
+ // estimation of possible length on the right
+ info.getLeftPosition()
+ + (mDataAdapter.getTotalNumber() - info.getID()) * w * 2);
+ layoutChildren();
+ } else {
+ // ignore horizontal fling.
+ }
+ return true;
+ }
+
+ @Override
+ public boolean onScaleBegin(float focusX, float focusY) {
+ if (isInCameraFullscreen()) return false;
+ mScaleTrend = 1f;
+ return true;
+ }
+
+ @Override
+ public boolean onScale(float focusX, float focusY, float scale) {
+ if (isInCameraFullscreen()) return false;
+
+ mScaleTrend = mScaleTrend * 0.3f + scale * 0.7f;
+ mScale *= scale;
+ if (mScale <= FILM_STRIP_SCALE) {
+ mScale = FILM_STRIP_SCALE;
+ }
+ if (mScale >= MAX_SCALE) {
+ mScale = MAX_SCALE;
+ }
+ layoutChildren();
+ return true;
+ }
+
+ @Override
+ public void onScaleEnd() {
+ if (mScaleTrend >= 1f) {
+ if (mScale != 1f) {
+ mGeometryAnimator.scaleTo(1f, DURATION_GEOMETRY_ADJUST, false);
+ }
+
+ if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW) {
+ if (isAnchoredTo(0)) {
+ mGeometryAnimator.lockPosition(mViewInfo[mCurrentInfo].getCenterX());
+ } else {
+ mGeometryAnimator.scrollTo(
+ mViewInfo[mCurrentInfo].getCenterX(),
+ DURATION_GEOMETRY_ADJUST, false);
+ mGeometryAnimator.setPostAction(mLockPositionRunnable);
+ }
+ }
+ } else {
+ // Scale down to film strip mode.
+ if (mScale == FILM_STRIP_SCALE) {
+ mGeometryAnimator.unlockPosition();
+ return;
+ }
+ mGeometryAnimator.scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST, false);
+ mGeometryAnimator.setPostAction(mUnlockPositionRunnable);
+ }
+ }
+
+ private Runnable mLockPositionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mGeometryAnimator.lockPosition(mViewInfo[mCurrentInfo].getCenterX());
+ }
+ };
+
+ private Runnable mUnlockPositionRunnable = new Runnable() {
+ @Override
+ public void run() {
+ mGeometryAnimator.unlockPosition();
+ }
+ };
+ }
+}
diff --git a/src/com/android/camera/ui/NewCameraRootView.java b/src/com/android/camera/ui/NewCameraRootView.java
new file mode 100644
index 000000000..a50e41a5b
--- /dev/null
+++ b/src/com/android/camera/ui/NewCameraRootView.java
@@ -0,0 +1,134 @@
+/*
+ * 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.ui;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.res.Configuration;
+import android.graphics.Rect;
+import android.os.Debug;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import com.android.camera.Util;
+import com.android.gallery3d.R;
+
+public class NewCameraRootView extends FrameLayout {
+
+ private int mTopMargin = 0;
+ private int mBottomMargin = 0;
+ private int mLeftMargin = 0;
+ private int mRightMargin = 0;
+ private Rect mCurrentInsets;
+ private int mOffset = 0;
+ public NewCameraRootView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
+ | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
+ }
+
+ @Override
+ protected boolean fitSystemWindows(Rect insets) {
+ super.fitSystemWindows(insets);
+ mCurrentInsets = insets;
+ // insets include status bar, navigation bar, etc
+ // In this case, we are only concerned with the size of nav bar
+ if (mOffset > 0) return true;
+
+ if (insets.bottom > 0) {
+ mOffset = insets.bottom;
+ } else if (insets.right > 0) {
+ mOffset = insets.right;
+ }
+ return true;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int rotation = Util.getDisplayRotation((Activity) getContext());
+ // all the layout code assumes camera device orientation to be portrait
+ // adjust rotation for landscape
+ int orientation = getResources().getConfiguration().orientation;
+ int camOrientation = (rotation % 180 == 0) ? Configuration.ORIENTATION_PORTRAIT
+ : Configuration.ORIENTATION_LANDSCAPE;
+ if (camOrientation != orientation) {
+ rotation = (rotation + 90) % 360;
+ }
+ // calculate margins
+ mLeftMargin = 0;
+ mRightMargin = 0;
+ mBottomMargin = 0;
+ mTopMargin = 0;
+ switch (rotation) {
+ case 0:
+ mBottomMargin += mOffset;
+ break;
+ case 90:
+ mRightMargin += mOffset;
+ break;
+ case 180:
+ mTopMargin += mOffset;
+ break;
+ case 270:
+ mLeftMargin += mOffset;
+ break;
+ }
+ if (mCurrentInsets != null) {
+ if (mCurrentInsets.right > 0) {
+ // navigation bar on the right
+ mRightMargin = mRightMargin > 0 ? mRightMargin : mCurrentInsets.right;
+ } else {
+ // navigation bar on the bottom
+ mBottomMargin = mBottomMargin > 0 ? mBottomMargin : mCurrentInsets.bottom;
+ }
+ }
+ // make sure all the children are resized
+ super.onMeasure(widthMeasureSpec - mLeftMargin - mRightMargin,
+ heightMeasureSpec - mTopMargin - mBottomMargin);
+ setMeasuredDimension(widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ public void onLayout(boolean changed, int l, int t, int r, int b) {
+ r -= l;
+ b -= t;
+ l = 0;
+ t = 0;
+ int orientation = getResources().getConfiguration().orientation;
+ // Lay out children
+ for (int i = 0; i < getChildCount(); i++) {
+ View v = getChildAt(i);
+ if (v instanceof CameraControls) {
+ // Lay out camera controls to center on the short side of the screen
+ // so that they stay in place during rotation
+ int width = v.getMeasuredWidth();
+ int height = v.getMeasuredHeight();
+ if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+ int left = (l + r - width) / 2;
+ v.layout(left, t + mTopMargin, left + width, b - mBottomMargin);
+ } else {
+ int top = (t + b - height) / 2;
+ v.layout(l + mLeftMargin, top, r - mRightMargin, top + height);
+ }
+ } else {
+ v.layout(l + mLeftMargin, t + mTopMargin, r - mRightMargin, b - mBottomMargin);
+ }
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/app/CommonControllerOverlay.java b/src/com/android/gallery3d/app/CommonControllerOverlay.java
index a4f5807ae..9adb4e7a8 100644
--- a/src/com/android/gallery3d/app/CommonControllerOverlay.java
+++ b/src/com/android/gallery3d/app/CommonControllerOverlay.java
@@ -66,6 +66,10 @@ public abstract class CommonControllerOverlay extends FrameLayout implements
protected boolean mCanReplay = true;
+ public void setSeekable(boolean canSeek) {
+ mTimeBar.setSeekable(canSeek);
+ }
+
public CommonControllerOverlay(Context context) {
super(context);
diff --git a/src/com/android/gallery3d/app/MoviePlayer.java b/src/com/android/gallery3d/app/MoviePlayer.java
index 00e4cd63b..ce9183483 100644
--- a/src/com/android/gallery3d/app/MoviePlayer.java
+++ b/src/com/android/gallery3d/app/MoviePlayer.java
@@ -25,7 +25,6 @@ import android.content.DialogInterface.OnCancelListener;
import android.content.DialogInterface.OnClickListener;
import android.content.Intent;
import android.content.IntentFilter;
-import android.graphics.Color;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.net.Uri;
@@ -135,6 +134,17 @@ public class MoviePlayer implements
return true;
}
});
+ mVideoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
+ @Override
+ public void onPrepared(MediaPlayer player) {
+ if (!mVideoView.canSeekForward() || !mVideoView.canSeekBackward()) {
+ mController.setSeekable(false);
+ } else {
+ mController.setSeekable(true);
+ }
+ setProgress();
+ }
+ });
// The SurfaceView is transparent before drawing the first frame.
// This makes the UI flashing when open a video. (black -> old screen
diff --git a/src/com/android/gallery3d/app/TimeBar.java b/src/com/android/gallery3d/app/TimeBar.java
index 402dfcfab..246346a56 100644
--- a/src/com/android/gallery3d/app/TimeBar.java
+++ b/src/com/android/gallery3d/app/TimeBar.java
@@ -259,4 +259,8 @@ public class TimeBar extends View {
}
}
+ public void setSeekable(boolean canSeek) {
+ mShowScrubber = canSeek;
+ }
+
}
diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
index d13d261a8..4b653780e 100644
--- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java
+++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java
@@ -69,6 +69,8 @@ import com.android.gallery3d.filtershow.provider.SharedImageProvider;
import com.android.gallery3d.filtershow.state.StateAdapter;
import com.android.gallery3d.filtershow.tools.BitmapTask;
import com.android.gallery3d.filtershow.tools.SaveCopyTask;
+import com.android.gallery3d.filtershow.tools.XmpPresets;
+import com.android.gallery3d.filtershow.tools.XmpPresets.XMresults;
import com.android.gallery3d.filtershow.ui.FramedTextButton;
import com.android.gallery3d.filtershow.ui.Spline;
import com.android.gallery3d.util.GalleryUtils;
@@ -119,6 +121,9 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
private LoadBitmapTask mLoadBitmapTask;
private boolean mLoading = true;
+ private Uri mOriginalImageUri = null;
+ private ImagePreset mOriginalPreset = null;
+
private CategoryAdapter mCategoryLooksAdapter = null;
private CategoryAdapter mCategoryBordersAdapter = null;
private CategoryAdapter mCategoryGeometryAdapter = null;
@@ -147,6 +152,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
setDefaultPreset();
+ extractXMPData();
processIntent();
}
@@ -285,9 +291,12 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
}
mAction = intent.getAction();
-
- if (intent.getData() != null) {
- startLoadBitmap(intent.getData());
+ Uri srcUri = intent.getData();
+ if (mOriginalImageUri != null) {
+ srcUri = mOriginalImageUri;
+ }
+ if (srcUri != null) {
+ startLoadBitmap(srcUri);
} else {
pickImage();
}
@@ -364,8 +373,9 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
for (int i = 0; i < borders.size(); i++) {
FilterRepresentation filter = borders.elementAt(i);
+ filter.setScrName(getString(R.string.borders));
if (i == 0) {
- filter.setName(getString(R.string.none));
+ filter.setScrName(getString(R.string.none));
}
}
@@ -516,6 +526,11 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
mCategoryFiltersAdapter.imageLoaded();
mLoadBitmapTask = null;
+ if (mOriginalPreset != null) {
+ MasterImage.getImage().setPreset(mOriginalPreset, true);
+ mOriginalPreset = null;
+ }
+
if (mAction == TINY_PLANET_ACTION) {
showRepresentation(mCategoryFiltersAdapter.getTinyPlanet());
}
@@ -1049,4 +1064,13 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL
System.loadLibrary("jni_filtershow_filters");
}
+ private void extractXMPData() {
+ XMresults res = XmpPresets.extractXMPData(
+ getBaseContext(), mMasterImage, getIntent().getData());
+ if (res == null)
+ return;
+
+ mOriginalImageUri = res.originalimage;
+ mOriginalPreset = res.preset;
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
index 8760c4a09..1ea40f202 100644
--- a/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
+++ b/src/com/android/gallery3d/filtershow/cache/CachingPipeline.java
@@ -29,8 +29,9 @@ import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
import com.android.gallery3d.filtershow.presets.FilterEnvironment;
import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.presets.PipelineInterface;
-public class CachingPipeline {
+public class CachingPipeline implements PipelineInterface {
private static final String LOGTAG = "CachingPipeline";
private boolean DEBUG = false;
@@ -65,22 +66,10 @@ public class CachingPipeline {
mName = name;
}
- public static synchronized Resources getResources() {
- return sResources;
- }
-
- public static synchronized void setResources(Resources resources) {
- sResources = resources;
- }
-
public static synchronized RenderScript getRenderScriptContext() {
return sRS;
}
- public static synchronized void setRenderScriptContext(RenderScript RS) {
- sRS = RS;
- }
-
public static synchronized void createRenderscriptContext(Activity context) {
if (sRS != null) {
Log.w(LOGTAG, "A prior RS context exists when calling setRenderScriptContext");
@@ -128,6 +117,10 @@ public class CachingPipeline {
}
}
+ public Resources getResources() {
+ return sRS.getApplicationContext().getResources();
+ }
+
private synchronized void destroyPixelAllocations() {
if (DEBUG) {
Log.v(LOGTAG, "destroyPixelAllocations in " + getName());
@@ -167,14 +160,14 @@ public class CachingPipeline {
}
private void setupEnvironment(ImagePreset preset, boolean highResPreview) {
- mEnvironment.setCachingPipeline(this);
+ mEnvironment.setPipeline(this);
mEnvironment.setFiltersManager(mFiltersManager);
if (highResPreview) {
mEnvironment.setScaleFactor(mHighResPreviewScaleFactor);
} else {
mEnvironment.setScaleFactor(mPreviewScaleFactor);
}
- mEnvironment.setQuality(ImagePreset.QUALITY_PREVIEW);
+ mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW);
mEnvironment.setImagePreset(preset);
mEnvironment.setStop(false);
}
@@ -293,11 +286,11 @@ public class CachingPipeline {
|| request.getType() == RenderingRequest.STYLE_ICON_RENDERING) {
if (request.getType() == RenderingRequest.ICON_RENDERING) {
- mEnvironment.setQuality(ImagePreset.QUALITY_ICON);
+ mEnvironment.setQuality(FilterEnvironment.QUALITY_ICON);
} else if (request.getType() == RenderingRequest.STYLE_ICON_RENDERING) {
mEnvironment.setQuality(ImagePreset.STYLE_ICON);
} else {
- mEnvironment.setQuality(ImagePreset.QUALITY_PREVIEW);
+ mEnvironment.setQuality(FilterEnvironment.QUALITY_PREVIEW);
}
Bitmap bmp = preset.apply(bitmap, mEnvironment);
@@ -317,8 +310,11 @@ public class CachingPipeline {
setupEnvironment(preset, false);
mFiltersManager.freeFilterResources(preset);
preset.applyFilters(-1, -1, in, out, mEnvironment);
- // TODO: we should render the border onto a different bitmap instead
- preset.applyBorder(in, out, mEnvironment);
+ boolean copyOut = false;
+ if (preset.nbFilters() > 0) {
+ copyOut = true;
+ }
+ preset.applyBorder(in, out, copyOut, mEnvironment);
}
}
@@ -328,7 +324,7 @@ public class CachingPipeline {
return bitmap;
}
setupEnvironment(preset, false);
- mEnvironment.setQuality(ImagePreset.QUALITY_FINAL);
+ mEnvironment.setQuality(FilterEnvironment.QUALITY_FINAL);
mEnvironment.setScaleFactor(1.0f);
mFiltersManager.freeFilterResources(preset);
bitmap = preset.applyGeometry(bitmap, mEnvironment);
@@ -345,7 +341,7 @@ public class CachingPipeline {
}
mGeometry.useRepresentation(preset.getGeometry());
return mGeometry.apply(bitmap, mPreviewScaleFactor,
- ImagePreset.QUALITY_PREVIEW);
+ FilterEnvironment.QUALITY_PREVIEW);
}
public synchronized void compute(TripleBufferBitmap buffer, ImagePreset preset, int type) {
@@ -458,4 +454,7 @@ public class CachingPipeline {
return mName;
}
+ public RenderScript getRSContext() {
+ return CachingPipeline.getRenderScriptContext();
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java b/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java
index 072edd72a..25169c2d8 100644
--- a/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java
+++ b/src/com/android/gallery3d/filtershow/controller/BasicParameterStyle.java
@@ -109,5 +109,4 @@ public class BasicParameterStyle implements ParameterStyles {
public void setFilterView(FilterView editor) {
mEditor = editor;
}
-
}
diff --git a/src/com/android/gallery3d/filtershow/controller/BasicSlider.java b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
index df5b6ae73..9d8278d52 100644
--- a/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
+++ b/src/com/android/gallery3d/filtershow/controller/BasicSlider.java
@@ -84,5 +84,4 @@ public class BasicSlider implements Control {
mSeekBar.setMax(mParameter.getMaximum() - mParameter.getMinimum());
mSeekBar.setProgress(mParameter.getValue() - mParameter.getMinimum());
}
-
}
diff --git a/src/com/android/gallery3d/filtershow/crop/CropMath.java b/src/com/android/gallery3d/filtershow/crop/CropMath.java
index 849ac60ef..671554f16 100644
--- a/src/com/android/gallery3d/filtershow/crop/CropMath.java
+++ b/src/com/android/gallery3d/filtershow/crop/CropMath.java
@@ -196,14 +196,13 @@ public class CropMath {
float finalH = origH;
if (origA < a) {
finalH = origW / a;
+ r.top = r.centerY() - finalH / 2;
+ r.bottom = r.top + finalH;
} else {
finalW = origH * a;
+ r.left = r.centerX() - finalW / 2;
+ r.right = r.left + finalW;
}
- float centX = r.centerX();
- float centY = r.centerY();
- float hw = finalW / 2;
- float hh = finalH / 2;
- r.set(centX - hw, centY - hh, centX + hw, centY + hh);
}
/**
diff --git a/src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java b/src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java
new file mode 100644
index 000000000..e18d3104f
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/data/FilterStackDBHelper.java
@@ -0,0 +1,101 @@
+/*
+ * 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.gallery3d.filtershow.data;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteOpenHelper;
+
+public class FilterStackDBHelper extends SQLiteOpenHelper {
+
+ public static final int DATABASE_VERSION = 1;
+ public static final String DATABASE_NAME = "filterstacks.db";
+ private static final String SQL_CREATE_TABLE = "CREATE TABLE ";
+
+ public static interface FilterStack {
+ /** The row uid */
+ public static final String _ID = "_id";
+ /** The table name */
+ public static final String TABLE = "filterstack";
+ /** The stack name */
+ public static final String STACK_ID = "stack_id";
+ /** A serialized stack of filters. */
+ public static final String FILTER_STACK= "stack";
+ }
+
+ private static final String[][] CREATE_FILTER_STACK = {
+ { FilterStack._ID, "INTEGER PRIMARY KEY AUTOINCREMENT" },
+ { FilterStack.STACK_ID, "TEXT" },
+ { FilterStack.FILTER_STACK, "BLOB" },
+ };
+
+ public FilterStackDBHelper(Context context, String name, int version) {
+ super(context, name, null, version);
+ }
+
+ public FilterStackDBHelper(Context context, String name) {
+ this(context, name, DATABASE_VERSION);
+ }
+
+ public FilterStackDBHelper(Context context) {
+ this(context, DATABASE_NAME);
+ }
+
+ @Override
+ public void onCreate(SQLiteDatabase db) {
+ createTable(db, FilterStack.TABLE, CREATE_FILTER_STACK);
+ }
+
+ @Override
+ public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+ dropTable(db, FilterStack.TABLE);
+ onCreate(db);
+ }
+
+ protected static void createTable(SQLiteDatabase db, String table, String[][] columns) {
+ StringBuilder create = new StringBuilder(SQL_CREATE_TABLE);
+ create.append(table).append('(');
+ boolean first = true;
+ for (String[] column : columns) {
+ if (!first) {
+ create.append(',');
+ }
+ first = false;
+ for (String val : column) {
+ create.append(val).append(' ');
+ }
+ }
+ create.append(')');
+ db.beginTransaction();
+ try {
+ db.execSQL(create.toString());
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+
+ protected static void dropTable(SQLiteDatabase db, String table) {
+ db.beginTransaction();
+ try {
+ db.execSQL("drop table if exists " + table);
+ db.setTransactionSuccessful();
+ } finally {
+ db.endTransaction();
+ }
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/data/FilterStackSource.java b/src/com/android/gallery3d/filtershow/data/FilterStackSource.java
new file mode 100644
index 000000000..4e343777d
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/data/FilterStackSource.java
@@ -0,0 +1,143 @@
+/*
+ * 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.gallery3d.filtershow.data;
+
+import android.content.ContentValues;
+import android.content.Context;
+import android.database.Cursor;
+import android.database.sqlite.SQLiteDatabase;
+import android.database.sqlite.SQLiteException;
+import android.util.Log;
+import android.util.Pair;
+
+import com.android.gallery3d.filtershow.data.FilterStackDBHelper.FilterStack;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class FilterStackSource {
+ private static final String LOGTAG = "FilterStackSource";
+
+ private SQLiteDatabase database = null;;
+ private final FilterStackDBHelper dbHelper;
+
+ public FilterStackSource(Context context) {
+ dbHelper = new FilterStackDBHelper(context);
+ }
+
+ public void open() {
+ try {
+ database = dbHelper.getWritableDatabase();
+ } catch (SQLiteException e) {
+ Log.w(LOGTAG, "could not open database", e);
+ }
+ }
+
+ public void close() {
+ database = null;
+ dbHelper.close();
+ }
+
+ public boolean insertStack(String stackName, byte[] stackBlob) {
+ boolean ret = true;
+ ContentValues val = new ContentValues();
+ val.put(FilterStack.STACK_ID, stackName);
+ val.put(FilterStack.FILTER_STACK, stackBlob);
+ database.beginTransaction();
+ try {
+ ret = (-1 != database.insert(FilterStack.TABLE, null, val));
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ return ret;
+ }
+
+ public boolean removeStack(String stackName) {
+ boolean ret = true;
+ database.beginTransaction();
+ try {
+ ret = (0 != database.delete(FilterStack.TABLE, FilterStack.STACK_ID + " = ?",
+ new String[] { stackName}));
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ return ret;
+ }
+
+ public void removeAllStacks() {
+ database.beginTransaction();
+ try {
+ database.delete(FilterStack.TABLE, null, null);
+ database.setTransactionSuccessful();
+ } finally {
+ database.endTransaction();
+ }
+ }
+
+ public byte[] getStack(String stackName) {
+ byte[] ret = null;
+ Cursor c = null;
+ database.beginTransaction();
+ try {
+ c = database.query(FilterStack.TABLE,
+ new String[] { FilterStack.FILTER_STACK },
+ FilterStack.STACK_ID + " = ?",
+ new String[] { stackName }, null, null, null, null);
+ if (c != null && c.moveToFirst() && !c.isNull(0)) {
+ ret = c.getBlob(0);
+ }
+ database.setTransactionSuccessful();
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ database.endTransaction();
+ }
+ return ret;
+ }
+
+ public List<Pair<String, byte[]>> getAllStacks() {
+ List<Pair<String, byte[]>> ret = new ArrayList<Pair<String, byte[]>>();
+ Cursor c = null;
+ database.beginTransaction();
+ try {
+ c = database.query(FilterStack.TABLE,
+ new String[] { FilterStack.STACK_ID, FilterStack.FILTER_STACK },
+ null, null, null, null, null, null);
+ if (c != null) {
+ boolean loopCheck = c.moveToFirst();
+ while (loopCheck) {
+ String name = (c.isNull(0)) ? null : c.getString(0);
+ byte[] b = (c.isNull(1)) ? null : c.getBlob(1);
+ ret.add(new Pair<String, byte[]>(name, b));
+ loopCheck = c.moveToNext();
+ }
+ }
+ database.setTransactionSuccessful();
+ } finally {
+ if (c != null) {
+ c.close();
+ }
+ database.endTransaction();
+ }
+ if (ret.size() <= 0) {
+ return null;
+ }
+ return ret;
+ }
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
index 9927a0a5e..1c7294c3a 100644
--- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
+++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java
@@ -17,6 +17,8 @@ package com.android.gallery3d.filtershow.filters;
import android.content.Context;
import android.content.res.Resources;
+import android.util.Log;
+
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.presets.ImagePreset;
@@ -24,11 +26,14 @@ import com.android.gallery3d.filtershow.presets.ImagePreset;
import java.util.HashMap;
import java.util.Vector;
-public abstract class BaseFiltersManager {
+public abstract class BaseFiltersManager implements FiltersManagerInterface {
protected HashMap<Class, ImageFilter> mFilters = null;
+ protected HashMap<String, FilterRepresentation> mRepresentationLookup = null;
+ private static final String LOGTAG = "BaseFiltersManager";
protected void init() {
mFilters = new HashMap<Class, ImageFilter>();
+ mRepresentationLookup = new HashMap<String, FilterRepresentation>();
Vector<Class> filters = new Vector<Class>();
addFilterClasses(filters);
for (Class filterClass : filters) {
@@ -36,6 +41,12 @@ public abstract class BaseFiltersManager {
Object filterInstance = filterClass.newInstance();
if (filterInstance instanceof ImageFilter) {
mFilters.put(filterClass, (ImageFilter) filterInstance);
+
+ FilterRepresentation rep =
+ ((ImageFilter) filterInstance).getDefaultRepresentation();
+ if (rep != null) {
+ addRepresentation(rep);
+ }
}
} catch (InstantiationException e) {
e.printStackTrace();
@@ -45,6 +56,20 @@ public abstract class BaseFiltersManager {
}
}
+ public void addRepresentation(FilterRepresentation rep) {
+ mRepresentationLookup.put(rep.getSerializationName(), rep);
+ }
+
+ public FilterRepresentation createFilterFromName(String name) {
+ try {
+ return mRepresentationLookup.get(name).clone();
+ } catch (Exception e) {
+ Log.v(LOGTAG, "unable to generate a filter representation for \"" + name + "\"");
+ e.printStackTrace();
+ }
+ return null;
+ }
+
public ImageFilter getFilter(Class c) {
return mFilters.get(c);
}
@@ -53,10 +78,6 @@ public abstract class BaseFiltersManager {
return mFilters.get(representation.getFilterClass());
}
- public void addFilter(Class filterClass, ImageFilter filter) {
- mFilters.put(filterClass, filter);
- }
-
public FilterRepresentation getRepresentation(Class c) {
ImageFilter filter = mFilters.get(c);
if (filter != null) {
@@ -89,7 +110,7 @@ public abstract class BaseFiltersManager {
protected void addFilterClasses(Vector<Class> filters) {
filters.add(ImageFilterTinyPlanet.class);
- //filters.add(ImageFilterRedEye.class);
+ filters.add(ImageFilterRedEye.class);
filters.add(ImageFilterWBalance.class);
filters.add(ImageFilterExposure.class);
filters.add(ImageFilterVignette.class);
@@ -99,7 +120,7 @@ public abstract class BaseFiltersManager {
filters.add(ImageFilterVibrance.class);
filters.add(ImageFilterSharpen.class);
filters.add(ImageFilterCurves.class);
- // filters.add(ImageFilterDraw.class);
+ filters.add(ImageFilterDraw.class);
filters.add(ImageFilterHue.class);
filters.add(ImageFilterSaturated.class);
filters.add(ImageFilterBwFilter.class);
@@ -168,8 +189,8 @@ public abstract class BaseFiltersManager {
}
public void addTools(Vector<FilterRepresentation> representations) {
- //representations.add(getRepresentation(ImageFilterRedEye.class));
- // representations.add(getRepresentation(ImageFilterDraw.class));
+ representations.add(getRepresentation(ImageFilterRedEye.class));
+ representations.add(getRepresentation(ImageFilterDraw.class));
}
public void setFilterResources(Resources resources) {
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java
index 4d0651edb..368e5c029 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterBasicRepresentation.java
@@ -31,6 +31,8 @@ public class FilterBasicRepresentation extends FilterRepresentation implements P
private int mMaximum;
private int mDefaultValue;
private int mPreviewValue;
+ public static final String SERIAL_NAME = "Name";
+ public static final String SERIAL_VALUE = "Value";
private boolean mLogVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
public FilterBasicRepresentation(String name, int minimum, int value, int maximum) {
@@ -171,4 +173,23 @@ public class FilterBasicRepresentation extends FilterRepresentation implements P
public void copyFrom(Parameter src) {
useParametersFrom((FilterBasicRepresentation) src);
}
+
+ @Override
+ public String[][] serializeRepresentation() {
+ String[][] ret = {
+ {SERIAL_NAME , getName() },
+ {SERIAL_VALUE , Integer.toString(mValue)}};
+ return ret;
+ }
+
+ @Override
+ public void deSerializeRepresentation(String[][] rep) {
+ super.deSerializeRepresentation(rep);
+ for (int i = 0; i < rep.length; i++) {
+ if (SERIAL_VALUE.equals(rep[i][0])) {
+ mValue = Integer.parseInt(rep[i][1]);
+ break;
+ }
+ }
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
index cbcae4b37..a32068aeb 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterCurvesRepresentation.java
@@ -15,6 +15,7 @@ public class FilterCurvesRepresentation extends FilterRepresentation {
public FilterCurvesRepresentation() {
super("Curves");
+ setSerializationName("CURVES");
setFilterClass(ImageFilterCurves.class);
setTextId(R.string.curvesRGB);
setButtonId(R.id.curvesButtonRGB);
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
index dc59b0cfc..9b144b9e9 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterDrawRepresentation.java
@@ -49,6 +49,7 @@ public class FilterDrawRepresentation extends FilterRepresentation {
public FilterDrawRepresentation() {
super("Draw");
+ setSerializationName("DRAW");
setFilterClass(ImageFilterDraw.class);
setPriority(FilterRepresentation.TYPE_VIGNETTE);
setTextId(R.string.imageDraw);
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
index 6e2e7ea16..1ceffb4a2 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java
@@ -21,7 +21,8 @@ import com.android.gallery3d.app.Log;
import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
public class FilterFxRepresentation extends FilterRepresentation {
- private static final String LOGTAG = "FilterFxRepresentation";
+ private static final String SERIALIZATION_NAME = "LUT3D";
+ private static final String LOGTAG = "FilterFxRepresentation";
// TODO: When implementing serialization, we should find a unique way of
// specifying bitmaps / names (the resource IDs being random)
private int mBitmapResource = 0;
@@ -29,6 +30,8 @@ public class FilterFxRepresentation extends FilterRepresentation {
public FilterFxRepresentation(String name, int bitmapResource, int nameResource) {
super(name);
+ setSerializationName(SERIALIZATION_NAME);
+
mBitmapResource = bitmapResource;
mNameResource = nameResource;
setFilterClass(ImageFilterFx.class);
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
index 3f823ea1e..8a878415c 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRedEyeRepresentation.java
@@ -28,6 +28,7 @@ public class FilterRedEyeRepresentation extends FilterPointRepresentation {
public FilterRedEyeRepresentation() {
super("RedEye",R.string.redeye,EditorRedEye.ID);
+ setSerializationName("REDEYE");
setFilterClass(ImageFilterRedEye.class);
setOverlayId(R.drawable.photoeditor_effect_redeye);
setOverlayOnly(true);
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
index 82012b992..91bf676f3 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterRepresentation.java
@@ -16,7 +16,7 @@
package com.android.gallery3d.filtershow.filters;
-import com.android.gallery3d.app.Log;
+import android.util.Log;
import com.android.gallery3d.filtershow.editors.BasicEditor;
public class FilterRepresentation implements Cloneable {
@@ -34,7 +34,7 @@ public class FilterRepresentation implements Cloneable {
private boolean mShowEditingControls = true;
private boolean mShowParameterValue = true;
private boolean mShowUtilityPanel = true;
-
+ private String mSerializationName;
public static final byte TYPE_BORDER = 1;
public static final byte TYPE_FX = 2;
public static final byte TYPE_WBALANCE = 3;
@@ -63,6 +63,8 @@ public class FilterRepresentation implements Cloneable {
representation.setShowEditingControls(showEditingControls());
representation.setShowParameterValue(showParameterValue());
representation.setShowUtilityPanel(showUtilityPanel());
+ representation.mSerializationName = mSerializationName;
+
representation.mTempRepresentation =
mTempRepresentation != null ? mTempRepresentation.clone() : null;
if (DEBUG) {
@@ -96,6 +98,10 @@ public class FilterRepresentation implements Cloneable {
return mName;
}
+ public void setScrName(String name) {
+ mName = name;
+ }
+
public void setName(String name) {
mName = name;
}
@@ -104,6 +110,14 @@ public class FilterRepresentation implements Cloneable {
return mName;
}
+ public void setSerializationName(String sname) {
+ mSerializationName = sname;
+ }
+
+ public String getSerializationName() {
+ return mSerializationName;
+ }
+
public void setPriority(int priority) {
mPriority = priority;
}
@@ -241,4 +255,17 @@ public class FilterRepresentation implements Cloneable {
return "";
}
+ public String[][] serializeRepresentation() {
+ String[][] ret = { { "Name" , getName() }};
+ return ret;
+ }
+
+ public void deSerializeRepresentation(String[][] rep) {
+ for (int i = 0; i < rep.length; i++) {
+ if ("Name".equals(rep[i][0])) {
+ mName = rep[i][0];
+ break;
+ }
+ }
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java
index ac5e04601..48c8b380e 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterTinyPlanetRepresentation.java
@@ -20,11 +20,14 @@ import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.editors.EditorTinyPlanet;
public class FilterTinyPlanetRepresentation extends FilterBasicRepresentation {
+ private static final String SERIALIZATION_NAME = "TINYPLANET";
private static final String LOGTAG = "FilterTinyPlanetRepresentation";
+ private static final String SERIAL_ANGLE = "Angle";
private float mAngle = 0;
public FilterTinyPlanetRepresentation() {
super("TinyPlanet", 0, 50, 100);
+ setSerializationName(SERIALIZATION_NAME);
setShowParameterValue(true);
setFilterClass(ImageFilterTinyPlanet.class);
setPriority(FilterRepresentation.TYPE_TINYPLANET);
@@ -71,4 +74,25 @@ public class FilterTinyPlanetRepresentation extends FilterBasicRepresentation {
// TinyPlanet always has an effect
return false;
}
+
+ @Override
+ public String[][] serializeRepresentation() {
+ String[][] ret = {
+ {SERIAL_NAME , getName() },
+ {SERIAL_VALUE , Integer.toString(getValue())},
+ {SERIAL_ANGLE , Float.toString(mAngle)}};
+ return ret;
+ }
+
+ @Override
+ public void deSerializeRepresentation(String[][] rep) {
+ super.deSerializeRepresentation(rep);
+ for (int i = 0; i < rep.length; i++) {
+ if (SERIAL_VALUE.equals(rep[i][0])) {
+ setValue(Integer.parseInt(rep[i][1]));
+ } else if (SERIAL_ANGLE.equals(rep[i][0])) {
+ setAngle(Float.parseFloat(rep[i][1]));
+ }
+ }
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java
index eef54ef58..9827088ff 100644
--- a/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java
+++ b/src/com/android/gallery3d/filtershow/filters/FilterVignetteRepresentation.java
@@ -29,6 +29,7 @@ public class FilterVignetteRepresentation extends FilterBasicRepresentation impl
public FilterVignetteRepresentation() {
super("Vignette", -100, 50, 100);
+ setSerializationName("VIGNETTE");
setShowParameterValue(true);
setPriority(FilterRepresentation.TYPE_VIGNETTE);
setTextId(R.string.vignette);
@@ -111,4 +112,44 @@ public class FilterVignetteRepresentation extends FilterBasicRepresentation impl
public boolean isNil() {
return getValue() == 0;
}
+
+ private static final String[] sParams = {
+ "Name", "value", "mCenterX", "mCenterY", "mRadiusX",
+ "mRadiusY"
+ };
+
+ @Override
+ public String[][] serializeRepresentation() {
+ String[][] ret = {
+ { sParams[0], getName() },
+ { sParams[1], Integer.toString(getValue()) },
+ { sParams[2], Float.toString(mCenterX) },
+ { sParams[3], Float.toString(mCenterY) },
+ { sParams[4], Float.toString(mRadiusX) },
+ { sParams[5], Float.toString(mRadiusY) }
+ };
+ return ret;
+ }
+
+ @Override
+ public void deSerializeRepresentation(String[][] rep) {
+ super.deSerializeRepresentation(rep);
+ for (int i = 0; i < rep.length; i++) {
+ String key = rep[i][0];
+ String value = rep[i][1];
+ if (sParams[0].equals(key)) {
+ setName(value);
+ } else if (sParams[1].equals(key)) {
+ setValue(Integer.parseInt(value));
+ } else if (sParams[2].equals(key)) {
+ mCenterX = Float.parseFloat(value);
+ } else if (sParams[3].equals(key)) {
+ mCenterY = Float.parseFloat(value);
+ } else if (sParams[4].equals(key)) {
+ mRadiusX = Float.parseFloat(value);
+ } else if (sParams[5].equals(key)) {
+ mRadiusY = Float.parseFloat(value);
+ }
+ }
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java b/src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java
new file mode 100644
index 000000000..710128f99
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/filters/FiltersManagerInterface.java
@@ -0,0 +1,21 @@
+/*
+ * 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.gallery3d.filtershow.filters;
+
+public interface FiltersManagerInterface {
+ ImageFilter getFilterForRepresentation(FilterRepresentation representation);
+}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
index 96ab84f9d..b80fc7f15 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilter.java
@@ -16,12 +16,12 @@
package com.android.gallery3d.filtershow.filters;
+import android.app.Activity;
import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.support.v8.renderscript.Allocation;
import android.widget.Toast;
-import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
import com.android.gallery3d.filtershow.presets.FilterEnvironment;
import com.android.gallery3d.filtershow.presets.ImagePreset;
@@ -35,9 +35,9 @@ public abstract class ImageFilter implements Cloneable {
// TODO: Temporary, for dogfood note memory issues with toasts for better
// feedback. Remove this when filters actually work in low memory
// situations.
- private static FilterShowActivity sActivity = null;
+ private static Activity sActivity = null;
- public static void setActivityForMemoryToasts(FilterShowActivity activity) {
+ public static void setActivityForMemoryToasts(Activity activity) {
sActivity = activity;
}
@@ -76,10 +76,6 @@ public abstract class ImageFilter implements Cloneable {
return bitmap;
}
- public ImagePreset getImagePreset() {
- return getEnvironment().getImagePreset();
- }
-
public abstract void useRepresentation(FilterRepresentation representation);
native protected void nativeApplyGradientFilter(Bitmap bitmap, int w, int h,
@@ -90,10 +86,11 @@ public abstract class ImageFilter implements Cloneable {
}
protected Matrix getOriginalToScreenMatrix(int w, int h) {
- GeometryMetadata geo = getImagePreset().mGeoData;
+ ImagePreset preset = getEnvironment().getImagePreset();
+ GeometryMetadata geo = getEnvironment().getImagePreset().mGeoData;
Matrix originalToScreen = geo.getOriginalToScreen(true,
- getImagePreset().getImageLoader().getOriginalBounds().width(),
- getImagePreset().getImageLoader().getOriginalBounds().height(),
+ preset.getImageLoader().getOriginalBounds().width(),
+ preset.getImageLoader().getOriginalBounds().height(),
w, h);
return originalToScreen;
}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
index a4626cdb2..64c48dffa 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterBwFilter.java
@@ -23,6 +23,7 @@ import android.graphics.Color;
public class ImageFilterBwFilter extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "BWFILTER";
public ImageFilterBwFilter() {
mName = "BW Filter";
@@ -31,6 +32,8 @@ public class ImageFilterBwFilter extends SimpleImageFilter {
public FilterRepresentation getDefaultRepresentation() {
FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation();
representation.setName("BW Filter");
+ representation.setSerializationName(SERIALIZATION_NAME);
+
representation.setFilterClass(ImageFilterBwFilter.class);
representation.setMaximum(180);
representation.setMinimum(-180);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
index 2097f0d6e..c8b41c248 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterContrast.java
@@ -21,6 +21,7 @@ import com.android.gallery3d.R;
import android.graphics.Bitmap;
public class ImageFilterContrast extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "CONTRAST";
public ImageFilterContrast() {
mName = "Contrast";
@@ -30,6 +31,8 @@ public class ImageFilterContrast extends SimpleImageFilter {
FilterBasicRepresentation representation =
(FilterBasicRepresentation) super.getDefaultRepresentation();
representation.setName("Contrast");
+ representation.setSerializationName(SERIALIZATION_NAME);
+
representation.setFilterClass(ImageFilterContrast.class);
representation.setTextId(R.string.contrast);
representation.setButtonId(R.id.contrastButton);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java
index 0b02fc4f6..ea2ff351d 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDownsample.java
@@ -24,6 +24,7 @@ import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.cache.ImageLoader;
public class ImageFilterDownsample extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "DOWNSAMPLE";
private static final int ICON_DOWNSAMPLE_FRACTION = 8;
private ImageLoader mImageLoader;
@@ -35,6 +36,8 @@ public class ImageFilterDownsample extends SimpleImageFilter {
public FilterRepresentation getDefaultRepresentation() {
FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation();
representation.setName("Downsample");
+ representation.setSerializationName(SERIALIZATION_NAME);
+
representation.setFilterClass(ImageFilterDownsample.class);
representation.setMaximum(100);
representation.setMinimum(1);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
index 1fd9071f7..812ab02f0 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterDraw.java
@@ -31,6 +31,7 @@ import android.graphics.PorterDuffColorFilter;
import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.filters.FilterDrawRepresentation.StrokeData;
import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.FilterEnvironment;
import com.android.gallery3d.filtershow.presets.ImagePreset;
import java.util.Vector;
@@ -204,7 +205,7 @@ public class ImageFilterDraw extends ImageFilter {
public void drawData(Canvas canvas, Matrix originalRotateToScreen, int quality) {
Paint paint = new Paint();
- if (quality == ImagePreset.QUALITY_FINAL) {
+ if (quality == FilterEnvironment.QUALITY_FINAL) {
paint.setAntiAlias(true);
}
paint.setStyle(Style.STROKE);
@@ -214,7 +215,7 @@ public class ImageFilterDraw extends ImageFilter {
if (mParameters.getDrawing().isEmpty() && mParameters.getCurrentDrawing() == null) {
return;
}
- if (quality == ImagePreset.QUALITY_FINAL) {
+ if (quality == FilterEnvironment.QUALITY_FINAL) {
for (FilterDrawRepresentation.StrokeData strokeData : mParameters.getDrawing()) {
paint(strokeData, canvas, originalRotateToScreen, quality);
}
@@ -248,17 +249,17 @@ public class ImageFilterDraw extends ImageFilter {
int n = v.size();
for (int i = mCachedStrokes; i < n; i++) {
- paint(v.get(i), drawCache, originalRotateToScreen, ImagePreset.QUALITY_PREVIEW);
+ paint(v.get(i), drawCache, originalRotateToScreen, FilterEnvironment.QUALITY_PREVIEW);
}
mCachedStrokes = n;
}
public void draw(Canvas canvas, Matrix originalRotateToScreen) {
for (FilterDrawRepresentation.StrokeData strokeData : mParameters.getDrawing()) {
- paint(strokeData, canvas, originalRotateToScreen, ImagePreset.QUALITY_PREVIEW);
+ paint(strokeData, canvas, originalRotateToScreen, FilterEnvironment.QUALITY_PREVIEW);
}
mDrawingsTypes[mCurrentStyle].paint(
- null, canvas, originalRotateToScreen, ImagePreset.QUALITY_PREVIEW);
+ null, canvas, originalRotateToScreen, FilterEnvironment.QUALITY_PREVIEW);
}
@Override
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
index 46a9a294c..82de2b73a 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterEdge.java
@@ -21,7 +21,7 @@ import android.graphics.Bitmap;
import com.android.gallery3d.R;
public class ImageFilterEdge extends SimpleImageFilter {
-
+ private static final String SERIALIZATION_NAME = "EDGE";
public ImageFilterEdge() {
mName = "Edge";
}
@@ -29,6 +29,7 @@ public class ImageFilterEdge extends SimpleImageFilter {
public FilterRepresentation getDefaultRepresentation() {
FilterRepresentation representation = super.getDefaultRepresentation();
representation.setName("Edge");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterEdge.class);
representation.setTextId(R.string.edge);
representation.setButtonId(R.id.edgeButton);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
index b0b0b2dd8..6fdcd249b 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterExposure.java
@@ -21,7 +21,7 @@ import com.android.gallery3d.R;
import android.graphics.Bitmap;
public class ImageFilterExposure extends SimpleImageFilter {
-
+ private static final String SERIALIZATION_NAME = "EXPOSURE";
public ImageFilterExposure() {
mName = "Exposure";
}
@@ -30,6 +30,7 @@ public class ImageFilterExposure extends SimpleImageFilter {
FilterBasicRepresentation representation =
(FilterBasicRepresentation) super.getDefaultRepresentation();
representation.setName("Exposure");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterExposure.class);
representation.setTextId(R.string.exposure);
representation.setButtonId(R.id.exposureButton);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
index 68e8a7c9d..51c66127b 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterFx.java
@@ -37,6 +37,11 @@ public class ImageFilterFx extends ImageFilter {
mFxBitmap = null;
}
+ @Override
+ public FilterRepresentation getDefaultRepresentation() {
+ return null;
+ }
+
public void useRepresentation(FilterRepresentation representation) {
FilterFxRepresentation parameters = (FilterFxRepresentation) representation;
mParameters = parameters;
@@ -87,4 +92,5 @@ public class ImageFilterFx extends ImageFilter {
public void setResources(Resources resources) {
mResources = resources;
}
+
}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
index 0022a9eae..0725dd1c4 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHighlights.java
@@ -21,6 +21,7 @@ import android.graphics.Bitmap;
import com.android.gallery3d.R;
public class ImageFilterHighlights extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "HIGHLIGHTS";
private static final String LOGTAG = "ImageFilterVignette";
public ImageFilterHighlights() {
@@ -33,7 +34,8 @@ public class ImageFilterHighlights extends SimpleImageFilter {
public FilterRepresentation getDefaultRepresentation() {
FilterBasicRepresentation representation =
(FilterBasicRepresentation) super.getDefaultRepresentation();
- representation.setName("Shadows");
+ representation.setName("Highlights");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterHighlights.class);
representation.setTextId(R.string.highlight_recovery);
representation.setButtonId(R.id.highlightRecoveryButton);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
index b1f9f7365..7e6f68548 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterHue.java
@@ -22,6 +22,7 @@ import com.android.gallery3d.filtershow.editors.BasicEditor;
import android.graphics.Bitmap;
public class ImageFilterHue extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "HUE";
private ColorSpaceMatrix cmatrix = null;
public ImageFilterHue() {
@@ -33,6 +34,7 @@ public class ImageFilterHue extends SimpleImageFilter {
FilterBasicRepresentation representation =
(FilterBasicRepresentation) super.getDefaultRepresentation();
representation.setName("Hue");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterHue.class);
representation.setMinimum(-180);
representation.setMaximum(180);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
index 29e6d162f..93813813f 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterKMeans.java
@@ -22,6 +22,7 @@ import android.text.format.Time;
import com.android.gallery3d.R;
public class ImageFilterKMeans extends SimpleImageFilter {
+ private static final String SERIALIZATION_NAME = "KMEANS";
private int mSeed = 0;
public ImageFilterKMeans() {
@@ -36,6 +37,7 @@ public class ImageFilterKMeans extends SimpleImageFilter {
public FilterRepresentation getDefaultRepresentation() {
FilterBasicRepresentation representation = (FilterBasicRepresentation) super.getDefaultRepresentation();
representation.setName("KMeans");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterKMeans.class);
representation.setMaximum(20);
representation.setMinimum(2);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
index c256686fb..0747190fa 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterNegative.java
@@ -6,13 +6,14 @@ import com.android.gallery3d.R;
import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
public class ImageFilterNegative extends ImageFilter {
-
+ private static final String SERIALIZATION_NAME = "NEGATIVE";
public ImageFilterNegative() {
mName = "Negative";
}
public FilterRepresentation getDefaultRepresentation() {
FilterRepresentation representation = new FilterDirectRepresentation("Negative");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterNegative.class);
representation.setTextId(R.string.negative);
representation.setButtonId(R.id.negativeButton);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
index cfbb560c7..69d18f805 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterRS.java
@@ -22,13 +22,14 @@ import android.support.v8.renderscript.*;
import android.util.Log;
import android.content.res.Resources;
import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.cache.CachingPipeline;
+import com.android.gallery3d.filtershow.presets.PipelineInterface;
public abstract class ImageFilterRS extends ImageFilter {
private static final String LOGTAG = "ImageFilterRS";
private boolean DEBUG = false;
private int mLastInputWidth = 0;
private int mLastInputHeight = 0;
+ private long mLastTimeCalled;
public static boolean PERF_LOGGING = false;
@@ -51,26 +52,36 @@ public abstract class ImageFilterRS extends ImageFilter {
}
protected RenderScript getRenderScriptContext() {
- return CachingPipeline.getRenderScriptContext();
+ PipelineInterface pipeline = getEnvironment().getPipeline();
+ return pipeline.getRSContext();
}
protected Allocation getInPixelsAllocation() {
- CachingPipeline pipeline = getEnvironment().getCachingPipeline();
+ PipelineInterface pipeline = getEnvironment().getPipeline();
return pipeline.getInPixelsAllocation();
}
protected Allocation getOutPixelsAllocation() {
- CachingPipeline pipeline = getEnvironment().getCachingPipeline();
+ PipelineInterface pipeline = getEnvironment().getPipeline();
return pipeline.getOutPixelsAllocation();
}
@Override
public void apply(Allocation in, Allocation out) {
long startOverAll = System.nanoTime();
+ if (PERF_LOGGING) {
+ long delay = (startOverAll - mLastTimeCalled) / 1000;
+ String msg = String.format("%s; image size %dx%d; ", getName(),
+ in.getType().getX(), in.getType().getY());
+ msg += String.format("called after %.2f ms (%.2f FPS); ",
+ delay / 1000.f, 1000000.f / delay);
+ Log.i(LOGTAG, msg);
+ }
+ mLastTimeCalled = startOverAll;
long startFilter = 0;
long endFilter = 0;
if (!mResourcesLoaded) {
- CachingPipeline pipeline = getEnvironment().getCachingPipeline();
+ PipelineInterface pipeline = getEnvironment().getPipeline();
createFilter(pipeline.getResources(), getEnvironment().getScaleFactor(),
getEnvironment().getQuality(), in);
mResourcesLoaded = true;
@@ -102,7 +113,7 @@ public abstract class ImageFilterRS extends ImageFilter {
return bitmap;
}
try {
- CachingPipeline pipeline = getEnvironment().getCachingPipeline();
+ PipelineInterface pipeline = getEnvironment().getPipeline();
if (DEBUG) {
Log.v(LOGTAG, "apply filter " + getName() + " in pipeline " + pipeline.getName());
}
@@ -137,18 +148,16 @@ public abstract class ImageFilterRS extends ImageFilter {
displayLowMemoryToast();
Log.e(LOGTAG, "not enough memory for filter " + getName(), e);
}
-
return bitmap;
}
- protected static Allocation convertBitmap(Bitmap bitmap) {
- return Allocation.createFromBitmap(CachingPipeline.getRenderScriptContext(), bitmap,
+ protected static Allocation convertBitmap(RenderScript RS, Bitmap bitmap) {
+ return Allocation.createFromBitmap(RS, bitmap,
Allocation.MipmapControl.MIPMAP_NONE,
Allocation.USAGE_SCRIPT | Allocation.USAGE_GRAPHICS_TEXTURE);
}
- private static Allocation convertRGBAtoA(Bitmap bitmap) {
- RenderScript RS = CachingPipeline.getRenderScriptContext();
+ private static Allocation convertRGBAtoA(RenderScript RS, Bitmap bitmap) {
if (RS != mRScache || mGreyConvert == null) {
mGreyConvert = new ScriptC_grey(RS, RS.getApplicationContext().getResources(),
R.raw.grey);
@@ -157,7 +166,7 @@ public abstract class ImageFilterRS extends ImageFilter {
Type.Builder tb_a8 = new Type.Builder(RS, Element.A_8(RS));
- Allocation bitmapTemp = convertBitmap(bitmap);
+ Allocation bitmapTemp = convertBitmap(RS, bitmap);
if (bitmapTemp.getType().getElement().isCompatible(Element.A_8(RS))) {
return bitmapTemp;
}
@@ -173,20 +182,20 @@ public abstract class ImageFilterRS extends ImageFilter {
}
public Allocation loadScaledResourceAlpha(int resource, int inSampleSize) {
- Resources res = CachingPipeline.getResources();
+ Resources res = getEnvironment().getPipeline().getResources();
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ALPHA_8;
options.inSampleSize = inSampleSize;
Bitmap bitmap = BitmapFactory.decodeResource(
res,
resource, options);
- Allocation ret = convertRGBAtoA(bitmap);
+ Allocation ret = convertRGBAtoA(getRenderScriptContext(), bitmap);
bitmap.recycle();
return ret;
}
public Allocation loadScaledResourceAlpha(int resource, int w, int h, int inSampleSize) {
- Resources res = CachingPipeline.getResources();
+ Resources res = getEnvironment().getPipeline().getResources();
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ALPHA_8;
options.inSampleSize = inSampleSize;
@@ -194,7 +203,7 @@ public abstract class ImageFilterRS extends ImageFilter {
res,
resource, options);
Bitmap resizeBitmap = Bitmap.createScaledBitmap(bitmap, w, h, true);
- Allocation ret = convertRGBAtoA(resizeBitmap);
+ Allocation ret = convertRGBAtoA(getRenderScriptContext(), resizeBitmap);
resizeBitmap.recycle();
bitmap.recycle();
return ret;
@@ -205,13 +214,13 @@ public abstract class ImageFilterRS extends ImageFilter {
}
public Allocation loadResource(int resource) {
- Resources res = CachingPipeline.getResources();
+ Resources res = getEnvironment().getPipeline().getResources();
final BitmapFactory.Options options = new BitmapFactory.Options();
options.inPreferredConfig = Bitmap.Config.ARGB_8888;
Bitmap bitmap = BitmapFactory.decodeResource(
res,
resource, options);
- Allocation ret = convertBitmap(bitmap);
+ Allocation ret = convertBitmap(getRenderScriptContext(), bitmap);
bitmap.recycle();
return ret;
}
@@ -232,7 +241,7 @@ public abstract class ImageFilterRS extends ImageFilter {
/**
* RS Script objects (and all other RS objects) should be cleared here
*/
- abstract protected void resetScripts();
+ public abstract void resetScripts();
/**
* Scripts values should be bound here
@@ -244,6 +253,8 @@ public abstract class ImageFilterRS extends ImageFilter {
return;
}
resetAllocations();
+ mLastInputWidth = 0;
+ mLastInputHeight = 0;
setResourcesLoaded(false);
}
}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
index 0febe4957..adc74c5ec 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSaturated.java
@@ -21,7 +21,7 @@ import com.android.gallery3d.R;
import android.graphics.Bitmap;
public class ImageFilterSaturated extends SimpleImageFilter {
-
+ private static final String SERIALIZATION_NAME = "SATURATED";
public ImageFilterSaturated() {
mName = "Saturated";
}
@@ -31,6 +31,7 @@ public class ImageFilterSaturated extends SimpleImageFilter {
FilterBasicRepresentation representation =
(FilterBasicRepresentation) super.getDefaultRepresentation();
representation.setName("Saturated");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterSaturated.class);
representation.setTextId(R.string.saturation);
representation.setButtonId(R.id.saturationButton);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
index fd67ee8fc..845290b80 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterShadows.java
@@ -21,7 +21,7 @@ import com.android.gallery3d.R;
import android.graphics.Bitmap;
public class ImageFilterShadows extends SimpleImageFilter {
-
+ private static final String SERIALIZATION_NAME = "SHADOWS";
public ImageFilterShadows() {
mName = "Shadows";
@@ -31,6 +31,7 @@ public class ImageFilterShadows extends SimpleImageFilter {
FilterBasicRepresentation representation =
(FilterBasicRepresentation) super.getDefaultRepresentation();
representation.setName("Shadows");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterShadows.class);
representation.setTextId(R.string.shadow_recovery);
representation.setButtonId(R.id.shadowRecoveryButton);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
index 76ae475ac..1dc2c0516 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterSharpen.java
@@ -19,7 +19,7 @@ package com.android.gallery3d.filtershow.filters;
import com.android.gallery3d.R;
public class ImageFilterSharpen extends ImageFilterRS {
-
+ private static final String SERIALIZATION_NAME = "SHARPEN";
private static final String LOGTAG = "ImageFilterSharpen";
private ScriptC_convolve3x3 mScript;
@@ -31,6 +31,7 @@ public class ImageFilterSharpen extends ImageFilterRS {
public FilterRepresentation getDefaultRepresentation() {
FilterRepresentation representation = new FilterBasicRepresentation("Sharpen", 0, 0, 100);
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setShowParameterValue(true);
representation.setFilterClass(ImageFilterSharpen.class);
representation.setTextId(R.string.sharpness);
@@ -52,7 +53,7 @@ public class ImageFilterSharpen extends ImageFilterRS {
}
@Override
- protected void resetScripts() {
+ public void resetScripts() {
if (mScript != null) {
mScript.destroy();
mScript = null;
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
index 37d5739a0..f265c4dcc 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterTinyPlanet.java
@@ -76,7 +76,7 @@ public class ImageFilterTinyPlanet extends SimpleImageFilter {
int w = bitmapIn.getWidth();
int h = bitmapIn.getHeight();
int outputSize = (int) (w / 2f);
- ImagePreset preset = getImagePreset();
+ ImagePreset preset = getEnvironment().getImagePreset();
Bitmap mBitmapOut = null;
if (preset != null) {
XMPMeta xmp = preset.getImageLoader().getXmpObject();
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
index ea315d326..900fd906c 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVibrance.java
@@ -21,7 +21,7 @@ import com.android.gallery3d.R;
import android.graphics.Bitmap;
public class ImageFilterVibrance extends SimpleImageFilter {
-
+ private static final String SERIALIZATION_NAME = "VIBRANCE";
public ImageFilterVibrance() {
mName = "Vibrance";
}
@@ -30,6 +30,7 @@ public class ImageFilterVibrance extends SimpleImageFilter {
FilterBasicRepresentation representation =
(FilterBasicRepresentation) super.getDefaultRepresentation();
representation.setName("Vibrance");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterVibrance.class);
representation.setTextId(R.string.vibrance);
representation.setButtonId(R.id.vibranceButton);
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
index e06f54493..cfe135033 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterVignette.java
@@ -22,7 +22,7 @@ import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import com.android.gallery3d.R;
-import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.filtershow.presets.FilterEnvironment;
public class ImageFilterVignette extends SimpleImageFilter {
private static final String LOGTAG = "ImageFilterVignette";
@@ -57,9 +57,9 @@ public class ImageFilterVignette extends SimpleImageFilter {
@Override
public Bitmap apply(Bitmap bitmap, float scaleFactor, int quality) {
- if (SIMPLE_ICONS && ImagePreset.QUALITY_ICON == quality) {
+ if (SIMPLE_ICONS && FilterEnvironment.QUALITY_ICON == quality) {
if (mOverlayBitmap == null) {
- Resources res = getEnvironment().getCachingPipeline().getResources();
+ Resources res = getEnvironment().getPipeline().getResources();
mOverlayBitmap = IconUtilities.getFXBitmap(res,
R.drawable.filtershow_icon_vignette);
}
diff --git a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
index c4c293a4b..84a14c902 100644
--- a/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
+++ b/src/com/android/gallery3d/filtershow/filters/ImageFilterWBalance.java
@@ -22,6 +22,7 @@ import com.android.gallery3d.filtershow.editors.ImageOnlyEditor;
import android.graphics.Bitmap;
public class ImageFilterWBalance extends ImageFilter {
+ private static final String SERIALIZATION_NAME = "WBALANCE";
private static final String TAG = "ImageFilterWBalance";
public ImageFilterWBalance() {
@@ -30,6 +31,7 @@ public class ImageFilterWBalance extends ImageFilter {
public FilterRepresentation getDefaultRepresentation() {
FilterRepresentation representation = new FilterDirectRepresentation("WBalance");
+ representation.setSerializationName(SERIALIZATION_NAME);
representation.setFilterClass(ImageFilterWBalance.class);
representation.setPriority(FilterRepresentation.TYPE_WBALANCE);
representation.setTextId(R.string.wbalance);
diff --git a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
index e5820a8f2..77dbd5e7b 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/GeometryMetadata.java
@@ -20,6 +20,7 @@ import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
+import android.util.Log;
import com.android.gallery3d.filtershow.cache.ImageLoader;
import com.android.gallery3d.filtershow.crop.CropExtras;
@@ -30,7 +31,14 @@ import com.android.gallery3d.filtershow.editors.EditorStraighten;
import com.android.gallery3d.filtershow.filters.FilterRepresentation;
import com.android.gallery3d.filtershow.filters.ImageFilterGeometry;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+
public class GeometryMetadata extends FilterRepresentation {
+ private static final String SERIALIZATION_NAME = "GEOM";
private static final String LOGTAG = "GeometryMetadata";
private float mScaleFactor = 1.0f;
private float mRotation = 0;
@@ -40,7 +48,26 @@ public class GeometryMetadata extends FilterRepresentation {
private FLIP mFlip = FLIP.NONE;
public enum FLIP {
- NONE, VERTICAL, HORIZONTAL, BOTH
+ NONE("N"), VERTICAL("V"), HORIZONTAL("H"), BOTH("B");
+ String mValue;
+
+ FLIP(String name) {
+ mValue = name;
+ }
+
+ public static FLIP parse(String name){
+ switch (name.charAt(0)) {
+ case 'N':
+ return NONE;
+ case 'V':
+ return VERTICAL;
+ case 'H':
+ return HORIZONTAL;
+ case 'B':
+ return BOTH;
+ };
+ return NONE;
+ }
}
// Output format data from intent extras
@@ -64,6 +91,7 @@ public class GeometryMetadata extends FilterRepresentation {
public GeometryMetadata() {
super("GeometryMetadata");
+ setSerializationName(SERIALIZATION_NAME);
setFilterClass(ImageFilterGeometry.class);
setEditorId(EditorCrop.ID);
setTextId(0);
@@ -492,4 +520,87 @@ public class GeometryMetadata extends FilterRepresentation {
representation.useParametersFrom(this);
return representation;
}
+
+ private static final String[] sParams = {
+ "Name", "ScaleFactor", "Rotation", "StraightenRotation", "CropBoundsLeft",
+ "CropBoundsTop", "CropBoundsRight", "CropBoundsBottom", "PhotoBoundsLeft",
+ "PhotoBoundsTop", "PhotoBoundsRight", "PhotoBoundsBottom", "Flip"
+ };
+
+ @Override
+ public String[][] serializeRepresentation() {
+ String[][] ret = {
+ { "Name", getName() },
+ { "ScaleFactor", Float.toString(mScaleFactor) },
+ { "Rotation", Float.toString(mRotation) },
+ { "StraightenRotation", Float.toString(mStraightenRotation) },
+ { "CropBoundsLeft", Float.toString(mCropBounds.left) },
+ { "CropBoundsTop", Float.toString(mCropBounds.top) },
+ { "CropBoundsRight", Float.toString(mCropBounds.right) },
+ { "CropBoundsBottom", Float.toString(mCropBounds.bottom) },
+ { "PhotoBoundsLeft", Float.toString(mPhotoBounds.left) },
+ { "PhotoBoundsTop", Float.toString(mPhotoBounds.top) },
+ { "PhotoBoundsRight", Float.toString(mPhotoBounds.right) },
+ { "PhotoBoundsBottom", Float.toString(mPhotoBounds.bottom) },
+ { "Flip", mFlip.mValue } };
+ return ret;
+ }
+
+ @Override
+ public void deSerializeRepresentation(String[][] rep) {
+ HashMap<String, Integer> map = new HashMap<String, Integer>();
+ for (int i = 0; i < sParams.length; i++) {
+ map.put(sParams[i], i);
+ }
+ for (int i = 0; i < rep.length; i++) {
+ String key = rep[i][0];
+ String value = rep[i][1];
+
+ switch (map.get(key)) {
+ case -1: // Unknown
+ break;
+ case 0:
+ if (!getName().equals(value)) {
+ throw new IllegalArgumentException("Not a "+getName());
+ }
+ break;
+ case 1: // "ScaleFactor", Float
+ mScaleFactor = Float.parseFloat(value);
+ break;
+ case 2: // "Rotation", Float
+ mRotation = Float.parseFloat(value);
+ break;
+ case 3: // "StraightenRotation", Float
+ mStraightenRotation = Float.parseFloat(value);
+ break;
+ case 4: // "mCropBoundsLeft", Float
+ mCropBounds.left = Float.parseFloat(value);
+ break;
+ case 5: // "mCropBoundsTop", Float
+ mCropBounds.top = Float.parseFloat(value);
+ break;
+ case 6: // "mCropBoundsRight", Float
+ mCropBounds.right = Float.parseFloat(value);
+ break;
+ case 7: // "mCropBoundsBottom", Float
+ mCropBounds.bottom = Float.parseFloat(value);
+ break;
+ case 8: // "mPhotoBoundsLeft", Float
+ mPhotoBounds.left = Float.parseFloat(value);
+ break;
+ case 9: // "mPhotoBoundsTop", Float
+ mPhotoBounds.top = Float.parseFloat(value);
+ break;
+ case 10: // "mPhotoBoundsRight", Float
+ mPhotoBounds.right = Float.parseFloat(value);
+ break;
+ case 11: // "mPhotoBoundsBottom", Float
+ mPhotoBounds.bottom = Float.parseFloat(value);
+ break;
+ case 12: // "Flip", enum
+ mFlip = FLIP.parse(value);
+ break;
+ }
+ }
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
index a0b59c0f8..ab8567f56 100644
--- a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
+++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java
@@ -20,6 +20,7 @@ import android.graphics.*;
import android.os.Handler;
import android.os.Message;
+import android.util.Log;
import com.android.gallery3d.filtershow.FilterShowActivity;
import com.android.gallery3d.filtershow.HistoryAdapter;
import com.android.gallery3d.filtershow.cache.*;
@@ -139,6 +140,7 @@ public class MasterImage implements RenderingRequestCaller {
}
public synchronized void setPreset(ImagePreset preset, boolean addToHistory) {
+ preset.showFilters();
mPreset = preset;
mPreset.setImageLoader(mLoader);
setGeometry();
diff --git a/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java b/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java
index c454c1ab6..47f8dfccb 100644
--- a/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java
+++ b/src/com/android/gallery3d/filtershow/presets/FilterEnvironment.java
@@ -18,11 +18,9 @@ package com.android.gallery3d.filtershow.presets;
import android.graphics.Bitmap;
import android.support.v8.renderscript.Allocation;
-import android.util.Log;
-import com.android.gallery3d.filtershow.cache.CachingPipeline;
import com.android.gallery3d.filtershow.filters.FilterRepresentation;
-import com.android.gallery3d.filtershow.filters.FiltersManager;
+import com.android.gallery3d.filtershow.filters.FiltersManagerInterface;
import com.android.gallery3d.filtershow.filters.ImageFilter;
import java.lang.ref.WeakReference;
@@ -33,10 +31,14 @@ public class FilterEnvironment {
private ImagePreset mImagePreset;
private float mScaleFactor;
private int mQuality;
- private FiltersManager mFiltersManager;
- private CachingPipeline mCachingPipeline;
+ private FiltersManagerInterface mFiltersManager;
+ private PipelineInterface mPipeline;
private volatile boolean mStop = false;
+ public static final int QUALITY_ICON = 0;
+ public static final int QUALITY_PREVIEW = 1;
+ public static final int QUALITY_FINAL = 2;
+
public synchronized boolean needsStop() {
return mStop;
}
@@ -98,11 +100,11 @@ public class FilterEnvironment {
return mQuality;
}
- public void setFiltersManager(FiltersManager filtersManager) {
+ public void setFiltersManager(FiltersManagerInterface filtersManager) {
mFiltersManager = filtersManager;
}
- public FiltersManager getFiltersManager() {
+ public FiltersManagerInterface getFiltersManager() {
return mFiltersManager;
}
@@ -126,12 +128,12 @@ public class FilterEnvironment {
return ret;
}
- public CachingPipeline getCachingPipeline() {
- return mCachingPipeline;
+ public PipelineInterface getPipeline() {
+ return mPipeline;
}
- public void setCachingPipeline(CachingPipeline cachingPipeline) {
- mCachingPipeline = cachingPipeline;
+ public void setPipeline(PipelineInterface cachingPipeline) {
+ mPipeline = cachingPipeline;
}
}
diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
index 2a7e601ee..84766958d 100644
--- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
+++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java
@@ -18,12 +18,19 @@ package com.android.gallery3d.filtershow.presets;
import android.graphics.Bitmap;
import android.graphics.Rect;
+import android.net.Uri;
import android.support.v8.renderscript.Allocation;
+import android.util.JsonReader;
+import android.util.JsonWriter;
import android.util.Log;
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.options.PropertyOptions;
import com.android.gallery3d.filtershow.cache.CachingPipeline;
import com.android.gallery3d.filtershow.cache.ImageLoader;
import com.android.gallery3d.filtershow.filters.BaseFiltersManager;
+import com.android.gallery3d.filtershow.filters.FiltersManager;
import com.android.gallery3d.filtershow.filters.FilterRepresentation;
import com.android.gallery3d.filtershow.filters.ImageFilter;
import com.android.gallery3d.filtershow.imageshow.GeometryMetadata;
@@ -32,6 +39,10 @@ import com.android.gallery3d.filtershow.state.State;
import com.android.gallery3d.filtershow.state.StateAdapter;
import com.android.gallery3d.util.UsageStatistics;
+import java.io.IOException;
+import java.io.StringReader;
+import java.io.StringWriter;
+import java.util.ArrayList;
import java.util.Vector;
public class ImagePreset {
@@ -39,10 +50,8 @@ public class ImagePreset {
private static final String LOGTAG = "ImagePreset";
private FilterRepresentation mBorder = null;
- public static final int QUALITY_ICON = 0;
- public static final int QUALITY_PREVIEW = 1;
- public static final int QUALITY_FINAL = 2;
public static final int STYLE_ICON = 3;
+ public static final String PRESET_NAME = "PresetName";
private ImageLoader mImageLoader = null;
@@ -210,11 +219,11 @@ public class ImagePreset {
}
for (FilterRepresentation representation : mFilters) {
if (representation.getPriority() == FilterRepresentation.TYPE_VIGNETTE
- && !representation.isNil()) {
+ && !representation.isNil()) {
return false;
}
if (representation.getPriority() == FilterRepresentation.TYPE_TINYPLANET
- && !representation.isNil()) {
+ && !representation.isNil()) {
return false;
}
}
@@ -460,7 +469,7 @@ public class ImagePreset {
if (mBorder != null && mDoApplyGeometry) {
mBorder.synchronizeRepresentation();
bitmap = environment.applyRepresentation(mBorder, bitmap);
- if (environment.getQuality() == QUALITY_FINAL) {
+ if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) {
UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
"SaveBorder", mBorder.getName(), 1);
}
@@ -468,6 +477,10 @@ public class ImagePreset {
return bitmap;
}
+ public int nbFilters() {
+ return mFilters.size();
+ }
+
public Bitmap applyFilters(Bitmap bitmap, int from, int to, FilterEnvironment environment) {
if (mDoApplyFilters) {
if (from < 0) {
@@ -483,7 +496,7 @@ public class ImagePreset {
representation.synchronizeRepresentation();
}
bitmap = environment.applyRepresentation(representation, bitmap);
- if (environment.getQuality() == QUALITY_FINAL) {
+ if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) {
UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR,
"SaveFilter", representation.getName(), 1);
}
@@ -496,17 +509,23 @@ public class ImagePreset {
return bitmap;
}
- public void applyBorder(Allocation in, Allocation out, FilterEnvironment environment) {
+ public void applyBorder(Allocation in, Allocation out,
+ boolean copyOut, FilterEnvironment environment) {
if (mBorder != null && mDoApplyGeometry) {
mBorder.synchronizeRepresentation();
// TODO: should keep the bitmap around
- Allocation bitmapIn = Allocation.createTyped(CachingPipeline.getRenderScriptContext(), in.getType());
- bitmapIn.copyFrom(out);
+ Allocation bitmapIn = in;
+ if (copyOut) {
+ bitmapIn = Allocation.createTyped(
+ CachingPipeline.getRenderScriptContext(), in.getType());
+ bitmapIn.copyFrom(out);
+ }
environment.applyRepresentation(mBorder, bitmapIn, out);
}
}
- public void applyFilters(int from, int to, Allocation in, Allocation out, FilterEnvironment environment) {
+ public void applyFilters(int from, int to, Allocation in, Allocation out,
+ FilterEnvironment environment) {
if (mDoApplyFilters) {
if (from < 0) {
from = 0;
@@ -605,4 +624,109 @@ public class ImagePreset {
return usedFilters;
}
+ public String getJsonString(String name) {
+ StringWriter swriter = new StringWriter();
+ try {
+ JsonWriter writer = new JsonWriter(swriter);
+ writeJson(writer, name);
+ writer.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ return swriter.toString();
+ }
+
+ public void writeJson(JsonWriter writer, String name) {
+ int numFilters = mFilters.size();
+ try {
+ writer.beginObject();
+ writer.name(PRESET_NAME).value(name);
+ writer.name(mGeoData.getSerializationName());
+ writer.beginObject();
+ {
+ String[][] rep = mGeoData.serializeRepresentation();
+ for (int i = 0; i < rep.length; i++) {
+ writer.name(rep[i][0]);
+ writer.value(rep[i][1]);
+ }
+ }
+ writer.endObject();
+
+ for (int i = 0; i < numFilters; i++) {
+ FilterRepresentation filter = mFilters.get(i);
+ String sname = filter.getSerializationName();
+ writer.name(sname);
+ writer.beginObject();
+ {
+ String[][] rep = filter.serializeRepresentation();
+ for (int k = 0; k < rep.length; k++) {
+ writer.name(rep[k][0]);
+ writer.value(rep[k][1]);
+ }
+ }
+ writer.endObject();
+ }
+ writer.endObject();
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public boolean readJsonFromString(String filterString) {
+ StringReader sreader = new StringReader(filterString);
+ try {
+ JsonReader reader = new JsonReader(sreader);
+ boolean ok = readJson(reader);
+ if (!ok) {
+ return false;
+ }
+ reader.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return true;
+ }
+
+ public boolean readJson(JsonReader sreader) throws IOException {
+ sreader.beginObject();
+ sreader.nextName();
+ mName = sreader.nextString();
+
+ while (sreader.hasNext()) {
+ String name = sreader.nextName();
+
+ if (mGeoData.getSerializationName().equals(name)) {
+ mGeoData.deSerializeRepresentation(read(sreader));
+ } else {
+ FilterRepresentation filter = creatFilterFromName(name);
+ if (filter == null)
+ return false;
+ filter.deSerializeRepresentation(read(sreader));
+ addFilter(filter);
+ }
+ }
+ sreader.endObject();
+ return true;
+ }
+
+ FilterRepresentation creatFilterFromName(String name) {
+ FiltersManager filtersManager = FiltersManager.getManager();
+ return filtersManager.createFilterFromName(name);
+ }
+
+ String[][] read(JsonReader reader) throws IOException {
+ ArrayList <String[]> al = new ArrayList<String[]>();
+
+ reader.beginObject();
+
+ while (reader.hasNext()) {
+ String[]kv = { reader.nextName(),reader.nextString()};
+ al.add(kv);
+
+ }
+ reader.endObject();
+ return al.toArray(new String[al.size()][]);
+ }
}
diff --git a/src/com/android/gallery3d/filtershow/presets/PipelineInterface.java b/src/com/android/gallery3d/filtershow/presets/PipelineInterface.java
new file mode 100644
index 000000000..05f0a1aa8
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/presets/PipelineInterface.java
@@ -0,0 +1,31 @@
+/*
+ * 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.gallery3d.filtershow.presets;
+
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.support.v8.renderscript.Allocation;
+import android.support.v8.renderscript.RenderScript;
+
+public interface PipelineInterface {
+ public String getName();
+ public Resources getResources();
+ public Allocation getInPixelsAllocation();
+ public Allocation getOutPixelsAllocation();
+ public boolean prepareRenderscriptAllocations(Bitmap bitmap);
+ public RenderScript getRSContext();
+}
diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
index c5851c476..b5de6929a 100644
--- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
+++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java
@@ -206,6 +206,8 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
uri = insertContent(context, sourceUri, this.destinationFile, saveFileName,
time);
}
+ XmpPresets.writeFilterXMP(context, sourceUri, this.destinationFile, preset);
+
noBitmap = false;
} catch (java.lang.OutOfMemoryError e) {
// Try 5 times before failing for good.
@@ -219,6 +221,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> {
return uri;
}
+
@Override
protected void onPostExecute(Uri result) {
if (callback != null) {
diff --git a/src/com/android/gallery3d/filtershow/tools/XmpPresets.java b/src/com/android/gallery3d/filtershow/tools/XmpPresets.java
new file mode 100644
index 000000000..be75f0253
--- /dev/null
+++ b/src/com/android/gallery3d/filtershow/tools/XmpPresets.java
@@ -0,0 +1,133 @@
+/*
+ * 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.gallery3d.filtershow.tools;
+
+import android.content.Context;
+import android.net.Uri;
+import android.util.Log;
+
+import com.adobe.xmp.XMPException;
+import com.adobe.xmp.XMPMeta;
+import com.adobe.xmp.XMPMetaFactory;
+import com.android.gallery3d.R;
+import com.android.gallery3d.common.Utils;
+import com.android.gallery3d.filtershow.imageshow.MasterImage;
+import com.android.gallery3d.filtershow.presets.ImagePreset;
+import com.android.gallery3d.util.XmpUtilHelper;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.InputStream;
+
+public class XmpPresets {
+ public static final String
+ XMP_GOOGLE_FILTER_NAMESPACE = "http://ns.google.com/photos/1.0/filter/";
+ public static final String XMP_GOOGLE_FILTER_PREFIX = "AFltr";
+ public static final String XMP_SRC_FILE_URI = "SourceFileUri";
+ public static final String XMP_FILTERSTACK = "filterstack";
+ private static final String LOGTAG = "XmpPresets";
+
+ public static class XMresults {
+ public String presetString;
+ public ImagePreset preset;
+ public Uri originalimage;
+ }
+
+ static {
+ try {
+ XMPMetaFactory.getSchemaRegistry().registerNamespace(
+ XMP_GOOGLE_FILTER_NAMESPACE, XMP_GOOGLE_FILTER_PREFIX);
+ } catch (XMPException e) {
+ Log.e(LOGTAG, "Register XMP name space failed", e);
+ }
+ }
+
+ public static void writeFilterXMP(
+ Context context, Uri srcUri, File dstFile, ImagePreset preset) {
+ InputStream is = null;
+ XMPMeta xmpMeta = null;
+ try {
+ is = context.getContentResolver().openInputStream(srcUri);
+ xmpMeta = XmpUtilHelper.extractXMPMeta(is);
+ } catch (FileNotFoundException e) {
+
+ } finally {
+ Utils.closeSilently(is);
+ }
+
+ if (xmpMeta == null) {
+ xmpMeta = XMPMetaFactory.create();
+ }
+ try {
+ xmpMeta.setProperty(XMP_GOOGLE_FILTER_NAMESPACE,
+ XMP_SRC_FILE_URI, srcUri.toString());
+ xmpMeta.setProperty(XMP_GOOGLE_FILTER_NAMESPACE,
+ XMP_FILTERSTACK, preset.getJsonString(context.getString(R.string.saved)));
+ } catch (XMPException e) {
+ Log.v(LOGTAG, "Write XMP meta to file failed:" + dstFile.getAbsolutePath());
+ return;
+ }
+
+ if (!XmpUtilHelper.writeXMPMeta(dstFile.getAbsolutePath(), xmpMeta)) {
+ Log.v(LOGTAG, "Write XMP meta to file failed:" + dstFile.getAbsolutePath());
+ }
+ }
+
+ public static XMresults extractXMPData(
+ Context context, MasterImage mMasterImage, Uri uriToEdit) {
+ XMresults ret = new XMresults();
+
+ InputStream is = null;
+ XMPMeta xmpMeta = null;
+ try {
+ is = context.getContentResolver().openInputStream(uriToEdit);
+ xmpMeta = XmpUtilHelper.extractXMPMeta(is);
+ } catch (FileNotFoundException e) {
+ } finally {
+ Utils.closeSilently(is);
+ }
+
+ if (xmpMeta == null) {
+ return null;
+ }
+
+ try {
+ String strSrcUri = xmpMeta.getPropertyString(XMP_GOOGLE_FILTER_NAMESPACE,
+ XMP_SRC_FILE_URI);
+
+ if (strSrcUri != null) {
+ String filterString = xmpMeta.getPropertyString(XMP_GOOGLE_FILTER_NAMESPACE,
+ XMP_FILTERSTACK);
+
+ Uri srcUri = Uri.parse(strSrcUri);
+ ret.originalimage = srcUri;
+
+ ret.preset = new ImagePreset(mMasterImage.getPreset());
+ ret.presetString = filterString;
+ boolean ok = ret.preset.readJsonFromString(filterString);
+ if (!ok) {
+ return null;
+ }
+ return ret;
+ }
+ } catch (XMPException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+}
diff --git a/src/com/android/gallery3d/ui/MenuExecutor.java b/src/com/android/gallery3d/ui/MenuExecutor.java
index 8f4854e10..29def0527 100644
--- a/src/com/android/gallery3d/ui/MenuExecutor.java
+++ b/src/com/android/gallery3d/ui/MenuExecutor.java
@@ -190,7 +190,7 @@ public class MenuExecutor {
setMenuItemVisible(menu, R.id.action_setas, supportSetAs);
setMenuItemVisible(menu, R.id.action_show_on_map, supportShowOnMap);
setMenuItemVisible(menu, R.id.action_edit, supportEdit);
- // setMenuItemVisible(menu, R.id.action_simple_edit, supportEdit);
+ setMenuItemVisible(menu, R.id.action_simple_edit, supportEdit);
setMenuItemVisible(menu, R.id.action_details, supportInfo);
}
diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java
index c281dd3e7..e2c5f51b9 100644
--- a/src/com/android/gallery3d/util/SaveVideoFileUtils.java
+++ b/src/com/android/gallery3d/util/SaveVideoFileUtils.java
@@ -19,6 +19,7 @@ package com.android.gallery3d.util;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.database.Cursor;
+import android.media.MediaMetadataRetriever;
import android.net.Uri;
import android.os.Environment;
import android.provider.MediaStore.Video;
@@ -95,7 +96,7 @@ public class SaveVideoFileUtils {
ContentResolver contentResolver, Uri uri ) {
long nowInMs = System.currentTimeMillis();
long nowInSec = nowInMs / 1000;
- final ContentValues values = new ContentValues(12);
+ final ContentValues values = new ContentValues(13);
values.put(Video.Media.TITLE, mDstFileInfo.mFileName);
values.put(Video.Media.DISPLAY_NAME, mDstFileInfo.mFile.getName());
values.put(Video.Media.MIME_TYPE, "video/mp4");
@@ -104,6 +105,8 @@ public class SaveVideoFileUtils {
values.put(Video.Media.DATE_ADDED, nowInSec);
values.put(Video.Media.DATA, mDstFileInfo.mFile.getAbsolutePath());
values.put(Video.Media.SIZE, mDstFileInfo.mFile.length());
+ int durationMs = retriveVideoDurationMs(mDstFileInfo.mFile.getPath());
+ values.put(Video.Media.DURATION, durationMs);
// Copy the data taken and location info from src.
String[] projection = new String[] {
VideoColumns.DATE_TAKEN,
@@ -138,4 +141,18 @@ public class SaveVideoFileUtils {
return contentResolver.insert(Video.Media.EXTERNAL_CONTENT_URI, values);
}
+ public static int retriveVideoDurationMs(String path) {
+ int durationMs = 0;
+ // Calculate the duration of the destination file.
+ MediaMetadataRetriever retriever = new MediaMetadataRetriever();
+ retriever.setDataSource(path);
+ String duration = retriever.extractMetadata(
+ MediaMetadataRetriever.METADATA_KEY_DURATION);
+ if (duration != null) {
+ durationMs = Integer.parseInt(duration);
+ }
+ retriever.release();
+ return durationMs;
+ }
+
}
diff --git a/src/com/android/photos/data/BitmapDecoder.java b/src/com/android/photos/data/BitmapDecoder.java
index a0ab4105a..f19808d06 100644
--- a/src/com/android/photos/data/BitmapDecoder.java
+++ b/src/com/android/photos/data/BitmapDecoder.java
@@ -41,7 +41,7 @@ public class BitmapDecoder {
private static final String TAG = BitmapDecoder.class.getSimpleName();
private static final int POOL_SIZE = 4;
private static final int TEMP_STORAGE_SIZE_BYTES = 16 * 1024;
- private static final int HEADER_MAX_SIZE = 16 * 1024;
+ private static final int HEADER_MAX_SIZE = 128 * 1024;
private static final Pool<BitmapFactory.Options> sOptions =
new SynchronizedPool<BitmapFactory.Options>(POOL_SIZE);