diff options
Diffstat (limited to 'src')
53 files changed, 2111 insertions, 10932 deletions
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index 5ba769a62..c5cbb7e8a 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -16,146 +16,80 @@ package com.android.camera; -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ObjectAnimator; +import android.app.Activity; +import android.content.BroadcastReceiver; import android.content.ComponentName; +import android.content.ContentResolver; 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.Drawable; +import android.graphics.drawable.ColorDrawable; +import android.net.Uri; import android.os.Bundle; +import android.os.Handler; import android.os.IBinder; -import android.provider.MediaStore; import android.provider.Settings; import android.view.KeyEvent; +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 android.widget.FrameLayout; +import android.widget.ImageView; -import com.android.camera.ui.CameraSwitcher; +import com.android.camera.data.CameraDataAdapter; +import com.android.camera.data.LocalData; +import com.android.camera.ui.CameraSwitcher.CameraSwitchListener; +import com.android.camera.ui.FilmStripView; import com.android.gallery3d.R; -import com.android.gallery3d.app.PhotoPage; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.util.LightCycleHelper; -public class CameraActivity extends ActivityBase - implements CameraSwitcher.CameraSwitchListener { +public class CameraActivity extends Activity + implements CameraSwitchListener { + + private static final String TAG = "CAM_Activity"; + 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; - CameraModule mCurrentModule; - private FrameLayout mFrame; - private ShutterButton mShutter; - private CameraSwitcher mSwitcher; - private View mCameraControls; - private View mControlsBackground; - private View mPieMenuButton; - private Drawable[] mDrawables; + 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 MotionEvent mDown; + private CameraModule 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 int mHeightOrWidth = -1; - + private boolean mSecureCamera; + private boolean mShowCameraPreview; + private int mLastRawOrientation; private MyOrientationEventListener mOrientationListener; - // The degrees of the device rotated clockwise from its natural orientation. - private int mLastRawOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; - - 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; - }}; - - private static final String TAG = "CAM_activity"; - - private static final int[] DRAW_IDS = { - R.drawable.ic_switch_camera, - R.drawable.ic_switch_video, - R.drawable.ic_switch_pan, - R.drawable.ic_switch_photosphere - }; - - @Override - public void onCreate(Bundle state) { - super.onCreate(state); - setContentView(R.layout.camera_main); - mFrame = (FrameLayout) findViewById(R.id.camera_app_root); - mDrawables = new Drawable[DRAW_IDS.length]; - for (int i = 0; i < DRAW_IDS.length; i++) { - mDrawables[i] = getResources().getDrawable(DRAW_IDS[i]); - } - init(); - if (MediaStore.INTENT_ACTION_VIDEO_CAMERA.equals(getIntent().getAction()) - || MediaStore.ACTION_VIDEO_CAPTURE.equals(getIntent().getAction())) { - mCurrentModule = new VideoModule(); - mCurrentModuleIndex = VIDEO_MODULE_INDEX; - } else { - mCurrentModule = new PhotoModule(); - mCurrentModuleIndex = PHOTO_MODULE_INDEX; - } - mCurrentModule.init(this, mFrame, true); - mSwitcher.setCurrentIndex(mCurrentModuleIndex); - mOrientationListener = new MyOrientationEventListener(this); - bindMediaSaveService(); - } - - public void init() { - boolean landscape = Util.getDisplayRotation(this) % 180 == 90; - mControlsBackground = findViewById(R.id.blocker); - mCameraControls = findViewById(R.id.camera_controls); - mShutter = (ShutterButton) findViewById(R.id.shutter_button); - mSwitcher = (CameraSwitcher) findViewById(R.id.camera_switcher); - mPieMenuButton = findViewById(R.id.menu); - int totaldrawid = (LightCycleHelper.hasLightCycleCapture(this) - ? DRAW_IDS.length : DRAW_IDS.length - 1); - if (!ApiHelper.HAS_OLD_PANORAMA) totaldrawid--; - - int[] drawids = new int[totaldrawid]; - int[] moduleids = new int[totaldrawid]; - int ix = 0; - for (int i = 0; i < mDrawables.length; i++) { - if (i == PANORAMA_MODULE_INDEX && !ApiHelper.HAS_OLD_PANORAMA) { - continue; // not enabled, so don't add to UI - } - if (i == LIGHTCYCLE_MODULE_INDEX && !LightCycleHelper.hasLightCycleCapture(this)) { - continue; // not enabled, so don't add to UI - } - moduleids[ix] = i; - drawids[ix++] = DRAW_IDS[i]; - } - mSwitcher.setIds(moduleids, drawids); - mSwitcher.setSwitchListener(this); - mSwitcher.setCurrentIndex(mCurrentModuleIndex); - } - - @Override - public void onDestroy() { - unbindMediaSaveService(); - super.onDestroy(); - } - - // Return whether the auto-rotate screen in system settings - // is turned on. - public boolean isAutoRotateScreen() { - return mAutoRotateScreen; - } + private Handler mMainHandler; private class MyOrientationEventListener - extends OrientationEventListener { + extends OrientationEventListener { public MyOrientationEventListener(Context context) { super(context); } @@ -171,155 +105,161 @@ public class CameraActivity extends ActivityBase } } - private ObjectAnimator mCameraSwitchAnimator; - - @Override - public void onCameraSelected(final int i) { - if (mPaused) return; - if (i != mCurrentModuleIndex) { - mPaused = true; - CameraScreenNail screenNail = getCameraScreenNail(); - if (screenNail != null) { - if (mCameraSwitchAnimator != null && mCameraSwitchAnimator.isRunning()) { - mCameraSwitchAnimator.cancel(); - } - mCameraSwitchAnimator = ObjectAnimator.ofFloat( - screenNail, "alpha", screenNail.getAlpha(), 0f); - mCameraSwitchAnimator.addListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - doChangeCamera(i); - } - }); - mCameraSwitchAnimator.start(); - } else { - doChangeCamera(i); + private MediaSaveService mMediaSaveService; + private ServiceConnection mConnection = new ServiceConnection() { + @Override + public void onServiceConnected(ComponentName className, IBinder b) { + mMediaSaveService = ((MediaSaveService.LocalBinder) b).getService(); + mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); } - } - } - - private void doChangeCamera(int i) { - boolean canReuse = canReuseScreenNail(); - CameraHolder.instance().keep(); - closeModule(mCurrentModule); - mCurrentModuleIndex = i; - switch (i) { - case VIDEO_MODULE_INDEX: - mCurrentModule = new VideoModule(); - break; - case PHOTO_MODULE_INDEX: - mCurrentModule = new PhotoModule(); - break; - case PANORAMA_MODULE_INDEX: - mCurrentModule = new PanoramaModule(); - break; - case LIGHTCYCLE_MODULE_INDEX: - mCurrentModule = LightCycleHelper.createPanoramaModule(); - break; - } - showPieMenuButton(mCurrentModule.needsPieMenu()); - - openModule(mCurrentModule, canReuse); - mCurrentModule.onOrientationChanged(mLastRawOrientation); - if (mMediaSaveService != null) { - mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); - } - getCameraScreenNail().setAlpha(0f); - getCameraScreenNail().setOnFrameDrawnOneShot(mOnFrameDrawn); - } - - public void showPieMenuButton(boolean show) { - if (show) { - findViewById(R.id.blocker).setVisibility(View.VISIBLE); - findViewById(R.id.menu).setVisibility(View.VISIBLE); - findViewById(R.id.on_screen_indicators).setVisibility(View.VISIBLE); - } else { - findViewById(R.id.blocker).setVisibility(View.INVISIBLE); - findViewById(R.id.menu).setVisibility(View.INVISIBLE); - findViewById(R.id.on_screen_indicators).setVisibility(View.INVISIBLE); - } - } - - private Runnable mOnFrameDrawn = new Runnable() { + @Override + public void onServiceDisconnected(ComponentName className) { + mMediaSaveService = null; + }}; + // close activity when screen turns off + private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() { @Override - public void run() { - runOnUiThread(mFadeInCameraScreenNail); + public void onReceive(Context context, Intent intent) { + finish(); } }; - private Runnable mFadeInCameraScreenNail = new Runnable() { + private FilmStripView.Listener mFilmStripListener = new FilmStripView.Listener() { + @Override + public void onDataPromoted(int dataID) { + removeData(dataID); + } - @Override - public void run() { - mCameraSwitchAnimator = ObjectAnimator.ofFloat( - getCameraScreenNail(), "alpha", 0f, 1f); - mCameraSwitchAnimator.setStartDelay(50); - mCameraSwitchAnimator.start(); - } - }; + @Override + public void onDataDemoted(int dataID) { + removeData(dataID); + } - @Override - public void onShowSwitcherPopup() { - mCurrentModule.onShowSwitcherPopup(); - } + @Override + public void onDataFullScreenChange(int dataID, boolean full) { + } - private void openModule(CameraModule module, boolean canReuse) { - module.init(this, mFrame, canReuse && canReuseScreenNail()); - mPaused = false; - module.onResumeBeforeSuper(); - module.onResumeAfterSuper(); - } + @Override + public void onSwitchMode(boolean toCamera) { + mCurrentModule.onSwitchMode(toCamera); + } + }; - private void closeModule(CameraModule module) { - module.onPauseBeforeSuper(); - module.onPauseAfterSuper(); - mFrame.removeAllViews(); + private Runnable mDeletionRunnable = new Runnable() { + @Override + public void run() { + mDataAdapter.executeDeletion(CameraActivity.this); + } + }; + + public MediaSaveService getMediaSaveService() { + return mMediaSaveService; } - public ShutterButton getShutterButton() { - return mShutter; + public void notifyNewMedia(Uri uri) { + ContentResolver cr = getContentResolver(); + String mimeType = cr.getType(uri); + if (mimeType.startsWith("video/")) { + sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO, uri)); + mDataAdapter.addNewVideo(cr, uri); + } else if (mimeType.startsWith("image/")) { + Util.broadcastNewPicture(this, uri); + mDataAdapter.addNewPhoto(cr, uri); + } else { + android.util.Log.w(TAG, "Unknown new media with MIME type:" + + mimeType + ", uri:" + uri); + } } - public void hideUI() { - mCameraControls.setVisibility(View.INVISIBLE); - hideSwitcher(); - mShutter.setVisibility(View.GONE); + private void removeData(int dataID) { + mDataAdapter.removeData(CameraActivity.this, dataID); + mMainHandler.removeCallbacks(mDeletionRunnable); + mMainHandler.postDelayed(mDeletionRunnable, 3000); } - public void showUI() { - mCameraControls.setVisibility(View.VISIBLE); - showSwitcher(); - mShutter.setVisibility(View.VISIBLE); - // Force a layout change to show shutter button - mShutter.requestLayout(); + 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); } - public void hideSwitcher() { - mSwitcher.closePopup(); - mSwitcher.setVisibility(View.INVISIBLE); + private void unbindMediaSaveService() { + if (mMediaSaveService != null) { + mMediaSaveService.setListener(null); + } + if (mConnection != null) { + unbindService(mConnection); + } } - public void showSwitcher() { - if (mCurrentModule.needsSwitcher()) { - mSwitcher.setVisibility(View.VISIBLE); + @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) + || ACTION_IMAGE_CAPTURE_SECURE.equals(action)) { + mSecureCamera = true; + } else { + mSecureCamera = intent.getBooleanExtra(SECURE_CAMERA_EXTRA, false); + } + + if (mSecureCamera) { + // Change the window flags so that secure camera can show when locked + Window win = getWindow(); + WindowManager.LayoutParams params = win.getAttributes(); + params.flags |= WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED; + win.setAttributes(params); + + // Filter for screen off so that we can finish secure camera activity + // when screen is off. + IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF); + registerReceiver(mScreenOffReceiver, 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); + mFilmStripView.setViewGap( + getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); + // 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(mFilmStripListener); + mCurrentModule = new PhotoModule(); + mCurrentModule.init(this, mRootView); + mOrientationListener = new MyOrientationEventListener(this); + mMainHandler = new Handler(getMainLooper()); + bindMediaSaveService(); } - public boolean isInCameraApp() { - return mShowCameraAppView; + 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 onConfigurationChanged(Configuration config) { - super.onConfigurationChanged(config); - mCurrentModule.onConfigurationChanged(config); + public void onUserInteraction() { + super.onUserInteraction(); + mCurrentModule.onUserInteraction(); } @Override public void onPause() { - mPaused = true; mOrientationListener.disable(); mCurrentModule.onPauseBeforeSuper(); super.onPause(); @@ -328,7 +268,6 @@ public class CameraActivity extends ActivityBase @Override public void onResume() { - mPaused = false; if (Settings.System.getInt(getContentResolver(), Settings.System.ACCELEROMETER_ROTATION, 0) == 0) {// auto-rotate off setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED); @@ -341,180 +280,186 @@ public class CameraActivity extends ActivityBase mCurrentModule.onResumeBeforeSuper(); super.onResume(); mCurrentModule.onResumeAfterSuper(); - } - private void bindMediaSaveService() { - Intent intent = new Intent(this, MediaSaveService.class); - bindService(intent, mConnection, Context.BIND_AUTO_CREATE); - } - - private void unbindMediaSaveService() { - if (mMediaSaveService != null) { - mMediaSaveService.setListener(null); - } - if (mConnection != null) { - unbindService(mConnection); - } - } - - @Override - protected void onFullScreenChanged(boolean full) { - if (full) { - showUI(); + // The loading is done in background and will update the filmstrip later. + if (!mSecureCamera) { + mDataAdapter.requestLoad(getContentResolver()); } else { - hideUI(); + // Flush out all the original data first. + mDataAdapter.flush(); + ImageView v = (ImageView) getLayoutInflater().inflate( + R.layout.secure_album_placeholder, null); + // Put a lock placeholder as the last image by setting its date to 0. + mDataAdapter.addLocalData( + new LocalData.LocalViewData( + v, + v.getDrawable().getIntrinsicWidth(), + v.getDrawable().getIntrinsicHeight(), + 0, 0)); } - super.onFullScreenChanged(full); - if (ApiHelper.HAS_ROTATION_ANIMATION) { - setRotationAnimation(full); - } - mCurrentModule.onFullScreenChanged(full); - } - - private void setRotationAnimation(boolean fullscreen) { - int rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_ROTATE; - if (fullscreen) { - rotationAnimation = WindowManager.LayoutParams.ROTATION_ANIMATION_CROSSFADE; - } - Window win = getWindow(); - WindowManager.LayoutParams winParams = win.getAttributes(); - winParams.rotationAnimation = rotationAnimation; - win.setAttributes(winParams); + setSwipingEnabled(true); } @Override - protected void onStop() { - super.onStop(); - mCurrentModule.onStop(); - getStateManager().clearTasks(); + public void onDestroy() { + unbindMediaSaveService(); + if (mSecureCamera) unregisterReceiver(mScreenOffReceiver); + super.onDestroy(); } @Override - protected void onNewIntent(Intent intent) { - super.onNewIntent(intent); - getStateManager().clearActivityResult(); + public void onConfigurationChanged(Configuration config) { + super.onConfigurationChanged(config); + mCurrentModule.onConfigurationChanged(config); } @Override - protected void installIntentFilter() { - super.installIntentFilter(); - mCurrentModule.installIntentFilter(); + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (mCurrentModule.onKeyDown(keyCode, event)) return true; + // Prevent software keyboard or voice search from showing up. + if (keyCode == KeyEvent.KEYCODE_SEARCH + || keyCode == KeyEvent.KEYCODE_MENU) { + if (event.isLongPress()) return true; + } + if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) { + return true; + } + + return super.onKeyDown(keyCode, event); } @Override - protected void onActivityResult( - int requestCode, int resultCode, Intent data) { - // Only PhotoPage understands ProxyLauncher.RESULT_USER_CANCELED - if (resultCode == ProxyLauncher.RESULT_USER_CANCELED - && !(getStateManager().getTopState() instanceof PhotoPage)) { - resultCode = RESULT_CANCELED; - } - super.onActivityResult(requestCode, resultCode, data); - // Unmap cancel vs. reset - if (resultCode == ProxyLauncher.RESULT_USER_CANCELED) { - resultCode = RESULT_CANCELED; + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (mCurrentModule.onKeyUp(keyCode, event)) return true; + if (keyCode == KeyEvent.KEYCODE_MENU && mShowCameraPreview) { + return true; } - mCurrentModule.onActivityResult(requestCode, resultCode, data); + return super.onKeyUp(keyCode, event); } - // Preview area is touched. Handle touch focus. - // Touch to focus is handled by PreviewGestures, this function call - // is no longer needed. TODO: Clean it up in the next refactor @Override - protected void onSingleTapUp(View view, int x, int y) { + public boolean dispatchTouchEvent(MotionEvent m) { + return mFilmStripView.dispatchTouchEvent(m); + } + public boolean isAutoRotateScreen() { + return mAutoRotateScreen; } - @Override - public void onBackPressed() { - if (!mCurrentModule.onBackPressed()) { - super.onBackPressed(); - } + protected void updateStorageSpace() { + mStorageSpace = Storage.getAvailableSpace(); } - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - return mCurrentModule.onKeyDown(keyCode, event) - || super.onKeyDown(keyCode, event); + protected long getStorageSpace() { + return mStorageSpace; } - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return mCurrentModule.onKeyUp(keyCode, event) - || super.onKeyUp(keyCode, event); + protected void updateStorageSpaceAndHint() { + updateStorageSpace(); + updateStorageHint(mStorageSpace); } - public void cancelActivityTouchHandling() { - if (mDown != null) { - MotionEvent cancel = MotionEvent.obtain(mDown); - cancel.setAction(MotionEvent.ACTION_CANCEL); - super.dispatchTouchEvent(cancel); - } + protected void updateStorageHint() { + updateStorageHint(mStorageSpace); } - @Override - public boolean dispatchTouchEvent(MotionEvent m) { - if (m.getActionMasked() == MotionEvent.ACTION_DOWN) { - mDown = m; + 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 ((mSwitcher != null) && mSwitcher.showsPopup() && !mSwitcher.isInsidePopup(m)) { - return mSwitcher.onTouch(null, m); - } else if ((mSwitcher != null) && mSwitcher.isInsidePopup(m)) { - return superDispatchTouchEvent(m); - } else { - return mCurrentModule.dispatchTouchEvent(m); + + 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; } } - @Override - public void startActivityForResult(Intent intent, int requestCode) { - Intent proxyIntent = new Intent(this, ProxyLauncher.class); - proxyIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - proxyIntent.putExtra(Intent.EXTRA_INTENT, intent); - super.startActivityForResult(proxyIntent, requestCode); + protected void setResultEx(int resultCode) { + mResultCodeForTesting = resultCode; + setResult(resultCode); } - public boolean superDispatchTouchEvent(MotionEvent m) { - return super.dispatchTouchEvent(m); + protected void setResultEx(int resultCode, Intent data) { + mResultCodeForTesting = resultCode; + mResultDataForTesting = data; + setResult(resultCode, data); } - // Preview texture has been copied. Now camera can be released and the - // animation can be started. - @Override - public void onPreviewTextureCopied() { - mCurrentModule.onPreviewTextureCopied(); + public int getResultCode() { + return mResultCodeForTesting; } - @Override - public void onCaptureTextureCopied() { - mCurrentModule.onCaptureTextureCopied(); + public Intent getResultData() { + return mResultDataForTesting; } - @Override - public void onUserInteraction() { - super.onUserInteraction(); - mCurrentModule.onUserInteraction(); + public boolean isSecureCamera() { + return mSecureCamera; } @Override - protected boolean updateStorageHintOnResume() { - return mCurrentModule.updateStorageHintOnResume(); + public void onCameraSelected(int i) { + if (mCurrentModuleIndex == i) return; + + CameraHolder.instance().keep(); + closeModule(mCurrentModule); + mCurrentModuleIndex = i; + switch (i) { + case VIDEO_MODULE_INDEX: + mCurrentModule = new VideoModule(); + break; + case PHOTO_MODULE_INDEX: + mCurrentModule = new PhotoModule(); + break; + case LIGHTCYCLE_MODULE_INDEX: + mCurrentModule = LightCycleHelper.createPanoramaModule(); + break; + default: + break; + } + + openModule(mCurrentModule); + mCurrentModule.onOrientationChanged(mLastRawOrientation); + if (mMediaSaveService != null) { + mCurrentModule.onMediaSaveServiceConnected(mMediaSaveService); + } } - @Override - public void updateCameraAppView() { - super.updateCameraAppView(); - mCurrentModule.updateCameraAppView(); + private void openModule(CameraModule module) { + module.init(this, mRootView); + module.onResumeBeforeSuper(); + module.onResumeAfterSuper(); } - private boolean canReuseScreenNail() { - return mCurrentModuleIndex == PHOTO_MODULE_INDEX - || mCurrentModuleIndex == VIDEO_MODULE_INDEX - || mCurrentModuleIndex == LIGHTCYCLE_MODULE_INDEX; + private void closeModule(CameraModule module) { + module.onPauseBeforeSuper(); + module.onPauseAfterSuper(); + ((ViewGroup) mRootView).removeAllViews(); } @Override - public boolean isPanoramaActivity() { - return (mCurrentModuleIndex == PANORAMA_MODULE_INDEX); + public void onShowSwitcherPopup() { + } + + public void setSwipingEnabled(boolean enable) { + mDataAdapter.setCameraPreviewLock(!enable); } // Accessor methods for getting latency times used in performance testing @@ -552,12 +497,4 @@ public class CameraActivity extends ActivityBase return (mCurrentModule instanceof VideoModule) ? ((VideoModule) mCurrentModule).isRecording() : false; } - - public CameraScreenNail getCameraScreenNail() { - return (CameraScreenNail) mCameraScreenNail; - } - - public MediaSaveService getMediaSaveService() { - return mMediaSaveService; - } } diff --git a/src/com/android/camera/CameraModule.java b/src/com/android/camera/CameraModule.java index 3275d5f51..bcfe98d65 100644 --- a/src/com/android/camera/CameraModule.java +++ b/src/com/android/camera/CameraModule.java @@ -24,9 +24,9 @@ import android.view.View; public interface CameraModule { - public void init(CameraActivity activity, View frame, boolean reuseScreenNail); + public void init(CameraActivity activity, View frame); - public void onFullScreenChanged(boolean full); + public void onSwitchMode(boolean toCamera); public void onPauseBeforeSuper(); @@ -52,8 +52,6 @@ public interface CameraModule { public void onSingleTapUp(View view, int x, int y); - public boolean dispatchTouchEvent(MotionEvent m); - public void onPreviewTextureCopied(); public void onCaptureTextureCopied(); @@ -64,10 +62,6 @@ public interface CameraModule { public void updateCameraAppView(); - public boolean needsSwitcher(); - - public boolean needsPieMenu(); - public void onOrientationChanged(int orientation); public void onShowSwitcherPopup(); diff --git a/src/com/android/camera/Mosaic.java b/src/com/android/camera/Mosaic.java deleted file mode 100644 index 78876c384..000000000 --- a/src/com/android/camera/Mosaic.java +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.camera; - -/** - * The Java interface to JNI calls regarding mosaic stitching. - * - * A high-level usage is: - * - * Mosaic mosaic = new Mosaic(); - * mosaic.setSourceImageDimensions(width, height); - * mosaic.reset(blendType); - * - * while ((pixels = hasNextImage()) != null) { - * mosaic.setSourceImage(pixels); - * } - * - * mosaic.createMosaic(highRes); - * byte[] result = mosaic.getFinalMosaic(); - * - */ -public class Mosaic { - /** - * In this mode, the images are stitched together in the same spatial arrangement as acquired - * i.e. if the user follows a curvy trajectory, the image boundary of the resulting mosaic will - * be curved in the same manner. This mode is useful if the user wants to capture a mosaic as - * if "painting" the scene using the smart-phone device and does not want any corrective warps - * to distort the captured images. - */ - public static final int BLENDTYPE_FULL = 0; - - /** - * This mode is the same as BLENDTYPE_FULL except that the resulting mosaic is rotated - * to balance the first and last images to be approximately at the same vertical offset in the - * output mosaic. This is useful when acquiring a mosaic by a typical panning-like motion to - * remove a one-sided curve in the mosaic (typically due to the camera not staying horizontal - * during the video capture) and convert it to a more symmetrical "smiley-face" like output. - */ - public static final int BLENDTYPE_PAN = 1; - - /** - * This mode compensates for typical "smiley-face" like output in longer mosaics and creates - * a rectangular mosaic with minimal black borders (by unwrapping the mosaic onto an imaginary - * cylinder). If the user follows a curved trajectory (instead of a perfect panning trajectory), - * the resulting mosaic here may suffer from some image distortions in trying to map the - * trajectory to a cylinder. - */ - public static final int BLENDTYPE_CYLINDERPAN = 2; - - /** - * This mode is basically BLENDTYPE_CYLINDERPAN plus doing a rectangle cropping before returning - * the mosaic. The mode is useful for making the resulting mosaic have a rectangle shape. - */ - public static final int BLENDTYPE_HORIZONTAL =3; - - /** - * This strip type will use the default thin strips where the strips are - * spaced according to the image capture rate. - */ - public static final int STRIPTYPE_THIN = 0; - - /** - * This strip type will use wider strips for blending. The strip separation - * is controlled by a threshold on the native side. Since the strips are - * wider, there is an additional cross-fade blending step to make the seam - * boundaries smoother. Since this mode uses lesser image frames, it is - * computationally more efficient than the thin strip mode. - */ - public static final int STRIPTYPE_WIDE = 1; - - /** - * Return flags returned by createMosaic() are one of the following. - */ - public static final int MOSAIC_RET_OK = 1; - public static final int MOSAIC_RET_ERROR = -1; - public static final int MOSAIC_RET_CANCELLED = -2; - public static final int MOSAIC_RET_LOW_TEXTURE = -3; - public static final int MOSAIC_RET_FEW_INLIERS = 2; - - - static { - System.loadLibrary("jni_mosaic"); - } - - /** - * Allocate memory for the image frames at the given resolution. - * - * @param width width of the input frames in pixels - * @param height height of the input frames in pixels - */ - public native void allocateMosaicMemory(int width, int height); - - /** - * Free memory allocated by allocateMosaicMemory. - * - */ - public native void freeMosaicMemory(); - - /** - * Pass the input image frame to the native layer. Each time the a new - * source image t is set, the transformation matrix from the first source - * image to t is computed and returned. - * - * @param pixels source image of NV21 format. - * @return Float array of length 11; first 9 entries correspond to the 3x3 - * transformation matrix between the first frame and the passed frame; - * the 10th entry is the number of the passed frame, where the counting - * starts from 1; and the 11th entry is the returning code, whose value - * is one of those MOSAIC_RET_* returning flags defined above. - */ - public native float[] setSourceImage(byte[] pixels); - - /** - * This is an alternative to the setSourceImage function above. This should - * be called when the image data is already on the native side in a fixed - * byte array. In implementation, this array is filled by the GL thread - * using glReadPixels directly from GPU memory (where it is accessed by - * an associated SurfaceTexture). - * - * @return Float array of length 11; first 9 entries correspond to the 3x3 - * transformation matrix between the first frame and the passed frame; - * the 10th entry is the number of the passed frame, where the counting - * starts from 1; and the 11th entry is the returning code, whose value - * is one of those MOSAIC_RET_* returning flags defined above. - */ - public native float[] setSourceImageFromGPU(); - - /** - * Set the type of blending. - * - * @param type the blending type defined in the class. {BLENDTYPE_FULL, - * BLENDTYPE_PAN, BLENDTYPE_CYLINDERPAN, BLENDTYPE_HORIZONTAL} - */ - public native void setBlendingType(int type); - - /** - * Set the type of strips to use for blending. - * @param type the blending strip type to use {STRIPTYPE_THIN, - * STRIPTYPE_WIDE}. - */ - public native void setStripType(int type); - - /** - * Tell the native layer to create the final mosaic after all the input frame - * data have been collected. - * The case of generating high-resolution mosaic may take dozens of seconds to finish. - * - * @param value True means generating a high-resolution mosaic - - * which is based on the original images set in setSourceImage(). - * False means generating a low-resolution version - - * which is based on 1/4 downscaled images from the original images. - * @return Returns a status code suggesting if the mosaic building was - * successful, in error, or was cancelled by the user. - */ - public native int createMosaic(boolean value); - - /** - * Get the data for the created mosaic. - * - * @return Returns an integer array which contains the final mosaic in the ARGB_8888 format. - * The first MosaicWidth*MosaicHeight values contain the image data, followed by 2 - * integers corresponding to the values MosaicWidth and MosaicHeight respectively. - */ - public native int[] getFinalMosaic(); - - /** - * Get the data for the created mosaic. - * - * @return Returns a byte array which contains the final mosaic in the NV21 format. - * The first MosaicWidth*MosaicHeight*1.5 values contain the image data, followed by - * 8 bytes which pack the MosaicWidth and MosaicHeight integers into 4 bytes each - * respectively. - */ - public native byte[] getFinalMosaicNV21(); - - /** - * Reset the state of the frame arrays which maintain the captured frame data. - * Also re-initializes the native mosaic object to make it ready for capturing a new mosaic. - */ - public native void reset(); - - /** - * Get the progress status of the mosaic computation process. - * @param hires Boolean flag to select whether to report progress of the - * low-res or high-res mosaicer. - * @param cancelComputation Boolean flag to allow cancelling the - * mosaic computation when needed from the GUI end. - * @return Returns a number from 0-100 where 50 denotes that the mosaic - * computation is 50% done. - */ - public native int reportProgress(boolean hires, boolean cancelComputation); -} diff --git a/src/com/android/camera/MosaicFrameProcessor.java b/src/com/android/camera/MosaicFrameProcessor.java deleted file mode 100644 index efd4ad2ae..000000000 --- a/src/com/android/camera/MosaicFrameProcessor.java +++ /dev/null @@ -1,237 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.camera; - -import android.util.Log; - -/** - * Class to handle the processing of each frame by Mosaicer. - */ -public class MosaicFrameProcessor { - private static final String TAG = "MosaicFrameProcessor"; - private static final int NUM_FRAMES_IN_BUFFER = 2; - private static final int MAX_NUMBER_OF_FRAMES = 100; - private static final int MOSAIC_RET_CODE_INDEX = 10; - private static final int FRAME_COUNT_INDEX = 9; - private static final int X_COORD_INDEX = 2; - private static final int Y_COORD_INDEX = 5; - private static final int HR_TO_LR_DOWNSAMPLE_FACTOR = 4; - private static final int WINDOW_SIZE = 3; - - private Mosaic mMosaicer; - private boolean mIsMosaicMemoryAllocated = false; - private float mTranslationLastX; - private float mTranslationLastY; - - private int mFillIn = 0; - private int mTotalFrameCount = 0; - private int mLastProcessFrameIdx = -1; - private int mCurrProcessFrameIdx = -1; - private boolean mFirstRun; - - // Panning rate is in unit of percentage of image content translation per - // frame. Use moving average to calculate the panning rate. - private float mPanningRateX; - private float mPanningRateY; - - private float[] mDeltaX = new float[WINDOW_SIZE]; - private float[] mDeltaY = new float[WINDOW_SIZE]; - private int mOldestIdx = 0; - private float mTotalTranslationX = 0f; - private float mTotalTranslationY = 0f; - - private ProgressListener mProgressListener; - - private int mPreviewWidth; - private int mPreviewHeight; - private int mPreviewBufferSize; - - private static MosaicFrameProcessor sMosaicFrameProcessor; // singleton - - public interface ProgressListener { - public void onProgress(boolean isFinished, float panningRateX, float panningRateY, - float progressX, float progressY); - } - - public static MosaicFrameProcessor getInstance() { - if (sMosaicFrameProcessor == null) { - sMosaicFrameProcessor = new MosaicFrameProcessor(); - } - return sMosaicFrameProcessor; - } - - private MosaicFrameProcessor() { - mMosaicer = new Mosaic(); - } - - public void setProgressListener(ProgressListener listener) { - mProgressListener = listener; - } - - public int reportProgress(boolean hires, boolean cancel) { - return mMosaicer.reportProgress(hires, cancel); - } - - public void initialize(int previewWidth, int previewHeight, int bufSize) { - mPreviewWidth = previewWidth; - mPreviewHeight = previewHeight; - mPreviewBufferSize = bufSize; - setupMosaicer(mPreviewWidth, mPreviewHeight, mPreviewBufferSize); - setStripType(Mosaic.STRIPTYPE_WIDE); - // no need to call reset() here. reset() should be called by the client - // after this initialization before calling other methods of this object. - } - - public void clear() { - if (mIsMosaicMemoryAllocated) { - mMosaicer.freeMosaicMemory(); - mIsMosaicMemoryAllocated = false; - } - synchronized (this) { - notify(); - } - } - - public boolean isMosaicMemoryAllocated() { - return mIsMosaicMemoryAllocated; - } - - public void setStripType(int type) { - mMosaicer.setStripType(type); - } - - private void setupMosaicer(int previewWidth, int previewHeight, int bufSize) { - Log.v(TAG, "setupMosaicer w, h=" + previewWidth + ',' + previewHeight + ',' + bufSize); - - if (mIsMosaicMemoryAllocated) throw new RuntimeException("MosaicFrameProcessor in use!"); - mIsMosaicMemoryAllocated = true; - mMosaicer.allocateMosaicMemory(previewWidth, previewHeight); - } - - public void reset() { - // reset() can be called even if MosaicFrameProcessor is not initialized. - // Only counters will be changed. - mFirstRun = true; - mTotalFrameCount = 0; - mFillIn = 0; - mTotalTranslationX = 0; - mTranslationLastX = 0; - mTotalTranslationY = 0; - mTranslationLastY = 0; - mPanningRateX = 0; - mPanningRateY = 0; - mLastProcessFrameIdx = -1; - mCurrProcessFrameIdx = -1; - for (int i = 0; i < WINDOW_SIZE; ++i) { - mDeltaX[i] = 0f; - mDeltaY[i] = 0f; - } - mMosaicer.reset(); - } - - public int createMosaic(boolean highRes) { - return mMosaicer.createMosaic(highRes); - } - - public byte[] getFinalMosaicNV21() { - return mMosaicer.getFinalMosaicNV21(); - } - - // Processes the last filled image frame through the mosaicer and - // updates the UI to show progress. - // When done, processes and displays the final mosaic. - public void processFrame() { - if (!mIsMosaicMemoryAllocated) { - // clear() is called and buffers are cleared, stop computation. - // This can happen when the onPause() is called in the activity, but still some frames - // are not processed yet and thus the callback may be invoked. - return; - } - - mCurrProcessFrameIdx = mFillIn; - mFillIn = ((mFillIn + 1) % NUM_FRAMES_IN_BUFFER); - - // Check that we are trying to process a frame different from the - // last one processed (useful if this class was running asynchronously) - if (mCurrProcessFrameIdx != mLastProcessFrameIdx) { - mLastProcessFrameIdx = mCurrProcessFrameIdx; - - // TODO: make the termination condition regarding reaching - // MAX_NUMBER_OF_FRAMES solely determined in the library. - if (mTotalFrameCount < MAX_NUMBER_OF_FRAMES) { - // If we are still collecting new frames for the current mosaic, - // process the new frame. - calculateTranslationRate(); - - // Publish progress of the ongoing processing - if (mProgressListener != null) { - mProgressListener.onProgress(false, mPanningRateX, mPanningRateY, - mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth, - mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight); - } - } else { - if (mProgressListener != null) { - mProgressListener.onProgress(true, mPanningRateX, mPanningRateY, - mTranslationLastX * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewWidth, - mTranslationLastY * HR_TO_LR_DOWNSAMPLE_FACTOR / mPreviewHeight); - } - } - } - } - - public void calculateTranslationRate() { - float[] frameData = mMosaicer.setSourceImageFromGPU(); - int ret_code = (int) frameData[MOSAIC_RET_CODE_INDEX]; - mTotalFrameCount = (int) frameData[FRAME_COUNT_INDEX]; - float translationCurrX = frameData[X_COORD_INDEX]; - float translationCurrY = frameData[Y_COORD_INDEX]; - - if (mFirstRun) { - // First time: no need to update delta values. - mTranslationLastX = translationCurrX; - mTranslationLastY = translationCurrY; - mFirstRun = false; - return; - } - - // Moving average: remove the oldest translation/deltaTime and - // add the newest translation/deltaTime in - int idx = mOldestIdx; - mTotalTranslationX -= mDeltaX[idx]; - mTotalTranslationY -= mDeltaY[idx]; - mDeltaX[idx] = Math.abs(translationCurrX - mTranslationLastX); - mDeltaY[idx] = Math.abs(translationCurrY - mTranslationLastY); - mTotalTranslationX += mDeltaX[idx]; - mTotalTranslationY += mDeltaY[idx]; - - // The panning rate is measured as the rate of the translation percentage in - // image width/height. Take the horizontal panning rate for example, the image width - // used in finding the translation is (PreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR). - // To get the horizontal translation percentage, the horizontal translation, - // (translationCurrX - mTranslationLastX), is divided by the - // image width. We then get the rate by dividing the translation percentage with the - // number of frames. - mPanningRateX = mTotalTranslationX / - (mPreviewWidth / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE; - mPanningRateY = mTotalTranslationY / - (mPreviewHeight / HR_TO_LR_DOWNSAMPLE_FACTOR) / WINDOW_SIZE; - - mTranslationLastX = translationCurrX; - mTranslationLastY = translationCurrY; - mOldestIdx = (mOldestIdx + 1) % WINDOW_SIZE; - } -} diff --git a/src/com/android/camera/MosaicPreviewRenderer.java b/src/com/android/camera/MosaicPreviewRenderer.java deleted file mode 100644 index 26ce733aa..000000000 --- a/src/com/android/camera/MosaicPreviewRenderer.java +++ /dev/null @@ -1,265 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.camera; - -import android.annotation.TargetApi; -import android.graphics.SurfaceTexture; -import android.os.ConditionVariable; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Looper; -import android.os.Message; -import android.util.Log; - -import com.android.gallery3d.common.ApiHelper; - -import javax.microedition.khronos.egl.EGL10; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.egl.EGLContext; -import javax.microedition.khronos.egl.EGLDisplay; -import javax.microedition.khronos.egl.EGLSurface; -import javax.microedition.khronos.opengles.GL10; - -@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture -public class MosaicPreviewRenderer { - private static final String TAG = "MosaicPreviewRenderer"; - private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; - private static final boolean DEBUG = false; - - private int mWidth; // width of the view in UI - private int mHeight; // height of the view in UI - - private boolean mIsLandscape = true; - private final float[] mTransformMatrix = new float[16]; - - private ConditionVariable mEglThreadBlockVar = new ConditionVariable(); - private HandlerThread mEglThread; - private EGLHandler mEglHandler; - - private EGLConfig mEglConfig; - private EGLDisplay mEglDisplay; - private EGLContext mEglContext; - private EGLSurface mEglSurface; - private SurfaceTexture mMosaicOutputSurfaceTexture; - private SurfaceTexture mInputSurfaceTexture; - private EGL10 mEgl; - private GL10 mGl; - - private class EGLHandler extends Handler { - public static final int MSG_INIT_EGL_SYNC = 0; - public static final int MSG_SHOW_PREVIEW_FRAME_SYNC = 1; - public static final int MSG_SHOW_PREVIEW_FRAME = 2; - public static final int MSG_ALIGN_FRAME_SYNC = 3; - public static final int MSG_RELEASE = 4; - - public EGLHandler(Looper looper) { - super(looper); - } - - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_INIT_EGL_SYNC: - doInitGL(); - mEglThreadBlockVar.open(); - break; - case MSG_SHOW_PREVIEW_FRAME_SYNC: - doShowPreviewFrame(); - mEglThreadBlockVar.open(); - break; - case MSG_SHOW_PREVIEW_FRAME: - doShowPreviewFrame(); - break; - case MSG_ALIGN_FRAME_SYNC: - doAlignFrame(); - mEglThreadBlockVar.open(); - break; - case MSG_RELEASE: - doRelease(); - mEglThreadBlockVar.open(); - break; - } - } - - private void doAlignFrame() { - mInputSurfaceTexture.updateTexImage(); - mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); - - MosaicRenderer.setWarping(true); - // Call preprocess to render it to low-res and high-res RGB textures. - MosaicRenderer.preprocess(mTransformMatrix); - // Now, transfer the textures from GPU to CPU memory for processing - MosaicRenderer.transferGPUtoCPU(); - MosaicRenderer.updateMatrix(); - draw(); - mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); - } - - private void doShowPreviewFrame() { - mInputSurfaceTexture.updateTexImage(); - mInputSurfaceTexture.getTransformMatrix(mTransformMatrix); - - MosaicRenderer.setWarping(false); - // Call preprocess to render it to low-res and high-res RGB textures. - MosaicRenderer.preprocess(mTransformMatrix); - MosaicRenderer.updateMatrix(); - draw(); - mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); - } - - private void doInitGL() { - // These are copied from GLSurfaceView - mEgl = (EGL10) EGLContext.getEGL(); - mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); - if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { - throw new RuntimeException("eglGetDisplay failed"); - } - int[] version = new int[2]; - if (!mEgl.eglInitialize(mEglDisplay, version)) { - throw new RuntimeException("eglInitialize failed"); - } else { - Log.v(TAG, "EGL version: " + version[0] + '.' + version[1]); - } - int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; - mEglConfig = chooseConfig(mEgl, mEglDisplay); - mEglContext = mEgl.eglCreateContext(mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, - attribList); - - if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { - throw new RuntimeException("failed to createContext"); - } - mEglSurface = mEgl.eglCreateWindowSurface( - mEglDisplay, mEglConfig, mMosaicOutputSurfaceTexture, null); - if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { - throw new RuntimeException("failed to createWindowSurface"); - } - - if (!mEgl.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { - throw new RuntimeException("failed to eglMakeCurrent"); - } - - mGl = (GL10) mEglContext.getGL(); - - mInputSurfaceTexture = new SurfaceTexture(MosaicRenderer.init()); - MosaicRenderer.reset(mWidth, mHeight, mIsLandscape); - } - - private void doRelease() { - mEgl.eglDestroySurface(mEglDisplay, mEglSurface); - mEgl.eglDestroyContext(mEglDisplay, mEglContext); - mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, - EGL10.EGL_NO_CONTEXT); - mEgl.eglTerminate(mEglDisplay); - mEglSurface = null; - mEglContext = null; - mEglDisplay = null; - releaseSurfaceTexture(mInputSurfaceTexture); - mEglThread.quit(); - } - - @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) - private void releaseSurfaceTexture(SurfaceTexture st) { - if (ApiHelper.HAS_RELEASE_SURFACE_TEXTURE) { - st.release(); - } - } - - // Should be called from other thread. - public void sendMessageSync(int msg) { - mEglThreadBlockVar.close(); - sendEmptyMessage(msg); - mEglThreadBlockVar.block(); - } - - } - - public MosaicPreviewRenderer(SurfaceTexture tex, int w, int h, boolean isLandscape) { - mMosaicOutputSurfaceTexture = tex; - mWidth = w; - mHeight = h; - mIsLandscape = isLandscape; - - mEglThread = new HandlerThread("PanoramaRealtimeRenderer"); - mEglThread.start(); - mEglHandler = new EGLHandler(mEglThread.getLooper()); - - // We need to sync this because the generation of surface texture for input is - // done here and the client will continue with the assumption that the - // generation is completed. - mEglHandler.sendMessageSync(EGLHandler.MSG_INIT_EGL_SYNC); - } - - public void release() { - mEglHandler.sendMessageSync(EGLHandler.MSG_RELEASE); - } - - public void showPreviewFrameSync() { - mEglHandler.sendMessageSync(EGLHandler.MSG_SHOW_PREVIEW_FRAME_SYNC); - } - - public void showPreviewFrame() { - mEglHandler.sendEmptyMessage(EGLHandler.MSG_SHOW_PREVIEW_FRAME); - } - - public void alignFrameSync() { - mEglHandler.sendMessageSync(EGLHandler.MSG_ALIGN_FRAME_SYNC); - } - - public SurfaceTexture getInputSurfaceTexture() { - return mInputSurfaceTexture; - } - - private void draw() { - MosaicRenderer.step(); - } - - private static void checkEglError(String prompt, EGL10 egl) { - int error; - while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) { - Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error)); - } - } - - private static final int EGL_OPENGL_ES2_BIT = 4; - private static final int[] CONFIG_SPEC = new int[] { - EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, - EGL10.EGL_RED_SIZE, 8, - EGL10.EGL_GREEN_SIZE, 8, - EGL10.EGL_BLUE_SIZE, 8, - EGL10.EGL_NONE - }; - - private static EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { - int[] numConfig = new int[1]; - if (!egl.eglChooseConfig(display, CONFIG_SPEC, null, 0, numConfig)) { - throw new IllegalArgumentException("eglChooseConfig failed"); - } - - int numConfigs = numConfig[0]; - if (numConfigs <= 0) { - throw new IllegalArgumentException("No configs match configSpec"); - } - - EGLConfig[] configs = new EGLConfig[numConfigs]; - if (!egl.eglChooseConfig( - display, CONFIG_SPEC, configs, numConfigs, numConfig)) { - throw new IllegalArgumentException("eglChooseConfig#2 failed"); - } - - return configs[0]; - } -} diff --git a/src/com/android/camera/MosaicRenderer.java b/src/com/android/camera/MosaicRenderer.java deleted file mode 100644 index c50ca0d52..000000000 --- a/src/com/android/camera/MosaicRenderer.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.camera; - -/** - * The Java interface to JNI calls regarding mosaic preview rendering. - * - */ -public class MosaicRenderer -{ - static - { - System.loadLibrary("jni_mosaic"); - } - - /** - * Function to be called in onSurfaceCreated() to initialize - * the GL context, load and link the shaders and create the - * program. Returns a texture ID to be used for SurfaceTexture. - * - * @return textureID the texture ID of the newly generated texture to - * be assigned to the SurfaceTexture object. - */ - public static native int init(); - - /** - * Pass the drawing surface's width and height to initialize the - * renderer viewports and FBO dimensions. - * - * @param width width of the drawing surface in pixels. - * @param height height of the drawing surface in pixels. - * @param isLandscapeOrientation is the orientation of the activity layout in landscape. - */ - public static native void reset(int width, int height, boolean isLandscapeOrientation); - - /** - * Calling this function will render the SurfaceTexture to a new 2D texture - * using the provided STMatrix. - * - * @param stMatrix texture coordinate transform matrix obtained from the - * Surface texture - */ - public static native void preprocess(float[] stMatrix); - - /** - * This function calls glReadPixels to transfer both the low-res and high-res - * data from the GPU memory to the CPU memory for further processing by the - * mosaicing library. - */ - public static native void transferGPUtoCPU(); - - /** - * Function to be called in onDrawFrame() to update the screen with - * the new frame data. - */ - public static native void step(); - - /** - * Call this function when a new low-res frame has been processed by - * the mosaicing library. This will tell the renderer library to - * update its texture and warping transformation. Any calls to step() - * after this call will use the new image frame and transformation data. - */ - public static native void updateMatrix(); - - /** - * This function allows toggling between showing the input image data - * (without applying any warp) and the warped image data. For running - * the renderer as a viewfinder, we set the flag to false. To see the - * preview mosaic, we set the flag to true. - * - * @param flag boolean flag to set the warping to true or false. - */ - public static native void setWarping(boolean flag); -} diff --git a/src/com/android/camera/NewCameraActivity.java b/src/com/android/camera/NewCameraActivity.java deleted file mode 100644 index 59b366419..000000000 --- a/src/com/android/camera/NewCameraActivity.java +++ /dev/null @@ -1,433 +0,0 @@ -/* - * 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.ContentResolver; -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.net.Uri; -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 android.widget.ImageView; - -import com.android.camera.data.CameraDataAdapter; -import com.android.camera.data.LocalData; -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 static final String TAG = "CAM_Activity"; - 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; - } - - public void notifyNewMedia(Uri uri) { - ContentResolver cr = getContentResolver(); - String mimeType = cr.getType(uri); - if (mimeType.startsWith("video/")) { - sendBroadcast(new Intent(Util.ACTION_NEW_VIDEO, uri)); - mDataAdapter.addNewVideo(cr, uri); - } else if (mimeType.startsWith("image/")) { - Util.broadcastNewPicture(this, uri); - mDataAdapter.addNewPhoto(cr, uri); - } else { - android.util.Log.w(TAG, "Unknown new media with MIME type:" - + mimeType + ", uri:" + uri); - } - } - - 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() { - if (mMediaSaveService != null) { - mMediaSaveService.setListener(null); - } - if (mConnection != 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; - } 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); - mFilmStripView.setViewGap( - getResources().getDimensionPixelSize(R.dimen.camera_film_strip_gap)); - // 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); - } - - @Override - public void onDataFullScreenChange(int dataID, boolean full) { - } - }); - 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. - if (!mSecureCamera) { - mDataAdapter.requestLoad(getContentResolver()); - } else { - // Flush out all the original data first. - mDataAdapter.flush(); - ImageView v = (ImageView) getLayoutInflater().inflate( - R.layout.secure_album_placeholder, null); - // Put a lock placeholder as the last image by setting its date to 0. - mDataAdapter.addLocalData( - new LocalData.LocalViewData( - v, - v.getDrawable().getIntrinsicWidth(), - v.getDrawable().getIntrinsicHeight(), - 0, 0)); - } - setSwipingEnabled(true); - } - - @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) { - 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() { - } - - public void setSwipingEnabled(boolean enable) { - mDataAdapter.setCameraPreviewLock(!enable); - } - - // Accessor methods for getting latency times used in performance testing - public long getAutoFocusTime() { - return (mCurrentModule instanceof PhotoModule) ? - ((PhotoModule) mCurrentModule).mAutoFocusTime : -1; - } - - public long getShutterLag() { - return (mCurrentModule instanceof PhotoModule) ? - ((PhotoModule) mCurrentModule).mShutterLag : -1; - } - - public long getShutterToPictureDisplayedTime() { - return (mCurrentModule instanceof PhotoModule) ? - ((PhotoModule) mCurrentModule).mShutterToPictureDisplayedTime : -1; - } - - public long getPictureDisplayedToJpegCallbackTime() { - return (mCurrentModule instanceof PhotoModule) ? - ((PhotoModule) mCurrentModule).mPictureDisplayedToJpegCallbackTime : -1; - } - - public long getJpegCallbackFinishTime() { - return (mCurrentModule instanceof PhotoModule) ? - ((PhotoModule) mCurrentModule).mJpegCallbackFinishTime : -1; - } - - public long getCaptureStartTime() { - return (mCurrentModule instanceof PhotoModule) ? - ((PhotoModule) mCurrentModule).mCaptureStartTime : -1; - } - - public boolean isRecording() { - return (mCurrentModule instanceof VideoModule) ? - ((VideoModule) mCurrentModule).isRecording() : false; - } -} diff --git a/src/com/android/camera/NewCameraModule.java b/src/com/android/camera/NewCameraModule.java deleted file mode 100644 index 35452bb3c..000000000 --- a/src/com/android/camera/NewCameraModule.java +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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 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 deleted file mode 100644 index c63aff458..000000000 --- a/src/com/android/camera/NewPhotoMenu.java +++ /dev/null @@ -1,200 +0,0 @@ -/* - * 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.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; - -import java.util.Locale; - -public class NewPhotoMenu extends PieController - implements CountdownTimerPopup.Listener, - ListPrefSettingPopup.Listener { - private static String TAG = "CAM_photomenu"; - - 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 deleted file mode 100644 index cd3281476..000000000 --- a/src/com/android/camera/NewPhotoModule.java +++ /dev/null @@ -1,2032 +0,0 @@ -/* - * 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.CropActivity; -import com.android.gallery3d.filtershow.crop.CropExtras; -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; - private static final int CAPTURE_ANIMATION_DONE = 13; - - // 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; - - // The activity is going to switch to the specified camera id. This is - // needed because texture copy is done in GL thread. -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 Runnable mFlashRunnable = new Runnable() { - @Override - public void run() { - animateFlash(); - } - }; - - 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 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) { - mActivity.notifyNewMedia(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; - } - case CAPTURE_ANIMATION_DONE: { - mUI.enablePreviewThumb(false); - 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(); - - // reset zoom value index - mZoomValue = 0; - 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(); - } - } - - private final class ShutterCallback - implements android.hardware.Camera.ShutterCallback { - - private boolean mAnimateFlash; - - public ShutterCallback(boolean animateFlash) { - mAnimateFlash = animateFlash; - } - - @Override - public void onShutter() { - mShutterCallbackTime = System.currentTimeMillis(); - mShutterLag = mShutterCallbackTime - mCaptureStartTime; - Log.v(TAG, "mShutterLag = " + mShutterLag + "ms"); - if (mAnimateFlash) { - mActivity.runOnUiThread(mFlashRunnable); - } - } - } - - 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(); - mUI.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 - mHandler.removeMessages(CAPTURE_ANIMATION_DONE); - ((CameraScreenNail) mActivity.mCameraScreenNail).animateSlide(); - mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, - CaptureAnimManager.getAnimationDuration()); - } */ - 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.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); - mUI.enablePreviewThumb(true); - mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, - CaptureAnimManager.getAnimationDuration()); - } */ - } - - @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; - // 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(new ShutterCallback(!animateBefore), - mRawPictureCallback, mPostViewPictureCallback, - new JpegPictureCallback(loc), mCameraState, - mFocusManager.getFocusState()); - - mNamedImages.nameNewImage(mContentResolver, mCaptureStartTime); - - mFaceDetectionStarted = false; - setCameraState(SNAPSHOT_IN_PROGRESS); - UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, - UsageStatistics.ACTION_CAPTURE_DONE, "Photo"); - 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(CropActivity.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) { - 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 (mSceneMode == Util.SCENE_MODE_HDR) { - mUI.hideSwitcher(); - mUI.setSwipingEnabled(false); - } - // 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. - int[] fpsRange = Util.getMaxPreviewFpsRange(mParameters); - if (fpsRange.length > 0) { - mParameters.setPreviewFpsRange( - fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX], - fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]); - } - - 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 - if (mHandler.getLooper() == Looper.myLooper()) { - // On UI thread only, not when camera starts up - setupPreview(); - } else { - 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 deleted file mode 100644 index 2f29e440f..000000000 --- a/src/com/android/camera/NewPhotoUI.java +++ /dev/null @@ -1,787 +0,0 @@ -/* - * 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, - FocusUI, TextureView.SurfaceTextureListener, - LocationManager.Listener, - FaceDetectionListener { - - 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 View mPreviewThumb; - - 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); - mRenderOverlay.addRenderer(mPieRenderer); - } - - if (mMenu == null) { - mMenu = new NewPhotoMenu(mActivity, this, mPieRenderer); - mMenu.setListener(listener); - } - mMenu.initialize(prefGroup); - - if (mZoomRenderer == null) { - mZoomRenderer = new ZoomRenderer(mActivity); - mRenderOverlay.addRenderer(mZoomRenderer); - } - - if (mGestures == null) { - // this will handle gesture disambiguation and dispatching - mGestures = new NewPreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); - mRenderOverlay.setGestures(mGestures); - } - mGestures.setZoomEnabled(params.isZoomSupported()); - mGestures.setRenderOverlay(mRenderOverlay); - 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); - mPreviewThumb = mActivity.findViewById(R.id.preview_thumb); - mPreviewThumb.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - // TODO: go to filmstrip - // mActivity.gotoGallery(); - } - }); - 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) { - } - - 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 void enablePreviewThumb(boolean enabled) { - if (enabled) { - mPreviewThumb.setVisibility(View.VISIBLE); - } else { - mPreviewThumb.setVisibility(View.GONE); - } - } - - 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; - } - onShowSwitcherPopup(); - return ret; - } - - protected void showPostCaptureAlert() { - mOnScreenIndicators.setVisibility(View.GONE); - mMenuButton.setVisibility(View.GONE); - Util.fadeIn(mReviewDoneButton); - mShutterButton.setVisibility(View.INVISIBLE); - Util.fadeIn(mReviewRetakeButton); - pauseFaceDetection(); - } - - protected void hidePostCaptureAlert() { - mOnScreenIndicators.setVisibility(View.VISIBLE); - mMenuButton.setVisibility(View.VISIBLE); - Util.fadeOut(mReviewDoneButton); - mShutterButton.setVisibility(View.VISIBLE); - Util.fadeOut(mReviewRetakeButton); - resumeFaceDetection(); - } - - 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) { - setSwipingEnabled(false); - dismissPopup(); - if (mFaceView != null) { - mFaceView.setBlockDraw(true); - } - } - - @Override - public void onPieClosed() { - setSwipingEnabled(true); - if (mFaceView != null) { - mFaceView.setBlockDraw(false); - } - } - - public void setSwipingEnabled(boolean enable) { - mActivity.setSwipingEnabled(enable); - } - - 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); - } - -} diff --git a/src/com/android/camera/NewPreviewGestures.java b/src/com/android/camera/NewPreviewGestures.java deleted file mode 100644 index 339c4b33f..000000000 --- a/src/com/android/camera/NewPreviewGestures.java +++ /dev/null @@ -1,263 +0,0 @@ -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.GestureDetector; -import android.view.MotionEvent; -import android.view.ScaleGestureDetector; -import android.view.View; -import android.view.ViewConfiguration; - -import com.android.camera.PreviewGestures.SingleTapListener; -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; - -/* NewPreviewGestures disambiguates touch events received on RenderOverlay - * and dispatch them to the proper recipient (i.e. zoom renderer or pie renderer). - * Touch events on CameraControls will be handled by framework. - * */ -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 RenderOverlay mOverlay; - private PieRenderer mPie; - private ZoomRenderer mZoom; - private MotionEvent mDown; - private MotionEvent mCurrent; - private ScaleGestureDetector mScale; - private int mMode; - private int mSlop; - private int mTapTimeout; - private boolean mZoomEnabled; - private boolean mEnabled; - private boolean mZoomOnly; - private int mOrientation; - private GestureDetector mGestureDetector; - - private GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { - @Override - public void onLongPress (MotionEvent e) { - // Open pie - if (mPie != null && !mPie.showsItems()) { - openPie(); - } - } - - @Override - public boolean onSingleTapUp (MotionEvent e) { - // Tap to focus when pie is not open - if (mPie == null || !mPie.showsItems()) { - mTapListener.onSingleTapUp(null, (int) e.getX(), (int) e.getY()); - return true; - } - return false; - } - - @Override - public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { - if (mMode == MODE_ZOOM) return false; - int deltaX = (int) (e1.getX() - e2.getX()); - int deltaY = (int) (e1.getY() - e2.getY()); - if (deltaY > 2 * deltaX && deltaY > -2 * deltaX) { - // Open pie on swipe up - if (mPie != null && !mPie.showsItems()) { - openPie(); - return true; - } - } - return false; - } - }; - - private Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - if (msg.what == MSG_PIE) { - mMode = MODE_PIE; - openPie(); - } - } - }; - - public interface SingleTapListener { - public void onSingleTapUp(View v, int x, int y); - } - - public NewPreviewGestures(NewCameraActivity ctx, SingleTapListener tapListener, - ZoomRenderer zoom, PieRenderer pie) { - 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; - mGestureDetector = new GestureDetector(mGestureListener); - } - - public void setRenderOverlay(RenderOverlay overlay) { - mOverlay = overlay; - } - - public void setOrientation(int orientation) { - mOrientation = orientation; - } - - public void setEnabled(boolean enabled) { - mEnabled = enabled; - } - - public void setZoomEnabled(boolean enable) { - mZoomEnabled = enable; - } - - public void setZoomOnly(boolean zoom) { - mZoomOnly = zoom; - } - - public boolean isEnabled() { - return mEnabled; - } - - public boolean dispatchTouch(MotionEvent m) { - if (!mEnabled) { - return false; - } - mCurrent = m; - if (MotionEvent.ACTION_DOWN == m.getActionMasked()) { - mMode = MODE_NONE; - mDown = MotionEvent.obtain(m); - } - - // If pie is open, redirects all the touch events to pie. - if (mPie != null && mPie.isOpen()) { - return sendToPie(m); - } - - // If pie is not open, send touch events to gesture detector and scale - // listener to recognize the gesture. - mGestureDetector.onTouchEvent(m); - if (mZoom != null) { - mScale.onTouchEvent(m); - if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) { - mMode = MODE_ZOOM; - if (mZoomEnabled) { - // Start showing zoom UI as soon as there is a second finger down - mZoom.onScaleBegin(mScale); - } - } else if (MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) { - mZoom.onScaleEnd(mScale); - } - } - return true; - } - - // 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 MotionEvent makeCancelEvent(MotionEvent m) { - MotionEvent c = MotionEvent.obtain(m); - c.setAction(MotionEvent.ACTION_CANCEL); - return c; - } - - private void openPie() { - mGestureDetector.onTouchEvent(makeCancelEvent(mDown)); - mScale.onTouchEvent(makeCancelEvent(mDown)); - mOverlay.directDispatchTouch(mDown, mPie); - } - - private boolean sendToPie(MotionEvent m) { - return mOverlay.directDispatchTouch(m, mPie); - } - - // OnScaleGestureListener implementation - @Override - public boolean onScale(ScaleGestureDetector detector) { - return mZoom.onScale(detector); - } - - @Override - public boolean onScaleBegin(ScaleGestureDetector detector) { - if (mPie == null || !mPie.isOpen()) { - mMode = MODE_ZOOM; - mGestureDetector.onTouchEvent(makeCancelEvent(mCurrent)); - if (!mZoomEnabled) return false; - return mZoom.onScaleBegin(detector); - } - return false; - } - - @Override - public void onScaleEnd(ScaleGestureDetector detector) { - mZoom.onScaleEnd(detector); - } -} - diff --git a/src/com/android/camera/NewVideoMenu.java b/src/com/android/camera/NewVideoMenu.java deleted file mode 100644 index 70f73ec39..000000000 --- a/src/com/android/camera/NewVideoMenu.java +++ /dev/null @@ -1,205 +0,0 @@ -/* - * 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 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 deleted file mode 100644 index 8bec332f9..000000000 --- a/src/com/android/camera/NewVideoModule.java +++ /dev/null @@ -1,2253 +0,0 @@ -/* - * 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.MediaColumns; -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 CAPTURE_ANIMATION_DONE = 11; - - 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 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 final MediaSaveService.OnMediaSavedListener mOnVideoSavedListener = - new MediaSaveService.OnMediaSavedListener() { - @Override - public void onMediaSaved(Uri uri) { - if (uri != null) { - mActivity.sendBroadcast( - new Intent(Util.ACTION_NEW_VIDEO, uri)); - Util.broadcastNewPicture(mActivity, uri); - } - } - }; - - private final MediaSaveService.OnMediaSavedListener mOnPhotoSavedListener = - 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; - } - - case CAPTURE_ANIMATION_DONE: { - mUI.enablePreviewThumb(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, mPreferences); - - // 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); - - mUI.enablePreviewThumb(true); - - // Make sure to disable the thumbnail preview after the - // animation is done to disable the click target. - mHandler.removeMessages(CAPTURE_ANIMATION_DONE); - mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, - CaptureAnimManager.getAnimationDuration()); - } - } - } - - 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); - - 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(); - } - - 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(9); - mCurrentVideoValues.put(Video.Media.TITLE, title); - mCurrentVideoValues.put(Video.Media.DISPLAY_NAME, filename); - mCurrentVideoValues.put(Video.Media.DATE_TAKEN, dateTaken); - mCurrentVideoValues.put(MediaColumns.DATE_MODIFIED, dateTaken / 1000); - 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()); - } - mVideoFilename = tmpPath; - Log.v(TAG, "New video filename: " + mVideoFilename); - } - - private void saveVideo() { - if (mVideoFileDescriptor == null) { - long duration = SystemClock.uptimeMillis() - mRecordingStartTime; - if (duration > 0) { - if (mCaptureTimeLapse) { - duration = getTimeLapseVideoLength(duration); - } - } else { - Log.w(TAG, "Video duration <= 0 : " + duration); - } - mActivity.getMediaSaveService().addVideo(mCurrentVideoFilename, - duration, mCurrentVideoValues, - mOnVideoSavedListener, mContentResolver); - } - mCurrentVideoValues = null; - } - - 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"); - mUI.enablePreviewThumb(false); - mUI.setSwipingEnabled(false); - - mActivity.updateStorageSpaceAndHint(); - if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) { - Log.v(TAG, "Storage issue, ignore the start request"); - return; - } - - if (!mCameraDevice.waitDone()) 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"); - 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 saveVideo() 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) { - saveVideo(); - } - } - // 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); - int[] fpsRange = Util.getMaxPreviewFpsRange(mParameters); - if (fpsRange.length > 0) { - mParameters.setPreviewFpsRange( - fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX], - fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]); - } else { - 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) { - saveVideo(); - 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(); - } - } 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, mPreferences); - } - } - - 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 - mZoomValue = 0; - 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, mPreferences); - } - - // 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(); - } - - 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, mOnPhotoSavedListener, 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(); - } - - @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 deleted file mode 100644 index 9e2c10810..000000000 --- a/src/com/android/camera/NewVideoUI.java +++ /dev/null @@ -1,706 +0,0 @@ -/* - * 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, - 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 OnScreenIndicators mOnScreenIndicators; - private RotateLayout mRecordingTimeRect; - private final Object mLock = new Object(); - private SurfaceTexture mSurfaceTexture; - private VideoController mController; - private int mZoomMax; - private List<Integer> mZoomRatios; - private View mPreviewThumb; - - 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 = new OnScreenIndicators(mActivity, - mActivity.findViewById(R.id.on_screen_indicators)); - mOnScreenIndicators.resetToDefault(); - 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); - mRenderOverlay.setGestures(mGestures); - } - mGestures.setRenderOverlay(mRenderOverlay); - - mPreviewThumb = mActivity.findViewById(R.id.preview_thumb); - mPreviewThumb.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View v) { - // TODO: Go to filmstrip view - } - }); - } - - 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, ComboPreferences prefs) { - mOnScreenIndicators.updateFlashOnScreenIndicator(param.getFlashMode()); - boolean location = RecordLocationPreference.get( - prefs, mActivity.getContentResolver()); - mOnScreenIndicators.updateLocationIndicator(location); - - } - - 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) { - // In review mode, we do not want to bring up the camera UI - if (mController.isInReviewMode()) return; - - 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) { - setSwipingEnabled(false); - dismissPopup(false, true); - } - - @Override - public void onPieClosed() { - setSwipingEnabled(true); - } - - public void setSwipingEnabled(boolean enable) { - mActivity.setSwipingEnabled(enable); - } - - 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()) { - mGestures.setZoomEnabled(false); - return; - } - mGestures.setZoomEnabled(true); - 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; - } - - public void setRecordingTime(String text) { - mRecordingTimeView.setText(text); - } - - public void setRecordingTimeTextColor(int color) { - mRecordingTimeView.setTextColor(color); - } - - public boolean isVisible() { - return mTextureView.getVisibility() == View.VISIBLE; - } - - /** - * Enable or disable the preview thumbnail for click events. - */ - public void enablePreviewThumb(boolean enabled) { - if (enabled) { - mPreviewThumb.setVisibility(View.VISIBLE); - } else { - mPreviewThumb.setVisibility(View.GONE); - } - } - - 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() { - } - } - - 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/PanoProgressBar.java b/src/com/android/camera/PanoProgressBar.java deleted file mode 100644 index 8dfb3660b..000000000 --- a/src/com/android/camera/PanoProgressBar.java +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.camera; - -import android.content.Context; -import android.graphics.Canvas; -import android.graphics.Paint; -import android.graphics.RectF; -import android.util.AttributeSet; -import android.widget.ImageView; - -class PanoProgressBar extends ImageView { - @SuppressWarnings("unused") - private static final String TAG = "PanoProgressBar"; - public static final int DIRECTION_NONE = 0; - public static final int DIRECTION_LEFT = 1; - public static final int DIRECTION_RIGHT = 2; - private float mProgress = 0; - private float mMaxProgress = 0; - private float mLeftMostProgress = 0; - private float mRightMostProgress = 0; - private float mProgressOffset = 0; - private float mIndicatorWidth = 0; - private int mDirection = 0; - private final Paint mBackgroundPaint = new Paint(); - private final Paint mDoneAreaPaint = new Paint(); - private final Paint mIndicatorPaint = new Paint(); - private float mWidth; - private float mHeight; - private RectF mDrawBounds; - private OnDirectionChangeListener mListener = null; - - public interface OnDirectionChangeListener { - public void onDirectionChange(int direction); - } - - public PanoProgressBar(Context context, AttributeSet attrs) { - super(context, attrs); - mDoneAreaPaint.setStyle(Paint.Style.FILL); - mDoneAreaPaint.setAlpha(0xff); - - mBackgroundPaint.setStyle(Paint.Style.FILL); - mBackgroundPaint.setAlpha(0xff); - - mIndicatorPaint.setStyle(Paint.Style.FILL); - mIndicatorPaint.setAlpha(0xff); - - mDrawBounds = new RectF(); - } - - public void setOnDirectionChangeListener(OnDirectionChangeListener l) { - mListener = l; - } - - private void setDirection(int direction) { - if (mDirection != direction) { - mDirection = direction; - if (mListener != null) { - mListener.onDirectionChange(mDirection); - } - invalidate(); - } - } - - public int getDirection() { - return mDirection; - } - - @Override - public void setBackgroundColor(int color) { - mBackgroundPaint.setColor(color); - invalidate(); - } - - public void setDoneColor(int color) { - mDoneAreaPaint.setColor(color); - invalidate(); - } - - public void setIndicatorColor(int color) { - mIndicatorPaint.setColor(color); - invalidate(); - } - - @Override - protected void onSizeChanged(int w, int h, int oldw, int oldh) { - mWidth = w; - mHeight = h; - mDrawBounds.set(0, 0, mWidth, mHeight); - } - - public void setMaxProgress(int progress) { - mMaxProgress = progress; - } - - public void setIndicatorWidth(float w) { - mIndicatorWidth = w; - invalidate(); - } - - public void setRightIncreasing(boolean rightIncreasing) { - if (rightIncreasing) { - mLeftMostProgress = 0; - mRightMostProgress = 0; - mProgressOffset = 0; - setDirection(DIRECTION_RIGHT); - } else { - mLeftMostProgress = mWidth; - mRightMostProgress = mWidth; - mProgressOffset = mWidth; - setDirection(DIRECTION_LEFT); - } - invalidate(); - } - - public void setProgress(int progress) { - // The panning direction will be decided after user pan more than 10 degrees in one - // direction. - if (mDirection == DIRECTION_NONE) { - if (progress > 10) { - setRightIncreasing(true); - } else if (progress < -10) { - setRightIncreasing(false); - } - } - // mDirection might be modified by setRightIncreasing() above. Need to check again. - if (mDirection != DIRECTION_NONE) { - mProgress = progress * mWidth / mMaxProgress + mProgressOffset; - // value bounds. - mProgress = Math.min(mWidth, Math.max(0, mProgress)); - if (mDirection == DIRECTION_RIGHT) { - // The right most progress is adjusted. - mRightMostProgress = Math.max(mRightMostProgress, mProgress); - } - if (mDirection == DIRECTION_LEFT) { - // The left most progress is adjusted. - mLeftMostProgress = Math.min(mLeftMostProgress, mProgress); - } - invalidate(); - } - } - - public void reset() { - mProgress = 0; - mProgressOffset = 0; - setDirection(DIRECTION_NONE); - invalidate(); - } - - @Override - protected void onDraw(Canvas canvas) { - // the background - canvas.drawRect(mDrawBounds, mBackgroundPaint); - if (mDirection != DIRECTION_NONE) { - // the progress area - canvas.drawRect(mLeftMostProgress, mDrawBounds.top, mRightMostProgress, - mDrawBounds.bottom, mDoneAreaPaint); - // the indication bar - float l; - float r; - if (mDirection == DIRECTION_RIGHT) { - l = Math.max(mProgress - mIndicatorWidth, 0f); - r = mProgress; - } else { - l = mProgress; - r = Math.min(mProgress + mIndicatorWidth, mWidth); - } - canvas.drawRect(l, mDrawBounds.top, r, mDrawBounds.bottom, mIndicatorPaint); - } - - // draw the mask image on the top for shaping. - super.onDraw(canvas); - } -} diff --git a/src/com/android/camera/PanoUtil.java b/src/com/android/camera/PanoUtil.java deleted file mode 100644 index e50eaccc8..000000000 --- a/src/com/android/camera/PanoUtil.java +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.camera; - -import java.text.SimpleDateFormat; -import java.util.Date; - -public class PanoUtil { - public static String createName(String format, long dateTaken) { - Date date = new Date(dateTaken); - SimpleDateFormat dateFormat = new SimpleDateFormat(format); - return dateFormat.format(date); - } - - // TODO: Add comments about the range of these two arguments. - public static double calculateDifferenceBetweenAngles(double firstAngle, - double secondAngle) { - double difference1 = (secondAngle - firstAngle) % 360; - if (difference1 < 0) { - difference1 += 360; - } - - double difference2 = (firstAngle - secondAngle) % 360; - if (difference2 < 0) { - difference2 += 360; - } - - return Math.min(difference1, difference2); - } - - public static void decodeYUV420SPQuarterRes(int[] rgb, byte[] yuv420sp, int width, int height) { - final int frameSize = width * height; - - for (int j = 0, ypd = 0; j < height; j += 4) { - int uvp = frameSize + (j >> 1) * width, u = 0, v = 0; - for (int i = 0; i < width; i += 4, ypd++) { - int y = (0xff & (yuv420sp[j * width + i])) - 16; - if (y < 0) { - y = 0; - } - if ((i & 1) == 0) { - v = (0xff & yuv420sp[uvp++]) - 128; - u = (0xff & yuv420sp[uvp++]) - 128; - uvp += 2; // Skip the UV values for the 4 pixels skipped in between - } - int y1192 = 1192 * y; - int r = (y1192 + 1634 * v); - int g = (y1192 - 833 * v - 400 * u); - int b = (y1192 + 2066 * u); - - if (r < 0) { - r = 0; - } else if (r > 262143) { - r = 262143; - } - if (g < 0) { - g = 0; - } else if (g > 262143) { - g = 262143; - } - if (b < 0) { - b = 0; - } else if (b > 262143) { - b = 262143; - } - - rgb[ypd] = 0xff000000 | ((r << 6) & 0xff0000) | ((g >> 2) & 0xff00) | - ((b >> 10) & 0xff); - } - } - } -} diff --git a/src/com/android/camera/PanoramaModule.java b/src/com/android/camera/PanoramaModule.java deleted file mode 100644 index 007ea7a4c..000000000 --- a/src/com/android/camera/PanoramaModule.java +++ /dev/null @@ -1,1304 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.camera; - -import android.annotation.TargetApi; -import android.content.ContentResolver; -import android.content.Context; -import android.content.Intent; -import android.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.Canvas; -import android.graphics.ImageFormat; -import android.graphics.Matrix; -import android.graphics.PixelFormat; -import android.graphics.Rect; -import android.graphics.SurfaceTexture; -import android.graphics.YuvImage; -import android.graphics.drawable.BitmapDrawable; -import android.graphics.drawable.Drawable; -import android.hardware.Camera.Parameters; -import android.hardware.Camera.Size; -import android.location.Location; -import android.net.Uri; -import android.os.AsyncTask; -import android.os.Handler; -import android.os.Message; -import android.os.PowerManager; -import android.util.Log; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.MotionEvent; -import android.view.OrientationEventListener; -import android.view.View; -import android.view.View.OnClickListener; -import android.view.ViewGroup; -import android.view.WindowManager; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import com.android.camera.CameraManager.CameraProxy; -import com.android.camera.ui.LayoutChangeNotifier; -import com.android.camera.ui.LayoutNotifyView; -import com.android.camera.ui.PopupManager; -import com.android.camera.ui.Rotatable; -import com.android.gallery3d.R; -import com.android.gallery3d.common.ApiHelper; -import com.android.gallery3d.exif.ExifInterface; -import com.android.gallery3d.exif.ExifTag; -import com.android.gallery3d.ui.GLRootView; -import com.android.gallery3d.util.UsageStatistics; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.List; -import java.util.TimeZone; - -/** - * Activity to handle panorama capturing. - */ -@TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) // uses SurfaceTexture -public class PanoramaModule implements CameraModule, - SurfaceTexture.OnFrameAvailableListener, - ShutterButton.OnShutterButtonListener, - LayoutChangeNotifier.Listener { - - public static final int DEFAULT_SWEEP_ANGLE = 160; - public static final int DEFAULT_BLEND_MODE = Mosaic.BLENDTYPE_HORIZONTAL; - public static final int DEFAULT_CAPTURE_PIXELS = 960 * 720; - - private static final int MSG_LOW_RES_FINAL_MOSAIC_READY = 1; - private static final int MSG_GENERATE_FINAL_MOSAIC_ERROR = 2; - private static final int MSG_END_DIALOG_RESET_TO_PREVIEW = 3; - private static final int MSG_CLEAR_SCREEN_DELAY = 4; - private static final int MSG_CONFIG_MOSAIC_PREVIEW = 5; - private static final int MSG_RESET_TO_PREVIEW = 6; - - private static final int SCREEN_DELAY = 2 * 60 * 1000; - - private static final String TAG = "CAM PanoModule"; - private static final int PREVIEW_STOPPED = 0; - private static final int PREVIEW_ACTIVE = 1; - private static final int CAPTURE_STATE_VIEWFINDER = 0; - private static final int CAPTURE_STATE_MOSAIC = 1; - // The unit of speed is degrees per frame. - private static final float PANNING_SPEED_THRESHOLD = 2.5f; - - private ContentResolver mContentResolver; - - private GLRootView mGLRootView; - private ViewGroup mPanoLayout; - private LinearLayout mCaptureLayout; - private View mReviewLayout; - private ImageView mReview; - private View mCaptureIndicator; - private PanoProgressBar mPanoProgressBar; - private PanoProgressBar mSavingProgressBar; - private Matrix mProgressDirectionMatrix = new Matrix(); - private float[] mProgressAngle = new float[2]; - private LayoutNotifyView mPreviewArea; - private View mLeftIndicator; - private View mRightIndicator; - private MosaicPreviewRenderer mMosaicPreviewRenderer; - private Object mRendererLock = new Object(); - private TextView mTooFastPrompt; - private ShutterButton mShutterButton; - private Object mWaitObject = new Object(); - - private String mPreparePreviewString; - private String mDialogTitle; - private String mDialogOkString; - private String mDialogPanoramaFailedString; - private String mDialogWaitingPreviousString; - - private int mIndicatorColor; - private int mIndicatorColorFast; - private int mReviewBackground; - - private boolean mUsingFrontCamera; - private int mPreviewWidth; - private int mPreviewHeight; - private int mCameraState; - private int mCaptureState; - private PowerManager.WakeLock mPartialWakeLock; - private MosaicFrameProcessor mMosaicFrameProcessor; - private boolean mMosaicFrameProcessorInitialized; - private AsyncTask <Void, Void, Void> mWaitProcessorTask; - private long mTimeTaken; - private Handler mMainHandler; - private SurfaceTexture mCameraTexture; - private boolean mThreadRunning; - private boolean mCancelComputation; - private float mHorizontalViewAngle; - private float mVerticalViewAngle; - - // Prefer FOCUS_MODE_INFINITY to FOCUS_MODE_CONTINUOUS_VIDEO because of - // getting a better image quality by the former. - private String mTargetFocusMode = Parameters.FOCUS_MODE_INFINITY; - - private PanoOrientationEventListener mOrientationEventListener; - // The value could be 0, 90, 180, 270 for the 4 different orientations measured in clockwise - // respectively. - private int mDeviceOrientation; - private int mDeviceOrientationAtCapture; - private int mCameraOrientation; - private int mOrientationCompensation; - - private RotateDialogController mRotateDialog; - - private SoundClips.Player mSoundPlayer; - - private Runnable mOnFrameAvailableRunnable; - - private CameraActivity mActivity; - private View mRootView; - private CameraProxy mCameraDevice; - private boolean mPaused; - private boolean mIsCreatingRenderer; - - private LocationManager mLocationManager; - private ComboPreferences mPreferences; - - private class MosaicJpeg { - public MosaicJpeg(byte[] data, int width, int height) { - this.data = data; - this.width = width; - this.height = height; - this.isValid = true; - } - - public MosaicJpeg() { - this.data = null; - this.width = 0; - this.height = 0; - this.isValid = false; - } - - public final byte[] data; - public final int width; - public final int height; - public final boolean isValid; - } - - private class PanoOrientationEventListener extends OrientationEventListener { - public PanoOrientationEventListener(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; - mDeviceOrientation = Util.roundOrientation(orientation, mDeviceOrientation); - // When the screen is unlocked, display rotation may change. Always - // calculate the up-to-date orientationCompensation. - int orientationCompensation = mDeviceOrientation - + Util.getDisplayRotation(mActivity) % 360; - if (mOrientationCompensation != orientationCompensation) { - mOrientationCompensation = orientationCompensation; - mActivity.getGLRoot().requestLayoutContentPane(); - } - } - } - - @Override - public void init(CameraActivity activity, View parent, boolean reuseScreenNail) { - mActivity = activity; - mRootView = parent; - - createContentView(); - - mContentResolver = mActivity.getContentResolver(); - if (reuseScreenNail) { - mActivity.reuseCameraScreenNail(true); - } else { - mActivity.createCameraScreenNail(true); - } - - // This runs in UI thread. - mOnFrameAvailableRunnable = new Runnable() { - @Override - public void run() { - // Frames might still be available after the activity is paused. - // If we call onFrameAvailable after pausing, the GL thread will crash. - if (mPaused) return; - - MosaicPreviewRenderer renderer = null; - synchronized (mRendererLock) { - if (mMosaicPreviewRenderer == null) { - return; - } - renderer = mMosaicPreviewRenderer; - } - if (mGLRootView.getVisibility() != View.VISIBLE) { - renderer.showPreviewFrameSync(); - mGLRootView.setVisibility(View.VISIBLE); - } else { - if (mCaptureState == CAPTURE_STATE_VIEWFINDER) { - renderer.showPreviewFrame(); - } else { - renderer.alignFrameSync(); - mMosaicFrameProcessor.processFrame(); - } - } - } - }; - - PowerManager pm = (PowerManager) mActivity.getSystemService(Context.POWER_SERVICE); - mPartialWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "Panorama"); - - mOrientationEventListener = new PanoOrientationEventListener(mActivity); - - mMosaicFrameProcessor = MosaicFrameProcessor.getInstance(); - - Resources appRes = mActivity.getResources(); - mPreparePreviewString = appRes.getString(R.string.pano_dialog_prepare_preview); - mDialogTitle = appRes.getString(R.string.pano_dialog_title); - mDialogOkString = appRes.getString(R.string.dialog_ok); - mDialogPanoramaFailedString = appRes.getString(R.string.pano_dialog_panorama_failed); - mDialogWaitingPreviousString = appRes.getString(R.string.pano_dialog_waiting_previous); - - mGLRootView = (GLRootView) mActivity.getGLRoot(); - - mPreferences = new ComboPreferences(mActivity); - CameraSettings.upgradeGlobalPreferences(mPreferences.getGlobal()); - mLocationManager = new LocationManager(mActivity, null); - - mMainHandler = new Handler() { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MSG_LOW_RES_FINAL_MOSAIC_READY: - onBackgroundThreadFinished(); - showFinalMosaic((Bitmap) msg.obj); - saveHighResMosaic(); - break; - case MSG_GENERATE_FINAL_MOSAIC_ERROR: - onBackgroundThreadFinished(); - if (mPaused) { - resetToPreview(); - } else { - mRotateDialog.showAlertDialog( - mDialogTitle, mDialogPanoramaFailedString, - mDialogOkString, new Runnable() { - @Override - public void run() { - resetToPreview(); - }}, - null, null); - } - clearMosaicFrameProcessorIfNeeded(); - break; - case MSG_END_DIALOG_RESET_TO_PREVIEW: - onBackgroundThreadFinished(); - resetToPreview(); - clearMosaicFrameProcessorIfNeeded(); - break; - case MSG_CLEAR_SCREEN_DELAY: - mActivity.getWindow().clearFlags(WindowManager.LayoutParams. - FLAG_KEEP_SCREEN_ON); - break; - case MSG_CONFIG_MOSAIC_PREVIEW: - configMosaicPreview(msg.arg1, msg.arg2); - break; - case MSG_RESET_TO_PREVIEW: - resetToPreview(); - break; - } - } - }; - } - - @Override - public boolean dispatchTouchEvent(MotionEvent m) { - return mActivity.superDispatchTouchEvent(m); - } - - private void setupCamera() throws CameraHardwareException, CameraDisabledException { - openCamera(); - Parameters parameters = mCameraDevice.getParameters(); - setupCaptureParams(parameters); - configureCamera(parameters); - } - - private void releaseCamera() { - if (mCameraDevice != null) { - mCameraDevice.setPreviewCallbackWithBuffer(null); - CameraHolder.instance().release(); - mCameraDevice = null; - mCameraState = PREVIEW_STOPPED; - } - } - - private void openCamera() throws CameraHardwareException, CameraDisabledException { - int cameraId = CameraHolder.instance().getBackCameraId(); - // If there is no back camera, use the first camera. Camera id starts - // from 0. Currently if a camera is not back facing, it is front facing. - // This is also forward compatible if we have a new facing other than - // back or front in the future. - if (cameraId == -1) cameraId = 0; - mCameraDevice = Util.openCamera(mActivity, cameraId); - mCameraOrientation = Util.getCameraOrientation(cameraId); - if (cameraId == CameraHolder.instance().getFrontCameraId()) mUsingFrontCamera = true; - } - - private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3, - boolean needSmaller) { - int pixelsDiff = DEFAULT_CAPTURE_PIXELS; - boolean hasFound = false; - for (Size size : supportedSizes) { - int h = size.height; - int w = size.width; - // we only want 4:3 format. - int d = DEFAULT_CAPTURE_PIXELS - h * w; - if (needSmaller && d < 0) { // no bigger preview than 960x720. - continue; - } - if (need4To3 && (h * 4 != w * 3)) { - continue; - } - d = Math.abs(d); - if (d < pixelsDiff) { - mPreviewWidth = w; - mPreviewHeight = h; - pixelsDiff = d; - hasFound = true; - } - } - return hasFound; - } - - private void setupCaptureParams(Parameters parameters) { - List<Size> supportedSizes = parameters.getSupportedPreviewSizes(); - if (!findBestPreviewSize(supportedSizes, true, true)) { - Log.w(TAG, "No 4:3 ratio preview size supported."); - if (!findBestPreviewSize(supportedSizes, false, true)) { - Log.w(TAG, "Can't find a supported preview size smaller than 960x720."); - findBestPreviewSize(supportedSizes, false, false); - } - } - Log.v(TAG, "preview h = " + mPreviewHeight + " , w = " + mPreviewWidth); - parameters.setPreviewSize(mPreviewWidth, mPreviewHeight); - - List<int[]> frameRates = parameters.getSupportedPreviewFpsRange(); - int last = frameRates.size() - 1; - int minFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MIN_INDEX]; - int maxFps = (frameRates.get(last))[Parameters.PREVIEW_FPS_MAX_INDEX]; - parameters.setPreviewFpsRange(minFps, maxFps); - Log.v(TAG, "preview fps: " + minFps + ", " + maxFps); - - List<String> supportedFocusModes = parameters.getSupportedFocusModes(); - if (supportedFocusModes.indexOf(mTargetFocusMode) >= 0) { - parameters.setFocusMode(mTargetFocusMode); - } else { - // Use the default focus mode and log a message - Log.w(TAG, "Cannot set the focus mode to " + mTargetFocusMode + - " becuase the mode is not supported."); - } - - parameters.set(Util.RECORDING_HINT, Util.FALSE); - - mHorizontalViewAngle = parameters.getHorizontalViewAngle(); - mVerticalViewAngle = parameters.getVerticalViewAngle(); - } - - public int getPreviewBufSize() { - PixelFormat pixelInfo = new PixelFormat(); - PixelFormat.getPixelFormatInfo(mCameraDevice.getParameters().getPreviewFormat(), pixelInfo); - // TODO: remove this extra 32 byte after the driver bug is fixed. - return (mPreviewWidth * mPreviewHeight * pixelInfo.bitsPerPixel / 8) + 32; - } - - private void configureCamera(Parameters parameters) { - mCameraDevice.setParameters(parameters); - } - - private void configMosaicPreview(final int w, final int h) { - synchronized (mRendererLock) { - if (mIsCreatingRenderer) { - mMainHandler.removeMessages(MSG_CONFIG_MOSAIC_PREVIEW); - mMainHandler.obtainMessage(MSG_CONFIG_MOSAIC_PREVIEW, w, h).sendToTarget(); - return; - } - mIsCreatingRenderer = true; - } - stopCameraPreview(); - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - screenNail.setSize(w, h); - synchronized (mRendererLock) { - if (mMosaicPreviewRenderer != null) { - mMosaicPreviewRenderer.release(); - } - mMosaicPreviewRenderer = null; - screenNail.releaseSurfaceTexture(); - screenNail.acquireSurfaceTexture(); - } - mActivity.notifyScreenNailChanged(); - final boolean isLandscape = (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); - new Thread(new Runnable() { - @Override - public void run() { - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - SurfaceTexture surfaceTexture = screenNail.getSurfaceTexture(); - if (surfaceTexture == null) { - synchronized (mRendererLock) { - mIsCreatingRenderer = false; - mRendererLock.notifyAll(); - return; - } - } - MosaicPreviewRenderer renderer = new MosaicPreviewRenderer( - screenNail.getSurfaceTexture(), w, h, isLandscape); - synchronized (mRendererLock) { - mMosaicPreviewRenderer = renderer; - mCameraTexture = mMosaicPreviewRenderer.getInputSurfaceTexture(); - - if (!mPaused && !mThreadRunning && mWaitProcessorTask == null) { - mMainHandler.sendEmptyMessage(MSG_RESET_TO_PREVIEW); - } - mIsCreatingRenderer = false; - mRendererLock.notifyAll(); - } - } - }).start(); - } - - // Receives the layout change event from the preview area. So we can set - // the camera preview screennail to the same size and initialize the mosaic - // preview renderer. - @Override - public void onLayoutChange(View v, int l, int t, int r, int b) { - Log.i(TAG, "layout change: "+(r - l) + "/" +(b - t)); - mActivity.onLayoutChange(v, l, t, r, b); - configMosaicPreview(r - l, b - t); - } - - @Override - public void onFrameAvailable(SurfaceTexture surface) { - /* This function may be called by some random thread, - * so let's be safe and jump back to ui thread. - * No OpenGL calls can be done here. */ - mActivity.runOnUiThread(mOnFrameAvailableRunnable); - } - - private void hideDirectionIndicators() { - mLeftIndicator.setVisibility(View.GONE); - mRightIndicator.setVisibility(View.GONE); - } - - private void showDirectionIndicators(int direction) { - switch (direction) { - case PanoProgressBar.DIRECTION_NONE: - mLeftIndicator.setVisibility(View.VISIBLE); - mRightIndicator.setVisibility(View.VISIBLE); - break; - case PanoProgressBar.DIRECTION_LEFT: - mLeftIndicator.setVisibility(View.VISIBLE); - mRightIndicator.setVisibility(View.GONE); - break; - case PanoProgressBar.DIRECTION_RIGHT: - mLeftIndicator.setVisibility(View.GONE); - mRightIndicator.setVisibility(View.VISIBLE); - break; - } - } - - public void startCapture() { - // Reset values so we can do this again. - mCancelComputation = false; - mTimeTaken = System.currentTimeMillis(); - mActivity.setSwipingEnabled(false); - mActivity.hideSwitcher(); - mShutterButton.setImageResource(R.drawable.btn_shutter_recording); - mCaptureState = CAPTURE_STATE_MOSAIC; - mCaptureIndicator.setVisibility(View.VISIBLE); - showDirectionIndicators(PanoProgressBar.DIRECTION_NONE); - - mMosaicFrameProcessor.setProgressListener(new MosaicFrameProcessor.ProgressListener() { - @Override - public void onProgress(boolean isFinished, float panningRateX, float panningRateY, - float progressX, float progressY) { - float accumulatedHorizontalAngle = progressX * mHorizontalViewAngle; - float accumulatedVerticalAngle = progressY * mVerticalViewAngle; - if (isFinished - || (Math.abs(accumulatedHorizontalAngle) >= DEFAULT_SWEEP_ANGLE) - || (Math.abs(accumulatedVerticalAngle) >= DEFAULT_SWEEP_ANGLE)) { - stopCapture(false); - } else { - float panningRateXInDegree = panningRateX * mHorizontalViewAngle; - float panningRateYInDegree = panningRateY * mVerticalViewAngle; - updateProgress(panningRateXInDegree, panningRateYInDegree, - accumulatedHorizontalAngle, accumulatedVerticalAngle); - } - } - }); - - mPanoProgressBar.reset(); - // TODO: calculate the indicator width according to different devices to reflect the actual - // angle of view of the camera device. - mPanoProgressBar.setIndicatorWidth(20); - mPanoProgressBar.setMaxProgress(DEFAULT_SWEEP_ANGLE); - mPanoProgressBar.setVisibility(View.VISIBLE); - mDeviceOrientationAtCapture = mDeviceOrientation; - keepScreenOn(); - mActivity.getOrientationManager().lockOrientation(); - setupProgressDirectionMatrix(); - } - - void setupProgressDirectionMatrix() { - int degrees = Util.getDisplayRotation(mActivity); - int cameraId = CameraHolder.instance().getBackCameraId(); - int orientation = Util.getDisplayOrientation(degrees, cameraId); - mProgressDirectionMatrix.reset(); - mProgressDirectionMatrix.postRotate(orientation); - } - - private void stopCapture(boolean aborted) { - mCaptureState = CAPTURE_STATE_VIEWFINDER; - mCaptureIndicator.setVisibility(View.GONE); - hideTooFastIndication(); - hideDirectionIndicators(); - - mMosaicFrameProcessor.setProgressListener(null); - stopCameraPreview(); - - mCameraTexture.setOnFrameAvailableListener(null); - - if (!aborted && !mThreadRunning) { - mRotateDialog.showWaitingDialog(mPreparePreviewString); - // Hide shutter button, shutter icon, etc when waiting for - // panorama to stitch - mActivity.hideUI(); - runBackgroundThread(new Thread() { - @Override - public void run() { - MosaicJpeg jpeg = generateFinalMosaic(false); - - if (jpeg != null && jpeg.isValid) { - Bitmap bitmap = null; - bitmap = BitmapFactory.decodeByteArray(jpeg.data, 0, jpeg.data.length); - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_LOW_RES_FINAL_MOSAIC_READY, bitmap)); - } else { - mMainHandler.sendMessage(mMainHandler.obtainMessage( - MSG_END_DIALOG_RESET_TO_PREVIEW)); - } - } - }); - } - keepScreenOnAwhile(); - } - - private void showTooFastIndication() { - mTooFastPrompt.setVisibility(View.VISIBLE); - // The PreviewArea also contains the border for "too fast" indication. - mPreviewArea.setVisibility(View.VISIBLE); - mPanoProgressBar.setIndicatorColor(mIndicatorColorFast); - mLeftIndicator.setEnabled(true); - mRightIndicator.setEnabled(true); - } - - private void hideTooFastIndication() { - mTooFastPrompt.setVisibility(View.GONE); - // We set "INVISIBLE" instead of "GONE" here because we need mPreviewArea to have layout - // information so we can know the size and position for mCameraScreenNail. - mPreviewArea.setVisibility(View.INVISIBLE); - mPanoProgressBar.setIndicatorColor(mIndicatorColor); - mLeftIndicator.setEnabled(false); - mRightIndicator.setEnabled(false); - } - - private void updateProgress(float panningRateXInDegree, float panningRateYInDegree, - float progressHorizontalAngle, float progressVerticalAngle) { - mGLRootView.requestRender(); - - if ((Math.abs(panningRateXInDegree) > PANNING_SPEED_THRESHOLD) - || (Math.abs(panningRateYInDegree) > PANNING_SPEED_THRESHOLD)) { - showTooFastIndication(); - } else { - hideTooFastIndication(); - } - - // progressHorizontalAngle and progressVerticalAngle are relative to the - // camera. Convert them to UI direction. - mProgressAngle[0] = progressHorizontalAngle; - mProgressAngle[1] = progressVerticalAngle; - mProgressDirectionMatrix.mapPoints(mProgressAngle); - - int angleInMajorDirection = - (Math.abs(mProgressAngle[0]) > Math.abs(mProgressAngle[1])) - ? (int) mProgressAngle[0] - : (int) mProgressAngle[1]; - mPanoProgressBar.setProgress((angleInMajorDirection)); - } - - private void setViews(Resources appRes) { - mCaptureState = CAPTURE_STATE_VIEWFINDER; - mPanoProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_pan_progress_bar); - mPanoProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); - mPanoProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_done)); - mPanoProgressBar.setIndicatorColor(mIndicatorColor); - mPanoProgressBar.setOnDirectionChangeListener( - new PanoProgressBar.OnDirectionChangeListener () { - @Override - public void onDirectionChange(int direction) { - if (mCaptureState == CAPTURE_STATE_MOSAIC) { - showDirectionIndicators(direction); - } - } - }); - - mLeftIndicator = mRootView.findViewById(R.id.pano_pan_left_indicator); - mRightIndicator = mRootView.findViewById(R.id.pano_pan_right_indicator); - mLeftIndicator.setEnabled(false); - mRightIndicator.setEnabled(false); - mTooFastPrompt = (TextView) mRootView.findViewById(R.id.pano_capture_too_fast_textview); - // This mPreviewArea also shows the border for visual "too fast" indication. - mPreviewArea = (LayoutNotifyView) mRootView.findViewById(R.id.pano_preview_area); - mPreviewArea.setOnLayoutChangeListener(this); - - mSavingProgressBar = (PanoProgressBar) mRootView.findViewById(R.id.pano_saving_progress_bar); - mSavingProgressBar.setIndicatorWidth(0); - mSavingProgressBar.setMaxProgress(100); - mSavingProgressBar.setBackgroundColor(appRes.getColor(R.color.pano_progress_empty)); - mSavingProgressBar.setDoneColor(appRes.getColor(R.color.pano_progress_indication)); - - mCaptureIndicator = mRootView.findViewById(R.id.pano_capture_indicator); - - mReviewLayout = mRootView.findViewById(R.id.pano_review_layout); - mReview = (ImageView) mRootView.findViewById(R.id.pano_reviewarea); - mReview.setBackgroundColor(mReviewBackground); - View cancelButton = mRootView.findViewById(R.id.pano_review_cancel_button); - cancelButton.setOnClickListener(new OnClickListener() { - @Override - public void onClick(View arg0) { - if (mPaused || mCameraTexture == null) return; - cancelHighResComputation(); - } - }); - - mShutterButton = mActivity.getShutterButton(); - mShutterButton.setImageResource(R.drawable.btn_new_shutter); - mShutterButton.setOnShutterButtonListener(this); - - if (mActivity.getResources().getConfiguration().orientation - == Configuration.ORIENTATION_PORTRAIT) { - Rotatable view = (Rotatable) mRootView.findViewById(R.id.pano_rotate_reviewarea); - view.setOrientation(270, false); - } - } - - private void createContentView() { - mActivity.getLayoutInflater().inflate(R.layout.panorama_module, (ViewGroup) mRootView, true); - Resources appRes = mActivity.getResources(); - mCaptureLayout = (LinearLayout) mRootView.findViewById(R.id.camera_app); - mIndicatorColor = appRes.getColor(R.color.pano_progress_indication); - mReviewBackground = appRes.getColor(R.color.review_background); - mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast); - mPanoLayout = (ViewGroup) mRootView.findViewById(R.id.camera_app_root); - mRotateDialog = new RotateDialogController(mActivity, R.layout.rotate_dialog); - setViews(appRes); - } - - @Override - public void onShutterButtonClick() { - // If mCameraTexture == null then GL setup is not finished yet. - // No buttons can be pressed. - if (mPaused || mThreadRunning || mCameraTexture == null) return; - // Since this button will stay on the screen when capturing, we need to check the state - // right now. - switch (mCaptureState) { - case CAPTURE_STATE_VIEWFINDER: - if(mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) return; - mSoundPlayer.play(SoundClips.START_VIDEO_RECORDING); - startCapture(); - break; - case CAPTURE_STATE_MOSAIC: - mSoundPlayer.play(SoundClips.STOP_VIDEO_RECORDING); - stopCapture(false); - } - } - - @Override - public void onShutterButtonFocus(boolean pressed) { - } - - public void reportProgress() { - mSavingProgressBar.reset(); - mSavingProgressBar.setRightIncreasing(true); - Thread t = new Thread() { - @Override - public void run() { - while (mThreadRunning) { - final int progress = mMosaicFrameProcessor.reportProgress( - true, mCancelComputation); - - try { - synchronized (mWaitObject) { - mWaitObject.wait(50); - } - } catch (InterruptedException e) { - throw new RuntimeException("Panorama reportProgress failed", e); - } - // Update the progress bar - mActivity.runOnUiThread(new Runnable() { - @Override - public void run() { - mSavingProgressBar.setProgress(progress); - } - }); - } - } - }; - t.start(); - } - - private int getCaptureOrientation() { - // The panorama image returned from the library is oriented based on the - // natural orientation of a camera. We need to set an orientation for the image - // in its EXIF header, so the image can be displayed correctly. - // The orientation is calculated from compensating the - // device orientation at capture and the camera orientation respective to - // the natural orientation of the device. - int orientation; - if (mUsingFrontCamera) { - // mCameraOrientation is negative with respect to the front facing camera. - // See document of android.hardware.Camera.Parameters.setRotation. - orientation = (mDeviceOrientationAtCapture - mCameraOrientation + 360) % 360; - } else { - orientation = (mDeviceOrientationAtCapture + mCameraOrientation) % 360; - } - return orientation; - } - - public void saveHighResMosaic() { - runBackgroundThread(new Thread() { - @Override - public void run() { - mPartialWakeLock.acquire(); - MosaicJpeg jpeg; - try { - jpeg = generateFinalMosaic(true); - } finally { - mPartialWakeLock.release(); - } - - if (jpeg == null) { // Cancelled by user. - mMainHandler.sendEmptyMessage(MSG_END_DIALOG_RESET_TO_PREVIEW); - } else if (!jpeg.isValid) { // Error when generating mosaic. - mMainHandler.sendEmptyMessage(MSG_GENERATE_FINAL_MOSAIC_ERROR); - } else { - int orientation = getCaptureOrientation(); - Uri uri = savePanorama(jpeg.data, jpeg.width, jpeg.height, orientation); - if (uri != null) { - mActivity.addSecureAlbumItemIfNeeded(false, uri); - Util.broadcastNewPicture(mActivity, uri); - } - mMainHandler.sendMessage( - mMainHandler.obtainMessage(MSG_END_DIALOG_RESET_TO_PREVIEW)); - } - } - }); - reportProgress(); - } - - private void runBackgroundThread(Thread thread) { - mThreadRunning = true; - thread.start(); - } - - private void onBackgroundThreadFinished() { - mThreadRunning = false; - mRotateDialog.dismissDialog(); - } - - private void cancelHighResComputation() { - mCancelComputation = true; - synchronized (mWaitObject) { - mWaitObject.notify(); - } - } - - // This function will be called upon the first camera frame is available. - private void reset() { - mCaptureState = CAPTURE_STATE_VIEWFINDER; - - mActivity.getOrientationManager().unlockOrientation(); - // We should set mGLRootView visible too. However, since there might be no - // frame available yet, setting mGLRootView visible should be done right after - // the first camera frame is available and therefore it is done by - // mOnFirstFrameAvailableRunnable. - mActivity.setSwipingEnabled(true); - mShutterButton.setImageResource(R.drawable.btn_new_shutter); - mReviewLayout.setVisibility(View.GONE); - mPanoProgressBar.setVisibility(View.GONE); - mGLRootView.setVisibility(View.VISIBLE); - // Orientation change will trigger onLayoutChange->configMosaicPreview-> - // resetToPreview. Do not show the capture UI in film strip. - if (mActivity.mShowCameraAppView) { - mCaptureLayout.setVisibility(View.VISIBLE); - mActivity.showUI(); - } - mMosaicFrameProcessor.reset(); - } - - private void resetToPreview() { - reset(); - if (!mPaused) startCameraPreview(); - } - - private static class FlipBitmapDrawable extends BitmapDrawable { - - public FlipBitmapDrawable(Resources res, Bitmap bitmap) { - super(res, bitmap); - } - - @Override - public void draw(Canvas canvas) { - Rect bounds = getBounds(); - int cx = bounds.centerX(); - int cy = bounds.centerY(); - canvas.save(Canvas.MATRIX_SAVE_FLAG); - canvas.rotate(180, cx, cy); - super.draw(canvas); - canvas.restore(); - } - } - - private void showFinalMosaic(Bitmap bitmap) { - if (bitmap != null) { - int orientation = getCaptureOrientation(); - if (orientation >= 180) { - // We need to flip the drawable to compensate - mReview.setImageDrawable(new FlipBitmapDrawable( - mActivity.getResources(), bitmap)); - } else { - mReview.setImageBitmap(bitmap); - } - } - - mCaptureLayout.setVisibility(View.GONE); - mReviewLayout.setVisibility(View.VISIBLE); - } - - private Uri savePanorama(byte[] jpegData, int width, int height, int orientation) { - if (jpegData != null) { - String filename = PanoUtil.createName( - mActivity.getResources().getString(R.string.pano_file_name_format), mTimeTaken); - String filepath = Storage.generateFilepath(filename); - - Location loc = mLocationManager.getCurrentLocation(); - ExifInterface exif = new ExifInterface(); - try { - exif.readExif(jpegData); - exif.addGpsDateTimeStampTag(mTimeTaken); - exif.addDateTimeStampTag(ExifInterface.TAG_DATE_TIME, mTimeTaken, - TimeZone.getDefault()); - exif.setTag(exif.buildTag(ExifInterface.TAG_ORIENTATION, - ExifInterface.getOrientationValueForRotation(orientation))); - writeLocation(loc, exif); - exif.writeExif(jpegData, filepath); - } catch (IOException e) { - Log.e(TAG, "Cannot set exif for " + filepath, e); - Storage.writeFile(filepath, jpegData); - } - int jpegLength = (int) (new File(filepath).length()); - return Storage.addImage(mContentResolver, filename, mTimeTaken, - loc, orientation, jpegLength, filepath, width, height); - } - return null; - } - - private static void writeLocation(Location location, ExifInterface exif) { - if (location == null) { - return; - } - exif.addGpsTags(location.getLatitude(), location.getLongitude()); - exif.setTag(exif.buildTag(ExifInterface.TAG_GPS_PROCESSING_METHOD, location.getProvider())); - } - - private void clearMosaicFrameProcessorIfNeeded() { - if (!mPaused || mThreadRunning) return; - // Only clear the processor if it is initialized by this activity - // instance. Other activity instances may be using it. - if (mMosaicFrameProcessorInitialized) { - mMosaicFrameProcessor.clear(); - mMosaicFrameProcessorInitialized = false; - } - } - - private void initMosaicFrameProcessorIfNeeded() { - if (mPaused || mThreadRunning) return; - mMosaicFrameProcessor.initialize( - mPreviewWidth, mPreviewHeight, getPreviewBufSize()); - mMosaicFrameProcessorInitialized = true; - } - - @Override - public void onPauseBeforeSuper() { - mPaused = true; - if (mLocationManager != null) mLocationManager.recordLocation(false); - } - - @Override - public void onPauseAfterSuper() { - mOrientationEventListener.disable(); - if (mCameraDevice == null) { - // Camera open failed. Nothing should be done here. - return; - } - // Stop the capturing first. - if (mCaptureState == CAPTURE_STATE_MOSAIC) { - stopCapture(true); - reset(); - } - - releaseCamera(); - synchronized (mRendererLock) { - mCameraTexture = null; - - // The preview renderer might not have a chance to be initialized - // before onPause(). - if (mMosaicPreviewRenderer != null) { - mMosaicPreviewRenderer.release(); - mMosaicPreviewRenderer = null; - } - } - - clearMosaicFrameProcessorIfNeeded(); - if (mWaitProcessorTask != null) { - mWaitProcessorTask.cancel(true); - mWaitProcessorTask = null; - } - resetScreenOn(); - if (mSoundPlayer != null) { - mSoundPlayer.release(); - mSoundPlayer = null; - } - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - screenNail.releaseSurfaceTexture(); - System.gc(); - } - - @Override - public void onConfigurationChanged(Configuration newConfig) { - Drawable lowResReview = null; - if (mThreadRunning) lowResReview = mReview.getDrawable(); - - // Change layout in response to configuration change - mCaptureLayout.setOrientation( - newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE - ? LinearLayout.HORIZONTAL : LinearLayout.VERTICAL); - mCaptureLayout.removeAllViews(); - LayoutInflater inflater = mActivity.getLayoutInflater(); - inflater.inflate(R.layout.preview_frame_pano, mCaptureLayout); - - mPanoLayout.removeView(mReviewLayout); - inflater.inflate(R.layout.pano_review, mPanoLayout); - - setViews(mActivity.getResources()); - if (mThreadRunning) { - mReview.setImageDrawable(lowResReview); - mCaptureLayout.setVisibility(View.GONE); - mReviewLayout.setVisibility(View.VISIBLE); - } - } - - @Override - public void onOrientationChanged(int orientation) { - } - - @Override - public void onResumeBeforeSuper() { - mPaused = false; - } - - @Override - public void onResumeAfterSuper() { - mOrientationEventListener.enable(); - - mCaptureState = CAPTURE_STATE_VIEWFINDER; - - try { - setupCamera(); - } catch (CameraHardwareException e) { - Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); - return; - } catch (CameraDisabledException e) { - Util.showErrorAndFinish(mActivity, R.string.camera_disabled); - return; - } - - // Set up sound playback for shutter button - mSoundPlayer = SoundClips.getPlayer(mActivity); - - // Check if another panorama instance is using the mosaic frame processor. - mRotateDialog.dismissDialog(); - if (!mThreadRunning && mMosaicFrameProcessor.isMosaicMemoryAllocated()) { - mGLRootView.setVisibility(View.GONE); - mRotateDialog.showWaitingDialog(mDialogWaitingPreviousString); - // If stitching is still going on, make sure switcher and shutter button - // are not showing - mActivity.hideUI(); - mWaitProcessorTask = new WaitProcessorTask().execute(); - } else { - mGLRootView.setVisibility(View.VISIBLE); - // Camera must be initialized before MosaicFrameProcessor is - // initialized. The preview size has to be decided by camera device. - initMosaicFrameProcessorIfNeeded(); - int w = mPreviewArea.getWidth(); - int h = mPreviewArea.getHeight(); - if (w != 0 && h != 0) { // The layout has been calculated. - configMosaicPreview(w, h); - } - } - keepScreenOnAwhile(); - - // Initialize location service. - boolean recordLocation = RecordLocationPreference.get(mPreferences, - mContentResolver); - mLocationManager.recordLocation(recordLocation); - - // Dismiss open menu if exists. - PopupManager.getInstance(mActivity).notifyShowPopup(null); - mRootView.requestLayout(); - UsageStatistics.onContentViewChanged( - UsageStatistics.COMPONENT_CAMERA, "PanoramaModule"); - } - - /** - * Generate the final mosaic image. - * - * @param highRes flag to indicate whether we want to get a high-res version. - * @return a MosaicJpeg with its isValid flag set to true if successful; null if the generation - * process is cancelled; and a MosaicJpeg with its isValid flag set to false if there - * is an error in generating the final mosaic. - */ - public MosaicJpeg generateFinalMosaic(boolean highRes) { - int mosaicReturnCode = mMosaicFrameProcessor.createMosaic(highRes); - if (mosaicReturnCode == Mosaic.MOSAIC_RET_CANCELLED) { - return null; - } else if (mosaicReturnCode == Mosaic.MOSAIC_RET_ERROR) { - return new MosaicJpeg(); - } - - byte[] imageData = mMosaicFrameProcessor.getFinalMosaicNV21(); - if (imageData == null) { - Log.e(TAG, "getFinalMosaicNV21() returned null."); - return new MosaicJpeg(); - } - - int len = imageData.length - 8; - int width = (imageData[len + 0] << 24) + ((imageData[len + 1] & 0xFF) << 16) - + ((imageData[len + 2] & 0xFF) << 8) + (imageData[len + 3] & 0xFF); - int height = (imageData[len + 4] << 24) + ((imageData[len + 5] & 0xFF) << 16) - + ((imageData[len + 6] & 0xFF) << 8) + (imageData[len + 7] & 0xFF); - Log.v(TAG, "ImLength = " + (len) + ", W = " + width + ", H = " + height); - - if (width <= 0 || height <= 0) { - // TODO: pop up an error message indicating that the final result is not generated. - Log.e(TAG, "width|height <= 0!!, len = " + (len) + ", W = " + width + ", H = " + - height); - return new MosaicJpeg(); - } - - YuvImage yuvimage = new YuvImage(imageData, ImageFormat.NV21, width, height, null); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - yuvimage.compressToJpeg(new Rect(0, 0, width, height), 100, out); - try { - out.close(); - } catch (Exception e) { - Log.e(TAG, "Exception in storing final mosaic", e); - return new MosaicJpeg(); - } - return new MosaicJpeg(out.toByteArray(), width, height); - } - - private void startCameraPreview() { - if (mCameraDevice == null) { - // Camera open failed. Return. - return; - } - - // This works around a driver issue. startPreview may fail if - // stopPreview/setPreviewTexture/startPreview are called several times - // in a row. mCameraTexture can be null after pressing home during - // mosaic generation and coming back. Preview will be started later in - // onLayoutChange->configMosaicPreview. This also reduces the latency. - synchronized (mRendererLock) { - if (mCameraTexture == null) return; - - // If we're previewing already, stop the preview first (this will - // blank the screen). - if (mCameraState != PREVIEW_STOPPED) stopCameraPreview(); - - // Set the display orientation to 0, so that the underlying mosaic - // library can always get undistorted mPreviewWidth x mPreviewHeight - // image data from SurfaceTexture. - mCameraDevice.setDisplayOrientation(0); - - mCameraTexture.setOnFrameAvailableListener(this); - mCameraDevice.setPreviewTextureAsync(mCameraTexture); - } - mCameraDevice.startPreviewAsync(); - mCameraState = PREVIEW_ACTIVE; - } - - private void stopCameraPreview() { - if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { - Log.v(TAG, "stopPreview"); - mCameraDevice.stopPreview(); - } - mCameraState = PREVIEW_STOPPED; - } - - @Override - public void onUserInteraction() { - if (mCaptureState != CAPTURE_STATE_MOSAIC) keepScreenOnAwhile(); - } - - @Override - public boolean onBackPressed() { - // If panorama is generating low res or high res mosaic, ignore back - // key. So the activity will not be destroyed. - if (mThreadRunning) return true; - return false; - } - - private void resetScreenOn() { - mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); - mActivity.getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - private void keepScreenOnAwhile() { - mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); - mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - mMainHandler.sendEmptyMessageDelayed(MSG_CLEAR_SCREEN_DELAY, SCREEN_DELAY); - } - - private void keepScreenOn() { - mMainHandler.removeMessages(MSG_CLEAR_SCREEN_DELAY); - mActivity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); - } - - private class WaitProcessorTask extends AsyncTask<Void, Void, Void> { - @Override - protected Void doInBackground(Void... params) { - synchronized (mMosaicFrameProcessor) { - while (!isCancelled() && mMosaicFrameProcessor.isMosaicMemoryAllocated()) { - try { - mMosaicFrameProcessor.wait(); - } catch (Exception e) { - // ignore - } - } - } - return null; - } - - @Override - protected void onPostExecute(Void result) { - mWaitProcessorTask = null; - mRotateDialog.dismissDialog(); - mGLRootView.setVisibility(View.VISIBLE); - initMosaicFrameProcessorIfNeeded(); - int w = mPreviewArea.getWidth(); - int h = mPreviewArea.getHeight(); - if (w != 0 && h != 0) { // The layout has been calculated. - configMosaicPreview(w, h); - } - resetToPreview(); - } - } - - @Override - public void onFullScreenChanged(boolean full) { - } - - - @Override - public void onStop() { - } - - @Override - public void installIntentFilter() { - } - - @Override - public void onActivityResult(int requestCode, int resultCode, Intent data) { - } - - - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - return false; - } - - @Override - public boolean onKeyUp(int keyCode, KeyEvent event) { - return false; - } - - @Override - public void onSingleTapUp(View view, int x, int y) { - } - - @Override - public void onPreviewTextureCopied() { - } - - @Override - public void onCaptureTextureCopied() { - } - - @Override - public boolean updateStorageHintOnResume() { - return false; - } - - @Override - public void updateCameraAppView() { - } - - @Override - public boolean needsSwitcher() { - return true; - } - - @Override - public boolean needsPieMenu() { - return false; - } - - @Override - public void onShowSwitcherPopup() { - } - - @Override - public void onMediaSaveServiceConnected(MediaSaveService s) { - // do nothing. - } -} diff --git a/src/com/android/camera/PhotoController.java b/src/com/android/camera/PhotoController.java index b76022e57..47c17218a 100644 --- a/src/com/android/camera/PhotoController.java +++ b/src/com/android/camera/PhotoController.java @@ -59,4 +59,5 @@ public interface PhotoController extends OnShutterButtonListener { public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight); + public void updateCameraOrientation(); } diff --git a/src/com/android/camera/PhotoMenu.java b/src/com/android/camera/PhotoMenu.java index d0f21ed68..6c1e2d085 100644 --- a/src/com/android/camera/PhotoMenu.java +++ b/src/com/android/camera/PhotoMenu.java @@ -168,7 +168,6 @@ public class PhotoMenu extends PieController } public void popupDismissed() { - // the popup gets dismissed if (mPopup != null) { mPopup = null; } @@ -198,5 +197,4 @@ public class PhotoMenu extends PieController } super.onSettingChanged(pref); } - } diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index c3d152716..7c4487b90 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -49,7 +49,6 @@ 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; @@ -64,7 +63,6 @@ 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.FilterShowActivity; import com.android.gallery3d.filtershow.crop.CropActivity; import com.android.gallery3d.filtershow.crop.CropExtras; import com.android.gallery3d.util.UsageStatistics; @@ -75,7 +73,6 @@ 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; @@ -127,8 +124,6 @@ public class PhotoModule private PhotoUI mUI; - // these are only used by Camera - // The activity is going to switch to the specified camera id. This is // needed because texture copy is done in GL thread. -1 means camera is not // switching. @@ -252,23 +247,21 @@ public class PhotoModule private PreferenceGroup mPreferenceGroup; private boolean mQuickCapture; - - CameraStartUpThread mCameraStartUpThread; - ConditionVariable mStartPreviewPrerequisiteReady = new ConditionVariable(); - 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) { - mActivity.addSecureAlbumItemIfNeeded(false, uri); - Util.broadcastNewPicture(mActivity, uri); + mActivity.notifyNewMedia(uri); } } }; @@ -371,7 +364,8 @@ public class PhotoModule } case SWITCH_CAMERA_START_ANIMATION: { - ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera(); + // TODO: Need to revisit + // ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera(); break; } @@ -409,7 +403,7 @@ public class PhotoModule } @Override - public void init(CameraActivity activity, View parent, boolean reuseNail) { + public void init(CameraActivity activity, View parent) { mActivity = activity; mUI = new PhotoUI(activity, this, parent); mPreferences = new ComboPreferences(mActivity); @@ -423,15 +417,9 @@ public class PhotoModule mCameraStartUpThread = new CameraStartUpThread(); mCameraStartUpThread.start(); - // Surface texture is from camera screen nail and startPreview needs it. // This must be done before startPreview. mIsImageCaptureIntent = isImageCaptureIntent(); - if (reuseNail) { - mActivity.reuseCameraScreenNail(!mIsImageCaptureIntent); - } else { - mActivity.createCameraScreenNail(!mIsImageCaptureIntent); - } mPreferences.setLocalId(mActivity, mCameraId); CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); @@ -445,7 +433,6 @@ public class PhotoModule mQuickCapture = mActivity.getIntent().getBooleanExtra(EXTRA_QUICK_CAPTURE, false); mLocationManager = new LocationManager(mActivity, mUI); mSensorManager = (SensorManager)(mActivity.getSystemService(Context.SENSOR_SERVICE)); - } private void initializeControlByIntent() { @@ -458,10 +445,6 @@ public class PhotoModule private void onPreviewStarted() { mCameraStartUpThread = null; setCameraState(IDLE); - if (!ApiHelper.HAS_SURFACE_TEXTURE) { - // This may happen if surfaceCreated has arrived. - mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); - } startFaceDetection(); locationFirstRun(); } @@ -520,16 +503,7 @@ public class PhotoModule int width = root.getWidth(); int height = root.getHeight(); mFocusManager.setPreviewSize(width, height); - // Full-screen screennail - if (Util.getDisplayRotation(mActivity) % 180 == 0) { - ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(width, height); - } else { - ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize(height, width); - } - // Set touch focus listener. - mActivity.setSingleTapUpListener(root); openCameraCommon(); - onFullScreenChanged(mActivity.isInCameraApp()); } private void switchCamera() { @@ -566,6 +540,8 @@ public class PhotoModule mFocusManager.setParameters(mInitialParams); setupPreview(); + // reset zoom value index + mZoomValue = 0; openCameraCommon(); if (ApiHelper.HAS_SURFACE_TEXTURE) { @@ -592,11 +568,7 @@ public class PhotoModule } public void onScreenSizeChanged(int width, int height, int previewWidth, int previewHeight) { - Log.d(TAG, "Preview size changed."); if (mFocusManager != null) mFocusManager.setPreviewSize(width, height); - ((CameraScreenNail) mActivity.mCameraScreenNail).setPreviewFrameLayoutSize( - previewWidth, previewHeight); - mActivity.notifyScreenNailChanged(); } private void resetExposureCompensation() { @@ -726,12 +698,6 @@ public class PhotoModule } } - @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 { @@ -786,9 +752,13 @@ public class PhotoModule if (mPaused) { return; } + //TODO: We should show the picture taken rather than frozen preview here + if (mIsImageCaptureIntent) { + stopPreview(); + } if (mSceneMode == Util.SCENE_MODE_HDR) { - mActivity.showSwitcher(); - mActivity.setSwipingEnabled(true); + mUI.showSwitcher(); + mUI.setSwipingEnabled(true); } mJpegPictureCallbackTime = System.currentTimeMillis(); @@ -809,6 +779,7 @@ public class PhotoModule 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 @@ -819,7 +790,7 @@ public class PhotoModule ((CameraScreenNail) mActivity.mCameraScreenNail).animateSlide(); mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, CaptureAnimManager.getAnimationDuration()); - } + } */ mFocusManager.updateFocusUI(); // Ensure focus indicator is hidden. if (!mIsImageCaptureIntent) { if (ApiHelper.CAN_START_PREVIEW_IN_JPEG_CALLBACK) { @@ -957,14 +928,11 @@ public class PhotoModule 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: - if (mActivity.isInCameraApp()) { - mUI.enableGestures(true); - } + mUI.enableGestures(true); break; } } @@ -973,13 +941,12 @@ public class PhotoModule // 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); - mUI.enablePreviewThumb(true); - mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, - CaptureAnimManager.getAnimationDuration()); + if (!mIsImageCaptureIntent) { + mUI.animateFlash(); + + // TODO: mUI.enablePreviewThumb(true); + // mHandler.sendEmptyMessageDelayed(CAPTURE_ANIMATION_DONE, + // CaptureAnimManager.getAnimationDuration()); } } @@ -1047,17 +1014,6 @@ public class PhotoModule } } - @Override - public void onFullScreenChanged(boolean full) { - mUI.onFullScreenChanged(full); - if (ApiHelper.HAS_SURFACE_TEXTURE) { - if (mActivity.mCameraScreenNail != null) { - ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full); - } - return; - } - } - private void updateSceneMode() { // If scene mode is set, we cannot set flash mode, white balance, and // focus mode, instead, we read it from driver @@ -1236,8 +1192,8 @@ public class PhotoModule Log.v(TAG, "onShutterButtonClick: mCameraState=" + mCameraState); if (mSceneMode == Util.SCENE_MODE_HDR) { - mActivity.hideSwitcher(); - mActivity.setSwipingEnabled(false); + mUI.hideSwitcher(); + mUI.setSwipingEnabled(false); } // 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 @@ -1295,7 +1251,6 @@ public class PhotoModule mJpegPictureCallbackTime = 0; mZoomValue = 0; - // Start the preview if it is not started. if (mCameraState == PREVIEW_STOPPED && mCameraStartUpThread == null) { resetExposureCompensation(); @@ -1375,8 +1330,6 @@ public class PhotoModule mCameraDevice.cancelAutoFocus(); } stopPreview(); - // Release surface texture. - ((CameraScreenNail) mActivity.mCameraScreenNail).releaseSurfaceTexture(); mNamedImages = null; @@ -1438,6 +1391,13 @@ public class PhotoModule } @Override + public void updateCameraOrientation() { + if (mDisplayRotation != Util.getDisplayRotation(mActivity)) { + setDisplayOrientation(); + } + } + + @Override public void onActivityResult( int requestCode, int resultCode, Intent data) { switch (requestCode) { @@ -1507,7 +1467,7 @@ public class PhotoModule case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: case KeyEvent.KEYCODE_FOCUS: - if (mActivity.isInCameraApp() && mFirstTimeInitialized) { + if (/*TODO: mActivity.isInCameraApp() &&*/ mFirstTimeInitialized) { if (event.getRepeatCount() == 0) { onShutterButtonFocus(true); } @@ -1540,7 +1500,7 @@ public class PhotoModule switch (keyCode) { case KeyEvent.KEYCODE_VOLUME_UP: case KeyEvent.KEYCODE_VOLUME_DOWN: - if (mActivity.isInCameraApp() && mFirstTimeInitialized) { + if (/*mActivity.isInCameraApp() && */ mFirstTimeInitialized) { onShutterButtonClick(); return true; } @@ -1573,13 +1533,15 @@ public class PhotoModule private void setDisplayOrientation() { mDisplayRotation = Util.getDisplayRotation(mActivity); mDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId); - mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId); + mCameraDisplayOrientation = mDisplayOrientation; mUI.setDisplayOrientation(mDisplayOrientation); if (mFocusManager != null) { mFocusManager.setDisplayOrientation(mDisplayOrientation); } - // GLRoot also uses the DisplayRotation, and needs to be told to layout to update - mActivity.getGLRoot().requestLayoutContentPane(); + // Change the camera display orientation + if (mCameraDevice != null) { + mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); + } } // Only called by UI thread. @@ -1611,40 +1573,15 @@ public class PhotoModule mFocusManager.setAeAwbLock(false); // Unlock AE and AWB. } setCameraParameters(UPDATE_PARAM_ALL); - - if (ApiHelper.HAS_SURFACE_TEXTURE) { - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - if (mUI.getSurfaceTexture() == null) { - Size size = mParameters.getPreviewSize(); - if (mCameraDisplayOrientation % 180 == 0) { - screenNail.setSize(size.width, size.height); - } else { - screenNail.setSize(size.height, size.width); - } - screenNail.enableAspectRatioClamping(); - mActivity.notifyScreenNailChanged(); - screenNail.acquireSurfaceTexture(); - CameraStartUpThread t = mCameraStartUpThread; - if (t != null && t.isCanceled()) { - return; // Exiting, so no need to get the surface texture. - } - mUI.setSurfaceTexture(screenNail.getSurfaceTexture()); - } else { - updatePreviewSize(screenNail); - } - mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); - Object st = mUI.getSurfaceTexture(); - if (st != null) { - mCameraDevice.setPreviewTextureAsync((SurfaceTexture) st); - } - } else { - mCameraDevice.setDisplayOrientation(mDisplayOrientation); - mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); + // 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) { @@ -1652,21 +1589,6 @@ public class PhotoModule } } - private void updatePreviewSize(CameraScreenNail snail) { - Size size = mParameters.getPreviewSize(); - int w = size.width; - int h = size.height; - if (mCameraDisplayOrientation % 180 != 0) { - w = size.height; - h = size.width; - } - if (snail.getWidth() != w || snail.getHeight() != h) { - snail.setSize(w, h); - } - snail.enableAspectRatioClamping(); - mActivity.notifyScreenNailChanged(); - } - @Override public void stopPreview() { if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { @@ -1682,10 +1604,11 @@ public class PhotoModule 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); + int[] fpsRange = Util.getMaxPreviewFpsRange(mParameters); + if (fpsRange.length > 0) { + mParameters.setPreviewFpsRange( + fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX], + fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]); } mParameters.set(Util.RECORDING_HINT, Util.FALSE); @@ -1760,6 +1683,7 @@ public class PhotoModule 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 if (mHandler.getLooper() == Looper.myLooper()) { @@ -1950,16 +1874,12 @@ public class PhotoModule if (mPaused || mPendingSwitchCameraId != -1) return; mPendingSwitchCameraId = cameraId; - if (ApiHelper.HAS_SURFACE_TEXTURE) { - Log.v(TAG, "Start to copy texture. cameraId=" + cameraId); - // We need to keep a preview frame for the animation before - // releasing the camera. This will trigger onPreviewTextureCopied. - ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture(); - // Disable all camera controls. - setCameraState(SWITCHING_CAMERA); - } else { - switchCamera(); - } + + 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 @@ -1975,7 +1895,7 @@ public class PhotoModule @Override public void onUserInteraction() { - if (!mActivity.isFinishing()) keepScreenOnAwhile(); + if (!mActivity.isFinishing()) keepScreenOnAwhile(); } private void resetScreenOn() { @@ -1989,11 +1909,6 @@ public class PhotoModule mHandler.sendEmptyMessageDelayed(CLEAR_SCREEN_DELAY, SCREEN_DELAY); } - // TODO: Delete this function after old camera code is removed - @Override - public void onRestorePreferencesClicked() { - } - @Override public void onOverriddenPreferencesClicked() { if (mPaused) return; @@ -2027,16 +1942,6 @@ public class PhotoModule } @Override - public boolean needsSwitcher() { - return !mIsImageCaptureIntent; - } - - @Override - public boolean needsPieMenu() { - return true; - } - - @Override public void onShowSwitcherPopup() { mUI.onShowSwitcherPopup(); } @@ -2101,4 +2006,18 @@ public class PhotoModule mHeading += 360; } } + + @Override + public void onSwitchMode(boolean toCamera) { + mUI.onSwitchMode(toCamera); + } + +/* 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() {} + } diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java index a364d797e..b2a9df8cc 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2012 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. @@ -17,13 +17,22 @@ package com.android.camera; +import android.animation.Animator; +import android.animation.ObjectAnimator; +import android.animation.ValueAnimator; +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.SurfaceHolder; +import android.view.TextureView; import android.view.View; import android.view.View.OnClickListener; import android.view.View.OnLayoutChangeListener; @@ -31,15 +40,20 @@ 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.CameraControls; +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.CameraRootView; import com.android.camera.ui.PieRenderer; import com.android.camera.ui.PieRenderer.PieListener; import com.android.camera.ui.RenderOverlay; @@ -47,25 +61,23 @@ 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 PhotoUI implements PieListener, - SurfaceHolder.Callback, PreviewGestures.SingleTapListener, - FocusUI, - LocationManager.Listener, - FaceDetectionListener, - PreviewGestures.SwipeListener { + FocusUI, TextureView.SurfaceTextureListener, + LocationManager.Listener, CameraRootView.MyDisplayListener, + FaceDetectionListener { private static final String TAG = "CAM_UI"; - + private static final int UPDATE_TRANSFORM_MATRIX = 1; private CameraActivity mActivity; private PhotoController mController; private PreviewGestures mGestures; private View mRootView; private Object mSurfaceTexture; - private volatile SurfaceHolder mSurfaceHolder; private AbstractSettingPopup mPopup; private ShutterButton mShutterButton; @@ -80,7 +92,10 @@ public class PhotoUI implements PieListener, private View mMenuButton; private View mBlocker; private PhotoMenu mMenu; + private CameraSwitcher mSwitcher; + private CameraControls mCameraControls; + // Small indicators which show the camera settings in the viewfinder. private OnScreenIndicators mOnScreenIndicators; private PieRenderer mPieRenderer; @@ -92,7 +107,34 @@ public class PhotoUI implements PieListener, private int mPreviewWidth = 0; private int mPreviewHeight = 0; + private float mSurfaceTextureUncroppedWidth; + private float mSurfaceTextureUncroppedHeight; + private View mPreviewThumb; + private ObjectAnimator mFlashAnim; + private View mFlashOverlay; + + 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 @@ -107,14 +149,35 @@ public class PhotoUI implements PieListener, w = height; h = width; } - if (mPreviewWidth != w || mPreviewHeight != h) { - mPreviewWidth = w; - mPreviewHeight = h; + if (mPreviewWidth != width || mPreviewHeight != height) { + mPreviewWidth = width; + mPreviewHeight = height; + onScreenSizeChanged(width, height, w, h); mController.onScreenSizeChanged(width, height, w, h); } } }; + private ValueAnimator.AnimatorListener mAnimatorListener = + new ValueAnimator.AnimatorListener() { + + @Override + public void onAnimationCancel(Animator arg0) {} + + @Override + public void onAnimationEnd(Animator arg0) { + mFlashOverlay.setAlpha(0f); + mFlashOverlay.setVisibility(View.GONE); + mFlashAnim.removeListener(this); + } + + @Override + public void onAnimationRepeat(Animator arg0) {} + + @Override + public void onAnimationStart(Animator arg0) {} + }; + public PhotoUI(CameraActivity activity, PhotoController controller, View parent) { mActivity = activity; mController = controller; @@ -123,20 +186,107 @@ public class PhotoUI implements PieListener, mActivity.getLayoutInflater().inflate(R.layout.photo_module, (ViewGroup) mRootView, true); mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); - + mFlashOverlay = mRootView.findViewById(R.id.flash_overlay); + // display the view + mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content); + mTextureView.setSurfaceTextureListener(this); + mTextureView.addOnLayoutChangeListener(mLayoutListener); initIndicators(); - mCountDownView = (CountDownView) (mRootView.findViewById(R.id.count_down_to_capture)); - mCountDownView.setCountDownFinishedListener((OnCountDownFinishedListener) mController); + 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 = (CameraControls) mRootView.findViewById(R.id.camera_controls); + ((CameraRootView) mRootView).setDisplayChangeListener(this); + } + + 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() { @@ -145,7 +295,7 @@ public class PhotoUI implements PieListener, private void initIndicators() { mOnScreenIndicators = new OnScreenIndicators(mActivity, - mActivity.findViewById(R.id.on_screen_indicators)); + mRootView.findViewById(R.id.on_screen_indicators)); } public void onCameraOpened(PreferenceGroup prefGroup, ComboPreferences prefs, @@ -155,6 +305,7 @@ public class PhotoUI implements PieListener, mPieRenderer.setPieListener(this); mRenderOverlay.addRenderer(mPieRenderer); } + if (mMenu == null) { mMenu = new PhotoMenu(mActivity, this, mPieRenderer); mMenu.setListener(listener); @@ -165,25 +316,14 @@ public class PhotoUI implements PieListener, mZoomRenderer = new ZoomRenderer(mActivity); mRenderOverlay.addRenderer(mZoomRenderer); } + if (mGestures == null) { // this will handle gesture disambiguation and dispatching - mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, - this); + mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); + mRenderOverlay.setGestures(mGestures); } - mGestures.reset(); + mGestures.setZoomEnabled(params.isZoomSupported()); mGestures.setRenderOverlay(mRenderOverlay); - mGestures.addTouchReceiver(mMenuButton); - mGestures.addUnclickableArea(mBlocker); - enablePreviewThumb(false); - // 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); @@ -202,15 +342,16 @@ public class PhotoUI implements PieListener, } public void initializeControlByIntent() { - mBlocker = mActivity.findViewById(R.id.blocker); + mBlocker = mRootView.findViewById(R.id.blocker); mPreviewThumb = mActivity.findViewById(R.id.preview_thumb); mPreviewThumb.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - mActivity.gotoGallery(); + // TODO: go to filmstrip + // mActivity.gotoGallery(); } }); - mMenuButton = mActivity.findViewById(R.id.menu); + mMenuButton = mRootView.findViewById(R.id.menu); mMenuButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { @@ -218,13 +359,13 @@ public class PhotoUI implements PieListener, } }); if (mController.isImageCaptureIntent()) { - mActivity.hideSwitcher(); - ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls); + hideSwitcher(); + ViewGroup cameraControls = (ViewGroup) mRootView.findViewById(R.id.camera_controls); mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls); - mReviewDoneButton = mActivity.findViewById(R.id.btn_done); - mReviewCancelButton = mActivity.findViewById(R.id.btn_cancel); - mReviewRetakeButton = mActivity.findViewById(R.id.btn_retake); + 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() { @@ -249,14 +390,29 @@ public class PhotoUI implements PieListener, } } + public void hideUI() { + mCameraControls.setVisibility(View.INVISIBLE); + mSwitcher.closePopup(); + } + + public void showUI() { + mCameraControls.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 = mActivity.getShutterButton(); mShutterButton.setImageResource(R.drawable.btn_new_shutter); mShutterButton.setOnShutterButtonListener(mController); mShutterButton.setVisibility(View.VISIBLE); - mRootView.addOnLayoutChangeListener(mLayoutListener); } // called from onResume every other time @@ -268,7 +424,6 @@ public class PhotoUI implements PieListener, if (mMenu != null) { mMenu.reloadPreferences(); } - mRootView.addOnLayoutChangeListener(mLayoutListener); } public void initializeZoom(Camera.Parameters params) { @@ -286,16 +441,8 @@ public class PhotoUI implements PieListener, } } - public void enableGestures(boolean enable) { - if (mGestures != null) { - mGestures.setEnabled(enable); - } - } - - @Override public void showGpsOnScreenIndicator(boolean hasSignal) { } - @Override public void hideGpsOnScreenIndicator() { } public void overrideSettings(final String ... keyvalues) { @@ -323,11 +470,29 @@ public class PhotoUI implements PieListener, public void setCameraState(int state) { } - public boolean dispatchTouchEvent(MotionEvent m) { - if (mGestures != null && mRenderOverlay != null) { - return mGestures.dispatchTouch(m); + public void animateFlash() { + // End the previous animation if the previous one is still running + if (mFlashAnim != null && mFlashAnim.isRunning()) { + mFlashAnim.end(); } - return false; + // Start new flash animation. + mFlashOverlay.setVisibility(View.VISIBLE); + mFlashAnim = ObjectAnimator.ofFloat((Object) mFlashOverlay, "alpha", 0.3f, 0f); + mFlashAnim.setDuration(300); + mFlashAnim.addListener(mAnimatorListener); + mFlashAnim.start(); + } + + 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() { @@ -352,36 +517,36 @@ public class PhotoUI implements PieListener, } } - public void onFullScreenChanged(boolean full) { + public void onSwitchMode(boolean toCamera) { + if (toCamera) { + showUI(); + } else { + hideUI(); + } if (mFaceView != null) { - mFaceView.setBlockDraw(!full); + mFaceView.setBlockDraw(!toCamera); } if (mPopup != null) { - dismissPopup(full); + dismissPopup(toCamera); } if (mGestures != null) { - mGestures.setEnabled(full); + mGestures.setEnabled(toCamera); } if (mRenderOverlay != null) { // this can not happen in capture mode - mRenderOverlay.setVisibility(full ? View.VISIBLE : View.GONE); + mRenderOverlay.setVisibility(toCamera ? View.VISIBLE : View.GONE); } if (mPieRenderer != null) { - mPieRenderer.setBlockFocus(!full); - } - setShowMenu(full); - if (mBlocker != null) { - mBlocker.setVisibility(full ? View.VISIBLE : View.GONE); + mPieRenderer.setBlockFocus(!toCamera); } - if (!full && mCountDownView != null) mCountDownView.cancelCountDown(); + setShowMenu(toCamera); + if (!toCamera && mCountDownView != null) mCountDownView.cancelCountDown(); } public void enablePreviewThumb(boolean enabled) { if (enabled) { - mGestures.addTouchReceiver(mPreviewThumb); mPreviewThumb.setVisibility(View.VISIBLE); } else { - mGestures.removeTouchReceiver(mPreviewThumb); mPreviewThumb.setVisibility(View.GONE); } } @@ -396,7 +561,7 @@ public class PhotoUI implements PieListener, } public void showPopup(AbstractSettingPopup popup) { - mActivity.hideUI(); + hideUI(); mBlocker.setVisibility(View.INVISIBLE); setShowMenu(false); mPopup = popup; @@ -405,7 +570,6 @@ public class PhotoUI implements PieListener, LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.CENTER; ((FrameLayout) mRootView).addView(mPopup, lp); - mGestures.addTouchReceiver(mPopup); } public void dismissPopup() { @@ -414,12 +578,11 @@ public class PhotoUI implements PieListener, private void dismissPopup(boolean fullScreen) { if (fullScreen) { - mActivity.showUI(); + showUI(); mBlocker.setVisibility(View.VISIBLE); } setShowMenu(fullScreen); if (mPopup != null) { - mGestures.removeTouchReceiver(mPopup); ((FrameLayout) mRootView).removeView(mPopup); mPopup = null; } @@ -482,8 +645,20 @@ public class PhotoUI implements PieListener, return mShutterButton.isPressed(); } - // focus handling + 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 @@ -511,9 +686,8 @@ public class PhotoUI implements PieListener, @Override public void onPieOpened(int centerX, int centerY) { + setSwipingEnabled(false); dismissPopup(); - mActivity.cancelActivityTouchHandling(); - mActivity.setSwipingEnabled(false); if (mFaceView != null) { mFaceView.setBlockDraw(true); } @@ -521,55 +695,49 @@ public class PhotoUI implements PieListener, @Override public void onPieClosed() { - mActivity.setSwipingEnabled(true); + setSwipingEnabled(true); if (mFaceView != null) { mFaceView.setBlockDraw(false); } } - // Surface Listener - - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - Log.v(TAG, "surfaceChanged:" + holder + " width=" + width + ". height=" - + height); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.v(TAG, "surfaceCreated: " + holder); - mSurfaceHolder = holder; - mController.onSurfaceCreated(holder); - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.v(TAG, "surfaceDestroyed: " + holder); - mSurfaceHolder = null; - mController.stopPreview(); + public void setSwipingEnabled(boolean enable) { + mActivity.setSwipingEnabled(enable); } 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; } - public void setSurfaceTexture(Object st) { - mSurfaceTexture = st; - } + // Countdown timer - public SurfaceHolder getSurfaceHolder() { - return mSurfaceHolder; + 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.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); } @@ -582,41 +750,16 @@ public class PhotoUI implements PieListener, } public void onPause() { - mCountDownView.cancelCountDown(); - // Close the camera now because other activities may need to use it. - mSurfaceTexture = null; + cancelCountDown(); // Clear UI. collapseCameraControls(); if (mFaceView != null) mFaceView.clear(); - - mRootView.removeOnLayoutChangeListener(mLayoutListener); mPreviewWidth = 0; mPreviewHeight = 0; } - 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); - } - - // forward from preview gestures to controller - @Override - public void onSingleTapUp(View view, int x, int y) { - mController.onSingleTapUp(view, x, y); - } - // focus UI implementation private FocusIndicator getFocusIndicator() { @@ -681,11 +824,9 @@ public class PhotoUI implements PieListener, mFaceView.setFaces(faces); } - @Override - public void onSwipe(int direction) { - if (direction == PreviewGestures.DIR_UP) { - openMenu(); - } + public void onDisplayChanged() { + mCameraControls.checkLayoutFlip(); + mController.updateCameraOrientation(); } } diff --git a/src/com/android/camera/PreviewGestures.java b/src/com/android/camera/PreviewGestures.java index 0b80ff688..925607e80 100644 --- a/src/com/android/camera/PreviewGestures.java +++ b/src/com/android/camera/PreviewGestures.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 The Android Open Source Project + * 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. @@ -18,6 +18,7 @@ package com.android.camera; import android.os.Handler; import android.os.Message; +import android.view.GestureDetector; import android.view.MotionEvent; import android.view.ScaleGestureDetector; import android.view.View; @@ -28,9 +29,10 @@ 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; - +/* PreviewGestures disambiguates touch events received on RenderOverlay + * and dispatch them to the proper recipient (i.e. zoom renderer or pie renderer). + * Touch events on CameraControls will be handled by framework. + * */ public class PreviewGestures implements ScaleGestureDetector.OnScaleGestureListener { @@ -58,23 +60,55 @@ public class PreviewGestures private MotionEvent mDown; private MotionEvent mCurrent; private ScaleGestureDetector mScale; - private List<View> mReceivers; - private List<View> mUnclickableAreas; private int mMode; private int mSlop; private int mTapTimeout; + private boolean mZoomEnabled; private boolean mEnabled; private boolean mZoomOnly; private int mOrientation; - private int[] mLocation; - private SwipeListener mSwipeListener; + private GestureDetector mGestureDetector; + + private GestureDetector.SimpleOnGestureListener mGestureListener = new GestureDetector.SimpleOnGestureListener() { + @Override + public void onLongPress (MotionEvent e) { + // Open pie + if (mPie != null && !mPie.showsItems()) { + openPie(); + } + } + + @Override + public boolean onSingleTapUp (MotionEvent e) { + // Tap to focus when pie is not open + if (mPie == null || !mPie.showsItems()) { + mTapListener.onSingleTapUp(null, (int) e.getX(), (int) e.getY()); + return true; + } + return false; + } + + @Override + public boolean onScroll (MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + if (mMode == MODE_ZOOM) return false; + int deltaX = (int) (e1.getX() - e2.getX()); + int deltaY = (int) (e1.getY() - e2.getY()); + if (deltaY > 2 * deltaX && deltaY > -2 * deltaX) { + // Open pie on swipe up + if (mPie != null && !mPie.showsItems()) { + openPie(); + return true; + } + } + return false; + } + }; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { if (msg.what == MSG_PIE) { mMode = MODE_PIE; openPie(); - cancelActivityTouchHandling(mDown); } } }; @@ -83,12 +117,8 @@ public class PreviewGestures public void onSingleTapUp(View v, int x, int y); } - interface SwipeListener { - public void onSwipe(int direction); - } - public PreviewGestures(CameraActivity ctx, SingleTapListener tapListener, - ZoomRenderer zoom, PieRenderer pie, SwipeListener swipe) { + ZoomRenderer zoom, PieRenderer pie) { mActivity = ctx; mTapListener = tapListener; mPie = pie; @@ -98,8 +128,7 @@ public class PreviewGestures mSlop = (int) ctx.getResources().getDimension(R.dimen.pie_touch_slop); mTapTimeout = ViewConfiguration.getTapTimeout(); mEnabled = true; - mLocation = new int[2]; - mSwipeListener = swipe; + mGestureDetector = new GestureDetector(mGestureListener); } public void setRenderOverlay(RenderOverlay overlay) { @@ -112,184 +141,51 @@ public class PreviewGestures 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 removeTouchReceiver(View v) { - if (mReceivers == null || v == null) return; - mReceivers.remove(v); - } - - public void addUnclickableArea(View v) { - if (mUnclickableAreas == null) { - mUnclickableAreas = new ArrayList<View>(); - } - mUnclickableAreas.add(v); - } - - public void clearTouchReceivers() { - if (mReceivers != null) { - mReceivers.clear(); - } } - public void clearUnclickableAreas() { - if (mUnclickableAreas != null) { - mUnclickableAreas.clear(); - } + public void setZoomEnabled(boolean enable) { + mZoomEnabled = enable; } - private boolean checkClickable(MotionEvent m) { - if (mUnclickableAreas != null) { - for (View v : mUnclickableAreas) { - if (isInside(m, v)) { - return false; - } - } - } - return true; + public void setZoomOnly(boolean zoom) { + mZoomOnly = zoom; } - public void reset() { - clearTouchReceivers(); - clearUnclickableAreas(); + public boolean isEnabled() { + return mEnabled; } public boolean dispatchTouch(MotionEvent m) { if (!mEnabled) { - return mActivity.superDispatchTouchEvent(m); + return false; } mCurrent = m; if (MotionEvent.ACTION_DOWN == m.getActionMasked()) { - if (checkReceivers(m)) { - mMode = MODE_MODULE; - return mActivity.superDispatchTouchEvent(m); - } else { - mMode = MODE_ALL; - mDown = MotionEvent.obtain(m); - if (mPie != null && mPie.showsItems()) { - mMode = MODE_PIE; - return sendToPie(m); - } - if (mPie != null && !mZoomOnly && checkClickable(m)) { - mHandler.sendEmptyMessageDelayed(MSG_PIE, TIMEOUT_PIE); - } - if (mZoom != null) { - mScale.onTouchEvent(m); - } - // make sure this is ok - return mActivity.superDispatchTouchEvent(m); - } - } 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 mActivity.superDispatchTouchEvent(m); - } 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(); - // must have been tap - if (m.getEventTime() - mDown.getEventTime() < mTapTimeout - && checkClickable(m)) { - cancelActivityTouchHandling(m); - mTapListener.onSingleTapUp(null, - (int) mDown.getX() - mOverlay.getWindowPositionX(), - (int) mDown.getY() - mOverlay.getWindowPositionY()); - return true; - } else { - return mActivity.superDispatchTouchEvent(m); - } - } 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 mActivity.superDispatchTouchEvent(m); - } else { - cancelActivityTouchHandling(m); - mMode = MODE_NONE; - } - } - } - return false; + mMode = MODE_NONE; + mDown = MotionEvent.obtain(m); } - } - private boolean checkReceivers(MotionEvent m) { - if (mReceivers != null) { - for (View receiver : mReceivers) { - if (isInside(m, receiver)) { - return true; + // If pie is open, redirects all the touch events to pie. + if (mPie != null && mPie.isOpen()) { + return sendToPie(m); + } + + // If pie is not open, send touch events to gesture detector and scale + // listener to recognize the gesture. + mGestureDetector.onTouchEvent(m); + if (mZoom != null) { + mScale.onTouchEvent(m); + if (MotionEvent.ACTION_POINTER_DOWN == m.getActionMasked()) { + mMode = MODE_ZOOM; + if (mZoomEnabled) { + // Start showing zoom UI as soon as there is a second finger down + mZoom.onScaleBegin(mScale); } + } else if (MotionEvent.ACTION_POINTER_UP == m.getActionMasked()) { + mZoom.onScaleEnd(mScale); } } - return false; + return true; } // left tests for finger moving right to left @@ -320,25 +216,6 @@ public class PreviewGestures return DIR_UP; } - private boolean isInside(MotionEvent evt, View v) { - v.getLocationInWindow(mLocation); - // when view is flipped horizontally - if ((int) v.getRotationY() == 180) { - mLocation[0] -= v.getWidth(); - } - // when view is flipped vertically - if ((int) v.getRotationX() == 180) { - mLocation[1] -= v.getHeight(); - } - 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) { - mActivity.superDispatchTouchEvent(makeCancelEvent(m)); - } - private MotionEvent makeCancelEvent(MotionEvent m) { MotionEvent c = MotionEvent.obtain(m); c.setAction(MotionEvent.ACTION_CANCEL); @@ -346,21 +223,16 @@ public class PreviewGestures } private void openPie() { - mDown.offsetLocation(-mOverlay.getWindowPositionX(), - -mOverlay.getWindowPositionY()); + mGestureDetector.onTouchEvent(makeCancelEvent(mDown)); + mScale.onTouchEvent(makeCancelEvent(mDown)); 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); } + // OnScaleGestureListener implementation @Override public boolean onScale(ScaleGestureDetector detector) { return mZoom.onScale(detector); @@ -368,21 +240,18 @@ public class PreviewGestures @Override public boolean onScaleBegin(ScaleGestureDetector detector) { - if (mMode != MODE_ZOOM) { + if (mPie == null || !mPie.isOpen()) { mMode = MODE_ZOOM; - cancelActivityTouchHandling(mCurrent); - } - if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) { + mGestureDetector.onTouchEvent(makeCancelEvent(mCurrent)); + if (!mZoomEnabled) return false; return mZoom.onScaleBegin(detector); - } else { - return true; } + return false; } @Override public void onScaleEnd(ScaleGestureDetector detector) { - if (mCurrent.getActionMasked() != MotionEvent.ACTION_MOVE) { - mZoom.onScaleEnd(detector); - } + mZoom.onScaleEnd(detector); } } + diff --git a/src/com/android/camera/SurfaceTextureRenderer.java b/src/com/android/camera/SurfaceTextureRenderer.java new file mode 100644 index 000000000..66f7aa219 --- /dev/null +++ b/src/com/android/camera/SurfaceTextureRenderer.java @@ -0,0 +1,224 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera; + +import android.graphics.SurfaceTexture; +import android.os.Handler; +import android.util.Log; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import javax.microedition.khronos.egl.EGLSurface; +import javax.microedition.khronos.opengles.GL10; + +public class SurfaceTextureRenderer { + + public interface FrameDrawer { + public void onDrawFrame(GL10 gl); + } + + private static final String TAG = "CAM_" + SurfaceTextureRenderer.class.getSimpleName(); + private static final int EGL_CONTEXT_CLIENT_VERSION = 0x3098; + + private EGLConfig mEglConfig; + private EGLDisplay mEglDisplay; + private EGLContext mEglContext; + private EGLSurface mEglSurface; + private EGL10 mEgl; + private GL10 mGl; + + private Handler mEglHandler; + private FrameDrawer mFrameDrawer; + + private Object mRenderLock = new Object(); + private Runnable mRenderTask = new Runnable() { + @Override + public void run() { + synchronized (mRenderLock) { + mFrameDrawer.onDrawFrame(mGl); + mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); + mRenderLock.notifyAll(); + } + } + }; + + public class RenderThread extends Thread { + private Boolean mRenderStopped = false; + + @Override + public void run() { + while (true) { + synchronized (mRenderStopped) { + if (mRenderStopped) return; + } + draw(true); + } + } + + public void stopRender() { + synchronized (mRenderStopped) { + mRenderStopped = true; + } + } + } + + public SurfaceTextureRenderer(SurfaceTexture tex, + Handler handler, FrameDrawer renderer) { + mEglHandler = handler; + mFrameDrawer = renderer; + + initialize(tex); + } + + public RenderThread createRenderThread() { + return new RenderThread(); + } + + public void release() { + mEglHandler.post(new Runnable() { + @Override + public void run() { + mEgl.eglDestroySurface(mEglDisplay, mEglSurface); + mEgl.eglDestroyContext(mEglDisplay, mEglContext); + mEgl.eglMakeCurrent(mEglDisplay, EGL10.EGL_NO_SURFACE, EGL10.EGL_NO_SURFACE, + EGL10.EGL_NO_CONTEXT); + mEgl.eglTerminate(mEglDisplay); + mEglSurface = null; + mEglContext = null; + mEglDisplay = null; + } + }); + } + + /** + * Posts a render request to the GL thread. + * @param sync set <code>true</code> if the caller needs it to be + * a synchronous call. + */ + public void draw(boolean sync) { + synchronized (mRenderLock) { + mEglHandler.post(mRenderTask); + if (sync) { + try { + mRenderLock.wait(); + } catch (InterruptedException ex) { + Log.v(TAG, "RenderLock.wait() interrupted"); + } + } + } + } + + private void initialize(final SurfaceTexture target) { + mEglHandler.post(new Runnable() { + @Override + public void run() { + mEgl = (EGL10) EGLContext.getEGL(); + mEglDisplay = mEgl.eglGetDisplay(EGL10.EGL_DEFAULT_DISPLAY); + if (mEglDisplay == EGL10.EGL_NO_DISPLAY) { + throw new RuntimeException("eglGetDisplay failed"); + } + int[] version = new int[2]; + if (!mEgl.eglInitialize(mEglDisplay, version)) { + throw new RuntimeException("eglInitialize failed"); + } else { + Log.v(TAG, "EGL version: " + version[0] + '.' + version[1]); + } + int[] attribList = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL10.EGL_NONE }; + mEglConfig = chooseConfig(mEgl, mEglDisplay); + mEglContext = mEgl.eglCreateContext( + mEglDisplay, mEglConfig, EGL10.EGL_NO_CONTEXT, attribList); + + if (mEglContext == null || mEglContext == EGL10.EGL_NO_CONTEXT) { + throw new RuntimeException("failed to createContext"); + } + mEglSurface = mEgl.eglCreateWindowSurface( + mEglDisplay, mEglConfig, target, null); + if (mEglSurface == null || mEglSurface == EGL10.EGL_NO_SURFACE) { + throw new RuntimeException("failed to createWindowSurface"); + } + + if (!mEgl.eglMakeCurrent( + mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + throw new RuntimeException("failed to eglMakeCurrent"); + } + + mGl = (GL10) mEglContext.getGL(); + } + }); + waitDone(); + } + + private void waitDone() { + final Object lock = new Object(); + synchronized (lock) { + mEglHandler.post(new Runnable() { + @Override + public void run() { + synchronized (lock) { + lock.notifyAll(); + } + } + }); + try { + lock.wait(); + } catch (InterruptedException ex) { + Log.v(TAG, "waitDone() interrupted"); + } + } + } + + private static void checkEglError(String prompt, EGL10 egl) { + int error; + while ((error = egl.eglGetError()) != EGL10.EGL_SUCCESS) { + Log.e(TAG, String.format("%s: EGL error: 0x%x", prompt, error)); + } + } + + private static final int EGL_OPENGL_ES2_BIT = 4; + private static final int[] CONFIG_SPEC = new int[] { + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + EGL10.EGL_RED_SIZE, 8, + EGL10.EGL_GREEN_SIZE, 8, + EGL10.EGL_BLUE_SIZE, 8, + EGL10.EGL_ALPHA_SIZE, 0, + EGL10.EGL_DEPTH_SIZE, 0, + EGL10.EGL_STENCIL_SIZE, 0, + EGL10.EGL_NONE + }; + + private static EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + int[] numConfig = new int[1]; + if (!egl.eglChooseConfig(display, CONFIG_SPEC, null, 0, numConfig)) { + throw new IllegalArgumentException("eglChooseConfig failed"); + } + + int numConfigs = numConfig[0]; + if (numConfigs <= 0) { + throw new IllegalArgumentException("No configs match configSpec"); + } + + EGLConfig[] configs = new EGLConfig[numConfigs]; + if (!egl.eglChooseConfig( + display, CONFIG_SPEC, configs, numConfigs, numConfig)) { + throw new IllegalArgumentException("eglChooseConfig#2 failed"); + } + + return configs[0]; + } +} diff --git a/src/com/android/camera/VideoController.java b/src/com/android/camera/VideoController.java index 474f521de..b53dec616 100644 --- a/src/com/android/camera/VideoController.java +++ b/src/com/android/camera/VideoController.java @@ -33,4 +33,6 @@ public interface VideoController extends OnShutterButtonListener { public void onSingleTapUp(View view, int x, int y); public void stopPreview(); + + public void updateCameraOrientation(); } diff --git a/src/com/android/camera/VideoMenu.java b/src/com/android/camera/VideoMenu.java index 9bfcdea5d..da0bde10e 100644 --- a/src/com/android/camera/VideoMenu.java +++ b/src/com/android/camera/VideoMenu.java @@ -16,6 +16,7 @@ package com.android.camera; +import android.app.Activity; import android.content.Context; import android.view.LayoutInflater; @@ -51,6 +52,7 @@ public class VideoMenu extends PieController mActivity = activity; } + public void initialize(PreferenceGroup group) { super.initialize(group); mPopup = null; @@ -200,5 +202,4 @@ public class VideoMenu extends PieController mUI.showPopup(mPopup); mPopupStatus = POPUP_SECOND_LEVEL; } - } diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java index 2c654fc29..839037fc0 100644 --- a/src/com/android/camera/VideoModule.java +++ b/src/com/android/camera/VideoModule.java @@ -51,13 +51,16 @@ 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; @@ -113,7 +116,7 @@ public class VideoModule implements CameraModule, private Parameters mParameters; private Boolean mCameraOpened = false; - + private boolean mIsInReviewMode; private boolean mSnapshotInProgress = false; private static final String EFFECT_BG_FROM_GALLERY = "gallery"; @@ -123,11 +126,8 @@ public class VideoModule implements CameraModule, private ComboPreferences mPreferences; private PreferenceGroup mPreferenceGroup; - private CameraScreenNail.OnFrameDrawnListener mFrameDrawnListener; - private boolean mIsVideoCaptureIntent; private boolean mQuickCapture; - private boolean mIsInReviewMode = false; private MediaRecorder mMediaRecorder; private EffectsRecorder mEffectsRecorder; @@ -176,11 +176,16 @@ public class VideoModule implements CameraModule, private ContentResolver mContentResolver; private LocationManager mLocationManager; + private OrientationManager mOrientationManager; + private Surface mSurface; private int mPendingSwitchCameraId; - + private boolean mOpenCameraFail; + private boolean mCameraDisabled; private final Handler mHandler = new MainHandler(); private VideoUI mUI; + private CameraProxy mCameraDevice; + // The degrees of the device rotated clockwise from its natural orientation. private int mOrientation = OrientationEventListener.ORIENTATION_UNKNOWN; @@ -194,7 +199,6 @@ public class VideoModule implements CameraModule, @Override public void onMediaSaved(Uri uri) { if (uri != null) { - mActivity.addSecureAlbumItemIfNeeded(true, uri); mActivity.sendBroadcast( new Intent(Util.ACTION_NEW_VIDEO, uri)); Util.broadcastNewPicture(mActivity, uri); @@ -224,15 +228,15 @@ public class VideoModule implements CameraModule, try { synchronized(mCameraOpened) { if (!mCameraOpened) { - mActivity.mCameraDevice = Util.openCamera(mActivity, mCameraId); + mCameraDevice = Util.openCamera(mActivity, mCameraId); mCameraOpened = true; } } - mParameters = mActivity.mCameraDevice.getParameters(); + mParameters = mCameraDevice.getParameters(); } catch (CameraHardwareException e) { - mActivity.mOpenCameraFail = true; + mOpenCameraFail = true; } catch (CameraDisabledException e) { - mActivity.mCameraDisabled = true; + mCameraDisabled = true; } } @@ -285,18 +289,14 @@ public class VideoModule implements CameraModule, } case SWITCH_CAMERA_START_ANIMATION: { - ((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera(); + //TODO: + //((CameraScreenNail) mActivity.mCameraScreenNail).animateSwitchCamera(); // Enable all camera controls. mSwitchingCamera = false; break; } - case HIDE_SURFACE_VIEW: { - mUI.hideSurfaceView(); - break; - } - case CAPTURE_ANIMATION_DONE: { mUI.enablePreviewThumb(false); break; @@ -345,18 +345,12 @@ public class VideoModule implements CameraModule, private void initializeSurfaceView() { if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // API level < 16 - mFrameDrawnListener = new CameraScreenNail.OnFrameDrawnListener() { - @Override - public void onFrameDrawn(CameraScreenNail c) { - mHandler.sendEmptyMessage(HIDE_SURFACE_VIEW); - } - }; - mUI.getSurfaceHolder().addCallback(mUI); + mUI.initializeSurfaceView(); } } @Override - public void init(CameraActivity activity, View root, boolean reuseScreenNail) { + public void init(CameraActivity activity, View root) { mActivity = activity; mUI = new VideoUI(activity, this, root); mPreferences = new ComboPreferences(mActivity); @@ -366,9 +360,9 @@ public class VideoModule implements CameraModule, mPreferences.setLocalId(mActivity, mCameraId); CameraSettings.upgradeLocalPreferences(mPreferences.getLocal()); - mActivity.mNumberOfCameras = CameraHolder.instance().getNumberOfCameras(); 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. @@ -382,20 +376,15 @@ public class VideoModule implements CameraModule, // Surface texture is from camera screen nail and startPreview needs it. // This must be done before startPreview. mIsVideoCaptureIntent = isVideoCaptureIntent(); - if (reuseScreenNail) { - mActivity.reuseCameraScreenNail(!mIsVideoCaptureIntent); - } else { - mActivity.createCameraScreenNail(!mIsVideoCaptureIntent); - } initializeSurfaceView(); // Make sure camera device is opened. try { cameraOpenThread.join(); - if (mActivity.mOpenCameraFail) { + if (mOpenCameraFail) { Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); return; - } else if (mActivity.mCameraDisabled) { + } else if (mCameraDisabled) { Util.showErrorAndFinish(mActivity, R.string.camera_disabled); return; } @@ -463,10 +452,10 @@ public class VideoModule implements CameraModule, mParameters.setRotation(rotation); Location loc = mLocationManager.getCurrentLocation(); Util.setGpsParameters(mParameters, loc); - mActivity.mCameraDevice.setParameters(mParameters); + mCameraDevice.setParameters(mParameters); Log.v(TAG, "Video snapshot start"); - mActivity.mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc)); + mCameraDevice.takePicture(null, null, null, new JpegPictureCallback(loc)); showVideoSnapshotUI(true); mSnapshotInProgress = true; UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, @@ -583,7 +572,8 @@ public class VideoModule implements CameraModule, // 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. - ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); + // TODO: need to get the capture animation to work + // ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); mUI.enablePreviewThumb(true); @@ -694,7 +684,7 @@ public class VideoModule implements CameraModule, @TargetApi(ApiHelper.VERSION_CODES.HONEYCOMB) private void getDesiredPreviewSize() { - mParameters = mActivity.mCameraDevice.getParameters(); + mParameters = mCameraDevice.getParameters(); if (ApiHelper.HAS_GET_SUPPORTED_VIDEO_SIZE) { if (mParameters.getSupportedVideoSizes() == null || effectsActive()) { mDesiredPreviewWidth = mProfile.videoFrameWidth; @@ -720,6 +710,7 @@ public class VideoModule implements CameraModule, mDesiredPreviewWidth = mProfile.videoFrameWidth; mDesiredPreviewHeight = mProfile.videoFrameHeight; } + mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight); Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth + ". mDesiredPreviewHeight=" + mDesiredPreviewHeight); } @@ -747,7 +738,7 @@ public class VideoModule implements CameraModule, @Override public void onResumeAfterSuper() { - if (mActivity.mOpenCameraFail || mActivity.mCameraDisabled) + if (mOpenCameraFail || mCameraDisabled) return; mUI.enableShutter(false); mZoomValue = 0; @@ -757,11 +748,11 @@ public class VideoModule implements CameraModule, if (!mPreviewing) { resetEffect(); openCamera(); - if (mActivity.mOpenCameraFail) { + if (mOpenCameraFail) { Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); return; - } else if (mActivity.mCameraDisabled) { + } else if (mCameraDisabled) { Util.showErrorAndFinish(mActivity, R.string.camera_disabled); return; } @@ -801,15 +792,19 @@ public class VideoModule implements CameraModule, private void setDisplayOrientation() { mDisplayRotation = Util.getDisplayRotation(mActivity); - if (ApiHelper.HAS_SURFACE_TEXTURE) { - // The display rotation is handled by gallery. - mCameraDisplayOrientation = Util.getDisplayOrientation(0, mCameraId); - } else { - // We need to consider display rotation ourselves. - mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId); + mCameraDisplayOrientation = Util.getDisplayOrientation(mDisplayRotation, mCameraId); + // Change the camera display orientation + if (mCameraDevice != null) { + mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); + } + } + + @Override + public void updateCameraOrientation() { + if (mMediaRecorderRecording) return; + if (mDisplayRotation != Util.getDisplayRotation(mActivity)) { + setDisplayOrientation(); } - // GLRoot also uses the DisplayRotation, and needs to be told to layout to update - mActivity.getGLRoot().requestLayoutContentPane(); } @Override @@ -817,18 +812,18 @@ public class VideoModule implements CameraModule, // Not useful to change zoom value when the activity is paused. if (mPaused) return index; mZoomValue = index; - if (mParameters == null || mActivity.mCameraDevice == null) return index; + if (mParameters == null || mCameraDevice == null) return index; // Set zoom parameters asynchronously mParameters.setZoom(mZoomValue); - mActivity.mCameraDevice.setParameters(mParameters); - Parameters p = mActivity.mCameraDevice.getParameters(); + mCameraDevice.setParameters(mParameters); + Parameters p = mCameraDevice.getParameters(); if (p != null) return p.getZoom(); return index; } private void startPreview() { Log.v(TAG, "startPreview"); - mActivity.mCameraDevice.setErrorCallback(mErrorCallback); + mCameraDevice.setErrorCallback(mErrorCallback); if (mPreviewing == true) { stopPreview(); if (effectsActive() && mEffectsRecorder != null) { @@ -838,22 +833,17 @@ public class VideoModule implements CameraModule, } setDisplayOrientation(); - mActivity.mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); + mCameraDevice.setDisplayOrientation(mCameraDisplayOrientation); setCameraParameters(); try { if (!effectsActive()) { - if (ApiHelper.HAS_SURFACE_TEXTURE) { - SurfaceTexture surfaceTexture = ((CameraScreenNail) mActivity.mCameraScreenNail) - .getSurfaceTexture(); - if (surfaceTexture == null) { - return; // The texture has been destroyed (pause, etc) - } - mActivity.mCameraDevice.setPreviewTextureAsync(surfaceTexture); - } else { - mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); + SurfaceTexture surfaceTexture = mUI.getSurfaceTexture(); + if (surfaceTexture == null) { + return; // The texture has been destroyed (pause, etc) } - mActivity.mCameraDevice.startPreviewAsync(); + mCameraDevice.setPreviewTextureAsync(surfaceTexture); + mCameraDevice.startPreviewAsync(); mPreviewing = true; onPreviewStarted(); } else { @@ -869,9 +859,9 @@ public class VideoModule implements CameraModule, mActivity.runOnUiThread(new Runnable() { @Override public void run() { - if (mActivity.mOpenCameraFail) { + if (mOpenCameraFail) { Util.showErrorAndFinish(mActivity, R.string.cannot_connect_camera); - } else if (mActivity.mCameraDisabled) { + } else if (mCameraDisabled) { Util.showErrorAndFinish(mActivity, R.string.camera_disabled); } } @@ -886,7 +876,8 @@ public class VideoModule implements CameraModule, @Override public void stopPreview() { - mActivity.mCameraDevice.stopPreview(); + if (!mPreviewing) return; + mCameraDevice.stopPreview(); mPreviewing = false; } @@ -922,7 +913,7 @@ public class VideoModule implements CameraModule, // effects also along with the camera. private void closeCamera(boolean closeEffectsAlso) { Log.v(TAG, "closeCamera"); - if (mActivity.mCameraDevice == null) { + if (mCameraDevice == null) { Log.d(TAG, "already stopped."); return; } @@ -933,27 +924,22 @@ public class VideoModule implements CameraModule, mEffectsRecorder.disconnectCamera(); } if (closeEffectsAlso) closeEffects(); - mActivity.mCameraDevice.setZoomChangeListener(null); - mActivity.mCameraDevice.setErrorCallback(null); + mCameraDevice.setZoomChangeListener(null); + mCameraDevice.setErrorCallback(null); synchronized(mCameraOpened) { if (mCameraOpened) { CameraHolder.instance().release(); } mCameraOpened = false; } - mActivity.mCameraDevice = null; + mCameraDevice = null; mPreviewing = false; mSnapshotInProgress = false; } private void releasePreviewResources() { - if (ApiHelper.HAS_SURFACE_TEXTURE) { - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - screenNail.releaseSurfaceTexture(); - if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { - mHandler.removeMessages(HIDE_SURFACE_VIEW); - mUI.hideSurfaceView(); - } + if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { + mUI.hideSurfaceView(); } } @@ -1096,13 +1082,11 @@ public class VideoModule implements CameraModule, private void setupMediaRecorderPreviewDisplay() { // Nothing to do here if using SurfaceTexture. - if (!ApiHelper.HAS_SURFACE_TEXTURE) { - mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface()); - } else if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { + if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { // We stop the preview here before unlocking the device because we // need to change the SurfaceTexture to SurfaceView for preview. stopPreview(); - mActivity.mCameraDevice.setPreviewDisplayAsync(mUI.getSurfaceHolder()); + 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 @@ -1110,9 +1094,9 @@ public class VideoModule implements CameraModule, // Gallery will handle the orientation for the preview. For // SurfaceView we will have to take everything into account so the // display rotation is considered. - mActivity.mCameraDevice.setDisplayOrientation( + mCameraDevice.setDisplayOrientation( Util.getDisplayOrientation(mDisplayRotation, mCameraId)); - mActivity.mCameraDevice.startPreviewAsync(); + mCameraDevice.startPreviewAsync(); mPreviewing = true; mMediaRecorder.setPreviewDisplay(mUI.getSurfaceHolder().getSurface()); } @@ -1122,15 +1106,14 @@ public class VideoModule implements CameraModule, private void initializeRecorder() { Log.v(TAG, "initializeRecorder"); // If the mCameraDevice is null, then this activity is going to finish - if (mActivity.mCameraDevice == null) return; + if (mCameraDevice == null) return; - if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING && ApiHelper.HAS_SURFACE_TEXTURE) { + 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(); - if (!mUI.isSurfaceViewReady()) return; } Intent intent = mActivity.getIntent(); @@ -1156,9 +1139,9 @@ public class VideoModule implements CameraModule, setupMediaRecorderPreviewDisplay(); // Unlock the camera object before passing it to media recorder. - mActivity.mCameraDevice.unlock(); - mActivity.mCameraDevice.waitDone(); - mMediaRecorder.setCamera(mActivity.mCameraDevice.getCamera()); + mCameraDevice.unlock(); + mCameraDevice.waitDone(); + mMediaRecorder.setCamera(mCameraDevice.getCamera()); if (!mCaptureTimeLapse) { mMediaRecorder.setAudioSource(MediaRecorder.AudioSource.CAMCORDER); } @@ -1244,7 +1227,7 @@ public class VideoModule implements CameraModule, private void initializeEffectsPreview() { Log.v(TAG, "initializeEffectsPreview"); // If the mCameraDevice is null, then this activity is going to finish - if (mActivity.mCameraDevice == null) return; + if (mCameraDevice == null) return; boolean inLandscape = (mActivity.getResources().getConfiguration().orientation == Configuration.ORIENTATION_LANDSCAPE); @@ -1257,7 +1240,7 @@ public class VideoModule implements CameraModule, // 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(mActivity.mCameraDevice); + mEffectsRecorder.setCamera(mCameraDevice); mEffectsRecorder.setCameraFacing(info.facing); mEffectsRecorder.setProfile(mProfile); mEffectsRecorder.setEffectsListener(this); @@ -1274,9 +1257,8 @@ public class VideoModule implements CameraModule, } mEffectsRecorder.setOrientationHint(orientation); - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - mEffectsRecorder.setPreviewSurfaceTexture(screenNail.getSurfaceTexture(), - screenNail.getWidth(), screenNail.getHeight()); + mEffectsRecorder.setPreviewSurfaceTexture(mUI.getSurfaceTexture(), + mUI.getPreviewWidth(), mUI.getPreviewHeight()); if (mEffectType == EffectsRecorder.EFFECT_BACKDROPPER && ((String) mEffectParameter).equals(EFFECT_BG_FROM_GALLERY)) { @@ -1472,7 +1454,7 @@ public class VideoModule implements CameraModule, private void startVideoRecording() { Log.v(TAG, "startVideoRecording"); mUI.enablePreviewThumb(false); - mActivity.setSwipingEnabled(false); + mUI.setSwipingEnabled(false); mActivity.updateStorageSpaceAndHint(); if (mActivity.getStorageSpace() <= Storage.LOW_STORAGE_THRESHOLD) { @@ -1480,7 +1462,7 @@ public class VideoModule implements CameraModule, return; } - if (!mActivity.mCameraDevice.waitDone()) return; + if (!mCameraDevice.waitDone()) return; mCurrentVideoUri = null; if (effectsActive()) { initializeEffectsRecording(); @@ -1513,31 +1495,31 @@ public class VideoModule implements CameraModule, Log.e(TAG, "Could not start media recorder. ", e); releaseMediaRecorder(); // If start fails, frameworks will not lock the camera for us. - mActivity.mCameraDevice.lock(); + mCameraDevice.lock(); return; } } // Make sure the video recording has started before announcing // this in accessibility. - AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(), + 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. - mActivity.mCameraDevice.refreshParameters(); + 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 = mActivity.mCameraDevice.getParameters(); + mParameters = mCameraDevice.getParameters(); } mUI.enableCameraControls(false); mMediaRecorderRecording = true; - mActivity.getOrientationManager().lockOrientation(); + mOrientationManager.lockOrientation(); mRecordingStartTime = SystemClock.uptimeMillis(); mUI.showRecordingUI(true, mParameters.isZoomSupported()); @@ -1581,8 +1563,8 @@ public class VideoModule implements CameraModule, private boolean stopVideoRecording() { Log.v(TAG, "stopVideoRecording"); - mActivity.setSwipingEnabled(true); - mActivity.showSwitcher(); + mUI.setSwipingEnabled(true); + mUI.showSwitcher(); boolean fail = false; if (mMediaRecorderRecording) { @@ -1604,7 +1586,7 @@ public class VideoModule implements CameraModule, mCurrentVideoFilename = mVideoFilename; Log.v(TAG, "stopVideoRecording: Setting current video filename: " + mCurrentVideoFilename); - AccessibilityUtils.makeAnnouncement(mActivity.getShutterButton(), + AccessibilityUtils.makeAnnouncement(mUI.getShutterButton(), mActivity.getString(R.string.video_recording_stopped)); } catch (RuntimeException e) { Log.e(TAG, "stop fail", e); @@ -1612,7 +1594,7 @@ public class VideoModule implements CameraModule, fail = true; } mMediaRecorderRecording = false; - mActivity.getOrientationManager().unlockOrientation(); + mOrientationManager.unlockOrientation(); // If the activity is paused, this means activity is interrupted // during recording. Release the camera as soon as possible because @@ -1646,21 +1628,19 @@ public class VideoModule implements CameraModule, if (!effectsActive()) { releaseMediaRecorder(); if (!mPaused) { - mActivity.mCameraDevice.lock(); - mActivity.mCameraDevice.waitDone(); - if (ApiHelper.HAS_SURFACE_TEXTURE && - !ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { + mCameraDevice.lock(); + mCameraDevice.waitDone(); + if (!ApiHelper.HAS_SURFACE_TEXTURE_RECORDING) { stopPreview(); + mUI.hideSurfaceView(); // Switch back to use SurfaceTexture for preview. - ((CameraScreenNail) mActivity.mCameraScreenNail).setOneTimeOnFrameDrawnListener( - mFrameDrawnListener); startPreview(); } } } // Update the parameters here because the parameters might have been altered // by MediaRecorder. - if (!mPaused) mParameters = mActivity.mCameraDevice.getParameters(); + if (!mPaused) mParameters = mCameraDevice.getParameters(); UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, fail ? UsageStatistics.ACTION_CAPTURE_FAIL : UsageStatistics.ACTION_CAPTURE_DONE, "Video", @@ -1792,11 +1772,18 @@ public class VideoModule implements CameraModule, @SuppressWarnings("deprecation") private void setCameraParameters() { mParameters.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight); - mParameters.setPreviewFrameRate(mProfile.videoFrameRate); + int[] fpsRange = Util.getMaxPreviewFpsRange(mParameters); + if (fpsRange.length > 0) { + mParameters.setPreviewFpsRange( + fpsRange[Parameters.PREVIEW_FPS_MIN_INDEX], + fpsRange[Parameters.PREVIEW_FPS_MAX_INDEX]); + } else { + mParameters.setPreviewFrameRate(mProfile.videoFrameRate); + } // Set flash mode. String flashMode; - if (mActivity.mShowCameraAppView) { + if (mUI.isVisible()) { flashMode = mPreferences.getString( CameraSettings.KEY_VIDEOCAMERA_FLASH_MODE, mActivity.getString(R.string.pref_camera_video_flashmode_default)); @@ -1867,35 +1854,9 @@ public class VideoModule implements CameraModule, CameraProfile.QUALITY_HIGH); mParameters.setJpegQuality(jpegQuality); - mActivity.mCameraDevice.setParameters(mParameters); + mCameraDevice.setParameters(mParameters); // Keep preview size up to date. - mParameters = mActivity.mCameraDevice.getParameters(); - - updateCameraScreenNailSize(mDesiredPreviewWidth, mDesiredPreviewHeight); - } - - private void updateCameraScreenNailSize(int width, int height) { - if (!ApiHelper.HAS_SURFACE_TEXTURE) return; - - if (mCameraDisplayOrientation % 180 != 0) { - int tmp = width; - width = height; - height = tmp; - } - - CameraScreenNail screenNail = (CameraScreenNail) mActivity.mCameraScreenNail; - int oldWidth = screenNail.getWidth(); - int oldHeight = screenNail.getHeight(); - - if (oldWidth != width || oldHeight != height) { - screenNail.setSize(width, height); - screenNail.enableAspectRatioClamping(); - mActivity.notifyScreenNailChanged(); - } - - if (screenNail.getSurfaceTexture() == null) { - screenNail.acquireSurfaceTexture(); - } + mParameters = mCameraDevice.getParameters(); } @Override @@ -2006,7 +1967,7 @@ public class VideoModule implements CameraModule, synchronized (mPreferences) { // If mCameraDevice is not ready then we can set the parameter in // startPreview(). - if (mActivity.mCameraDevice == null) return; + if (mCameraDevice == null) return; boolean recordLocation = RecordLocationPreference.get( mPreferences, mContentResolver); @@ -2062,14 +2023,13 @@ public class VideoModule implements CameraModule, initializeVideoControl(); // From onResume + mZoomValue = 0; mUI.initializeZoom(mParameters); mUI.setOrientationIndicator(0, false); - if (ApiHelper.HAS_SURFACE_TEXTURE) { - // Start switch camera animation. Post a message because - // onFrameAvailable from the old camera may already exist. - mHandler.sendEmptyMessage(SWITCH_CAMERA_START_ANIMATION); - } + // 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, mPreferences); } @@ -2138,32 +2098,23 @@ public class VideoModule implements CameraModule, 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) { - mActivity.setSingleTapUpListener(mUI.getPreview()); // Show the tap to focus toast if this is the first start. if (mPreferences.getBoolean( CameraSettings.KEY_VIDEO_FIRST_USE_HINT_SHOWN, true)) { // Delay the toast for one second to wait for orientation. mHandler.sendEmptyMessageDelayed(SHOW_TAP_TO_SNAPSHOT_TOAST, 1000); } - } else { - mActivity.setSingleTapUpListener(null); } } void showVideoSnapshotUI(boolean enabled) { if (mParameters == null) return; if (Util.isVideoSnapshotSupported(mParameters) && !mIsVideoCaptureIntent) { - if (ApiHelper.HAS_SURFACE_TEXTURE && enabled) { - ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); + if (enabled) { + // TODO: ((CameraScreenNail) mActivity.mCameraScreenNail).animateCapture(mDisplayRotation); } else { mUI.showPreviewBorder(enabled); } @@ -2176,7 +2127,7 @@ public class VideoModule implements CameraModule, if (!mPreviewing || mParameters.getFlashMode() == null) return; // When going to and back from gallery, we need to turn off/on the flash. - if (!mActivity.mShowCameraAppView) { + if (!mUI.isVisible()) { if (mParameters.getFlashMode().equals(Parameters.FLASH_MODE_OFF)) { mRestoreFlash = false; return; @@ -2190,14 +2141,8 @@ public class VideoModule implements CameraModule, } @Override - public void onFullScreenChanged(boolean full) { - mUI.onFullScreenChanged(full); - if (ApiHelper.HAS_SURFACE_TEXTURE) { - if (mActivity.mCameraScreenNail != null) { - ((CameraScreenNail) mActivity.mCameraScreenNail).setFullScreen(full); - } - return; - } + public void onSwitchMode(boolean toCamera) { + mUI.onSwitchMode(toCamera); } private final class JpegPictureCallback implements PictureCallback { @@ -2285,26 +2230,13 @@ public class VideoModule implements CameraModule, if (mPaused || mPendingSwitchCameraId != -1) return; mPendingSwitchCameraId = cameraId; - if (ApiHelper.HAS_SURFACE_TEXTURE) { - 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. - ((CameraScreenNail) mActivity.mCameraScreenNail).copyTexture(); - // Disable all camera controls. - mSwitchingCamera = true; - } else { - switchCamera(); - } - } - - @Override - public boolean needsSwitcher() { - return !mIsVideoCaptureIntent; - } + 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 needsPieMenu() { - return true; } @Override diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java index 9fc05769f..c446b9778 100644 --- a/src/com/android/camera/VideoUI.java +++ b/src/com/android/camera/VideoUI.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 The Android Open Source Project + * Copyright (C) 2012 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. @@ -17,13 +17,20 @@ package com.android.camera; import android.graphics.Bitmap; +import android.graphics.Matrix; +import android.graphics.SurfaceTexture; import android.hardware.Camera.Parameters; +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; @@ -33,8 +40,11 @@ import android.widget.TextView; import com.android.camera.CameraPreference.OnPreferenceChangedListener; import com.android.camera.ui.AbstractSettingPopup; +import com.android.camera.ui.CameraControls; +import com.android.camera.ui.CameraRootView; +import com.android.camera.ui.CameraSwitcher; +import com.android.camera.ui.CameraSwitcher.CameraSwitchListener; import com.android.camera.ui.PieRenderer; -import com.android.camera.ui.PreviewSurfaceView; import com.android.camera.ui.RenderOverlay; import com.android.camera.ui.RotateLayout; import com.android.camera.ui.ZoomRenderer; @@ -43,16 +53,16 @@ import com.android.gallery3d.common.ApiHelper; import java.util.List; -public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, +public class VideoUI implements PieRenderer.PieListener, PreviewGestures.SingleTapListener, - PreviewGestures.SwipeListener { + CameraRootView.MyDisplayListener, + SurfaceTextureListener, SurfaceHolder.Callback { private final static String TAG = "CAM_VideoUI"; + private static final int UPDATE_TRANSFORM_MATRIX = 1; // module fields private CameraActivity mActivity; private View mRootView; - private PreviewFrameLayout mPreviewFrameLayout; - private boolean mSurfaceViewReady; - private PreviewSurfaceView mPreviewSurfaceView; + private TextureView mTextureView; // An review image having same size as preview. It is displayed when // recording is stopped in capture intent. private ImageView mReviewImage; @@ -60,40 +70,97 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, 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 VideoMenu mVideoMenu; + private CameraControls mCameraControls; private AbstractSettingPopup mPopup; private ZoomRenderer mZoomRenderer; private PreviewGestures mGestures; - private View mMenu; + private View mMenuButton; private View mBlocker; private OnScreenIndicators mOnScreenIndicators; private RotateLayout mRecordingTimeRect; + private final Object mLock = new Object(); + private SurfaceTexture mSurfaceTexture; private VideoController mController; private int mZoomMax; private List<Integer> mZoomRatios; private View mPreviewThumb; + 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 VideoUI(CameraActivity activity, VideoController controller, View parent) { mActivity = activity; mController = controller; mRootView = parent; mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true); - mPreviewSurfaceView = (PreviewSurfaceView) mRootView - .findViewById(R.id.preview_surface_view); + mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content); + mTextureView.setSurfaceTextureListener(this); + mRootView.addOnLayoutChangeListener(mLayoutListener); + ((CameraRootView) mRootView).setDisplayChangeListener(this); + 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); - mMenu = mActivity.findViewById(R.id.menu); - mMenu.setOnClickListener(new OnClickListener() { + mMenuButton = mActivity.findViewById(R.id.menu); + mMenuButton.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if (mPieRenderer != null) { @@ -101,13 +168,15 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } } }); + + mCameraControls = (CameraControls) mActivity.findViewById(R.id.camera_controls); mOnScreenIndicators = new OnScreenIndicators(mActivity, mActivity.findViewById(R.id.on_screen_indicators)); mOnScreenIndicators.resetToDefault(); if (mController.isVideoCaptureIntent()) { - mActivity.hideSwitcher(); - ViewGroup cameraControls = (ViewGroup) mActivity.findViewById(R.id.camera_controls); - mActivity.getLayoutInflater().inflate(R.layout.review_module_control, cameraControls); + 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. @@ -136,6 +205,85 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } } + 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); + mSwitcher.closePopup(); + } + + public void showUI() { + mCameraControls.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) { @@ -166,10 +314,6 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, mVideoMenu.overrideSettings(keyvalues); } - public View getPreview() { - return mPreviewFrameLayout; - } - public void setOrientationIndicator(int orientation, boolean animation) { if (mGestures != null) { mGestures.setOrientation(orientation); @@ -187,15 +331,19 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } public SurfaceHolder getSurfaceHolder() { - return mPreviewSurfaceView.getHolder(); + return mSurfaceView.getHolder(); } public void hideSurfaceView() { - mPreviewSurfaceView.setVisibility(View.GONE); + mSurfaceView.setVisibility(View.GONE); + mTextureView.setVisibility(View.VISIBLE); + setTransformMatrix(mPreviewWidth, mPreviewHeight); } public void showSurfaceView() { - mPreviewSurfaceView.setVisibility(View.VISIBLE); + mSurfaceView.setVisibility(View.VISIBLE); + mTextureView.setVisibility(View.GONE); + setTransformMatrix(mPreviewWidth, mPreviewHeight); } private void initializeOverlay() { @@ -211,29 +359,16 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } mRenderOverlay.addRenderer(mZoomRenderer); if (mGestures == null) { - mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer, this); + mGestures = new PreviewGestures(mActivity, this, mZoomRenderer, mPieRenderer); + mRenderOverlay.setGestures(mGestures); } mGestures.setRenderOverlay(mRenderOverlay); - mGestures.reset(); - mGestures.addTouchReceiver(mMenu); - mGestures.addUnclickableArea(mBlocker); - if (mController.isVideoCaptureIntent()) { - if (mReviewCancelButton != null) { - mGestures.addTouchReceiver(mReviewCancelButton); - } - if (mReviewDoneButton != null) { - mGestures.addTouchReceiver(mReviewDoneButton); - } - if (mReviewPlayButton != null) { - mGestures.addTouchReceiver(mReviewPlayButton); - } - } mPreviewThumb = mActivity.findViewById(R.id.preview_thumb); mPreviewThumb.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { - mActivity.gotoGallery(); + // TODO: Go to filmstrip view } }); } @@ -243,10 +378,7 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } private void initializeMiscControls() { - mPreviewFrameLayout = (PreviewFrameLayout) mRootView.findViewById(R.id.frame); - mPreviewFrameLayout.setOnLayoutChangeListener(mActivity); mReviewImage = (ImageView) mRootView.findViewById(R.id.review_image); - mShutterButton = mActivity.getShutterButton(); mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); mShutterButton.setOnShutterButtonListener(mController); mShutterButton.setVisibility(View.VISIBLE); @@ -269,7 +401,7 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } public void setAspectRatio(double ratio) { - mPreviewFrameLayout.setAspectRatio(ratio); + // mPreviewFrameLayout.setAspectRatio(ratio); } public void showTimeLapseUI(boolean enable) { @@ -285,7 +417,7 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } public void showPopup(AbstractSettingPopup popup) { - mActivity.hideUI(); + hideUI(); mBlocker.setVisibility(View.INVISIBLE); setShowMenu(false); mPopup = popup; @@ -294,7 +426,6 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, LayoutParams.WRAP_CONTENT); lp.gravity = Gravity.CENTER; ((FrameLayout) mRootView).addView(mPopup, lp); - mGestures.addTouchReceiver(mPopup); } public void dismissPopup(boolean topLevelOnly) { @@ -306,12 +437,11 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, if (mController.isInReviewMode()) return; if (fullScreen) { - mActivity.showUI(); + showUI(); mBlocker.setVisibility(View.VISIBLE); } setShowMenu(fullScreen); if (mPopup != null) { - mGestures.removeTouchReceiver(mPopup); ((FrameLayout) mRootView).removeView(mPopup); mPopup = null; } @@ -345,18 +475,21 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, // PieListener @Override public void onPieOpened(int centerX, int centerY) { + setSwipingEnabled(false); dismissPopup(false, true); - mActivity.cancelActivityTouchHandling(); - mActivity.setSwipingEnabled(false); } @Override public void onPieClosed() { - mActivity.setSwipingEnabled(true); + setSwipingEnabled(true); + } + + public void setSwipingEnabled(boolean enable) { + mActivity.setSwipingEnabled(enable); } public void showPreviewBorder(boolean enable) { - mPreviewFrameLayout.showBorder(enable); + // TODO: mPreviewFrameLayout.showBorder(enable); } // SingleTapListener @@ -366,35 +499,12 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, mController.onSingleTapUp(view, x, y); } - // SurfaceView callback - @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { - Log.v(TAG, "Surface changed. width=" + width + ". height=" + height); - } - - @Override - public void surfaceCreated(SurfaceHolder holder) { - Log.v(TAG, "Surface created"); - mSurfaceViewReady = true; - } - - @Override - public void surfaceDestroyed(SurfaceHolder holder) { - Log.v(TAG, "Surface destroyed"); - mSurfaceViewReady = false; - mController.stopPreview(); - } - - public boolean isSurfaceViewReady() { - return mSurfaceViewReady; - } - public void showRecordingUI(boolean recording, boolean zoomSupported) { - mMenu.setVisibility(recording ? View.GONE : View.VISIBLE); + mMenuButton.setVisibility(recording ? View.GONE : View.VISIBLE); mOnScreenIndicators.setVisibility(recording ? View.GONE : View.VISIBLE); if (recording) { mShutterButton.setImageResource(R.drawable.btn_shutter_video_recording); - mActivity.hideSwitcher(); + hideSwitcher(); mRecordingTimeView.setText(""); mRecordingTimeView.setVisibility(View.VISIBLE); // The camera is not allowed to be accessed in older api levels during @@ -407,7 +517,7 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } } else { mShutterButton.setImageResource(R.drawable.btn_new_shutter_video); - mActivity.showSwitcher(); + showSwitcher(); mRecordingTimeView.setVisibility(View.GONE); if (!ApiHelper.HAS_ZOOM_WHEN_RECORDING && zoomSupported) { // TODO: enable zoom UI here. @@ -425,14 +535,14 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, Util.fadeIn(mReviewDoneButton); Util.fadeIn(mReviewPlayButton); mReviewImage.setVisibility(View.VISIBLE); - mMenu.setVisibility(View.GONE); + mMenuButton.setVisibility(View.GONE); mOnScreenIndicators.setVisibility(View.GONE); } public void hideReviewUI() { mReviewImage.setVisibility(View.GONE); mShutterButton.setEnabled(true); - mMenu.setVisibility(View.VISIBLE); + mMenuButton.setVisibility(View.VISIBLE); mOnScreenIndicators.setVisibility(View.VISIBLE); Util.fadeOut(mReviewDoneButton); Util.fadeOut(mReviewPlayButton); @@ -443,27 +553,28 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, if (mOnScreenIndicators != null) { mOnScreenIndicators.setVisibility(show ? View.VISIBLE : View.GONE); } - if (mMenu != null) { - mMenu.setVisibility(show ? View.VISIBLE : View.GONE); + if (mMenuButton != null) { + mMenuButton.setVisibility(show ? View.VISIBLE : View.GONE); } } - public void onFullScreenChanged(boolean full) { + public void onSwitchMode(boolean toCamera) { + if (toCamera) { + showUI(); + } else { + hideUI(); + } if (mGestures != null) { - mGestures.setEnabled(full); + mGestures.setEnabled(toCamera); } if (mPopup != null) { - dismissPopup(false, full); + dismissPopup(false, toCamera); } 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); + mRenderOverlay.setVisibility(toCamera ? View.VISIBLE : View.GONE); } + setShowMenu(toCamera); } public void initializePopup(PreferenceGroup pref) { @@ -471,7 +582,11 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } public void initializeZoom(Parameters param) { - if (param == null || !param.isZoomSupported()) return; + if (param == null || !param.isZoomSupported()) { + mGestures.setZoomEnabled(false); + return; + } + mGestures.setZoomEnabled(true); mZoomMax = param.getMaxZoom(); mZoomRatios = param.getZoomRatios(); // Currently we use immediate zoom for fast zooming to get better UX and @@ -490,11 +605,8 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, mShutterButton.setPressed(pressed); } - public boolean dispatchTouchEvent(MotionEvent m) { - if (mGestures != null && mRenderOverlay != null) { - return mGestures.dispatchTouch(m); - } - return false; + public View getShutterButton() { + return mShutterButton; } public void setRecordingTime(String text) { @@ -505,6 +617,26 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, mRecordingTimeView.setTextColor(color); } + public boolean isVisible() { + return mTextureView.getVisibility() == View.VISIBLE; + } + + public void onDisplayChanged() { + mCameraControls.checkLayoutFlip(); + mController.updateCameraOrientation(); + } + + /** + * Enable or disable the preview thumbnail for click events. + */ + public void enablePreviewThumb(boolean enabled) { + if (enabled) { + mPreviewThumb.setVisibility(View.VISIBLE); + } else { + mPreviewThumb.setVisibility(View.GONE); + } + } + private class ZoomChangeListener implements ZoomRenderer.OnZoomChangedListener { @Override public void onZoomValueChanged(int index) { @@ -523,23 +655,58 @@ public class VideoUI implements SurfaceHolder.Callback, PieRenderer.PieListener, } } - @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; } - /** - * Enable or disable the preview thumbnail for click events. - */ - public void enablePreviewThumb(boolean enabled) { - if (enabled) { - mGestures.addTouchReceiver(mPreviewThumb); - mPreviewThumb.setVisibility(View.VISIBLE); - } else { - mGestures.removeTouchReceiver(mPreviewThumb); - mPreviewThumb.setVisibility(View.GONE); + // 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 index 609e812fc..0788c1a7a 100644 --- a/src/com/android/camera/data/CameraDataAdapter.java +++ b/src/com/android/camera/data/CameraDataAdapter.java @@ -57,6 +57,7 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter { private int mSuggestedHeight = DEFAULT_DECODE_SIZE; private boolean mCameraPreviewLocked; + private LocalData mLocalDataToDelete; public CameraDataAdapter(Drawable placeHolder) { mPlaceHolder = placeHolder; @@ -86,6 +87,10 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter { @Override public ImageData getImageData(int id) { + return getData(id); + } + + public LocalData getData(int id) { if (mImages == null || id >= mImages.size() || id < 0) { return null; } @@ -133,9 +138,12 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter { return false; } - public void removeData(int dataID) { + public void removeData(Context c, int dataID) { if (dataID >= mImages.size()) return; LocalData d = mImages.remove(dataID); + // Delete previously removed data first. + executeDeletion(c); + mLocalDataToDelete = d; mListener.onDataRemoved(dataID, d); } @@ -177,6 +185,23 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter { } } + public boolean undoDataRemoval() { + if (mLocalDataToDelete == null) return false; + LocalData d = mLocalDataToDelete; + mLocalDataToDelete = null; + insertData(d); + return true; + } + + public boolean executeDeletion(Context c) { + if (mLocalDataToDelete == null) return false; + + DeletionTask task = new DeletionTask(c); + task.execute(mLocalDataToDelete); + mLocalDataToDelete = null; + return true; + } + // Update all the data but keep the camera data if already set. private void replaceData(List<LocalData> list) { boolean changed = (list != mImages); @@ -343,6 +368,26 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter { } } + private class DeletionTask extends AsyncTask<LocalData, Void, Void> { + Context mContext; + + DeletionTask(Context context) { + mContext = context; + } + + @Override + protected Void doInBackground(LocalData... data) { + for (int i = 0; i < data.length; i++) { + if (!data[i].isDataActionSupported(LocalData.ACTION_DELETE)) { + Log.v(TAG, "Deletion is not supported:" + data[i]); + continue; + } + data[i].delete(mContext); + } + return null; + } + } + private class CameraPreviewData implements LocalData { private int width; private int height; @@ -385,7 +430,17 @@ public class CameraDataAdapter implements FilmStripView.DataAdapter { } @Override - public boolean isActionSupported(int action) { + public boolean isUIActionSupported(int action) { + return false; + } + + @Override + public boolean isDataActionSupported(int action) { + return false; + } + + @Override + public boolean delete(Context c) { return false; } diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java index 704e8edc9..0ccc63950 100644 --- a/src/com/android/camera/data/LocalData.java +++ b/src/com/android/camera/data/LocalData.java @@ -16,6 +16,7 @@ package com.android.camera.data; +import android.content.ContentResolver; import android.content.Context; import android.database.Cursor; import android.graphics.Bitmap; @@ -42,19 +43,26 @@ import com.android.camera.Util; import com.android.camera.ui.FilmStripView; import com.android.gallery3d.R; +import java.io.File; 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. */ -public abstract interface LocalData extends FilmStripView.ImageData { - static final String TAG = "LocalData"; +public interface LocalData extends FilmStripView.ImageData { + static final String TAG = "CAM_LocalData"; - abstract View getView(Context c, int width, int height, Drawable placeHolder); - abstract long getDateTaken(); - abstract long getDateModified(); - abstract String getTitle(); + public static final int ACTION_NONE = 0; + public static final int ACTION_PLAY = 1; + public static final int ACTION_DELETE = (1 << 1); + + View getView(Context c, int width, int height, Drawable placeHolder); + long getDateTaken(); + long getDateModified(); + String getTitle(); + boolean isDataActionSupported(int action); + boolean delete(Context c); static class NewestFirstComparator implements Comparator<LocalData> { @@ -83,6 +91,8 @@ public abstract interface LocalData extends FilmStripView.ImageData { } } + // Implementations below. + /* * 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 @@ -128,10 +138,21 @@ public abstract interface LocalData extends FilmStripView.ImageData { } @Override - public boolean isActionSupported(int action) { + public boolean isUIActionSupported(int action) { + return false; + } + + @Override + public boolean isDataActionSupported(int action) { return false; } + @Override + public boolean delete(Context c) { + File f = new File(path); + return f.delete(); + } + protected View fillViewBackground(Context c, View v, int decodeWidth, int decodeHeight, Drawable placeHolder) { v.setBackground(placeHolder); @@ -225,9 +246,11 @@ public abstract interface LocalData extends FilmStripView.ImageData { ImageColumns.HEIGHT, // 8, int }; - private static final int mSupportedActions = + private static final int mSupportedUIActions = FilmStripView.ImageData.ACTION_DEMOTE | FilmStripView.ImageData.ACTION_PROMOTE; + private static final int mSupportedDataActions = + LocalData.ACTION_DELETE; // 32K buffer. private static final byte[] DECODE_TEMP_STORAGE = new byte[32 * 1024]; @@ -284,8 +307,20 @@ public abstract interface LocalData extends FilmStripView.ImageData { } @Override - public boolean isActionSupported(int action) { - return ((action & mSupportedActions) != 0); + public boolean isUIActionSupported(int action) { + return ((action & mSupportedUIActions) == action); + } + + @Override + public boolean isDataActionSupported(int action) { + return ((action & mSupportedDataActions) == action); + } + + @Override + public boolean delete(Context c) { + ContentResolver cr = c.getContentResolver(); + cr.delete(CONTENT_URI, ImageColumns._ID + "=" + id, null); + return super.delete(c); } @Override @@ -355,9 +390,12 @@ public abstract interface LocalData extends FilmStripView.ImageData { static final Uri CONTENT_URI = MediaStore.Video.Media.EXTERNAL_CONTENT_URI; - private static final int mSupportedActions = + private static final int mSupportedUIActions = FilmStripView.ImageData.ACTION_DEMOTE | FilmStripView.ImageData.ACTION_PROMOTE; + private static final int mSupportedDataActions = + LocalData.ACTION_DELETE + | LocalData.ACTION_PLAY; static final String QUERY_ORDER = VideoColumns.DATE_TAKEN + " DESC, " + VideoColumns._ID + " DESC"; @@ -418,8 +456,20 @@ public abstract interface LocalData extends FilmStripView.ImageData { } @Override - public boolean isActionSupported(int action) { - return ((action & mSupportedActions) != 0); + public boolean isUIActionSupported(int action) { + return ((action & mSupportedUIActions) == action); + } + + @Override + public boolean isDataActionSupported(int action) { + return ((action & mSupportedDataActions) == action); + } + + @Override + public boolean delete(Context c) { + ContentResolver cr = c.getContentResolver(); + cr.delete(CONTENT_URI, VideoColumns._ID + "=" + id, null); + return super.delete(c); } @Override @@ -531,7 +581,17 @@ public abstract interface LocalData extends FilmStripView.ImageData { } @Override - public boolean isActionSupported(int action) { + public boolean isUIActionSupported(int action) { + return false; + } + + @Override + public boolean isDataActionSupported(int action) { + return false; + } + + @Override + public boolean delete(Context c) { return false; } diff --git a/src/com/android/camera/ui/CameraControls.java b/src/com/android/camera/ui/CameraControls.java index 7940ae0d9..7fa6890a7 100644 --- a/src/com/android/camera/ui/CameraControls.java +++ b/src/com/android/camera/ui/CameraControls.java @@ -20,8 +20,6 @@ import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; -import android.hardware.display.DisplayManager; -import android.hardware.display.DisplayManager.DisplayListener; import android.util.AttributeSet; import android.view.View; import android.widget.FrameLayout; @@ -40,44 +38,13 @@ public class CameraControls extends RotatableLayout { private View mMenu; private View mIndicators; private View mPreview; - private Object mDisplayListener = null; - private int mLastRotation = 0; public CameraControls(Context context, AttributeSet attrs) { super(context, attrs); - initDisplayListener(); } public CameraControls(Context context) { super(context); - initDisplayListener(); - } - - public void initDisplayListener() { - if (ApiHelper.HAS_DISPLAY_LISTENER) { - mDisplayListener = new DisplayListener() { - - @Override - public void onDisplayAdded(int arg0) {} - - @Override - public void onDisplayChanged(int arg0) { - checkLayoutFlip(); - } - - @Override - public void onDisplayRemoved(int arg0) {} - }; - } - } - - private void checkLayoutFlip() { - int currentRotation = Util.getDisplayRotation((Activity) getContext()); - if ((currentRotation - mLastRotation + 360) % 360 == 180) { - mLastRotation = currentRotation; - flipChildren(); - getParent().requestLayout(); - } } @Override @@ -92,36 +59,7 @@ public class CameraControls extends RotatableLayout { } @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - adjustControlsToRightPosition(); - mLastRotation = Util.getDisplayRotation((Activity) getContext()); - if (ApiHelper.HAS_DISPLAY_LISTENER) { - ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE)) - .registerDisplayListener((DisplayListener) mDisplayListener, null); - } - } - - @Override - public void onWindowVisibilityChanged(int visibility) { - if (visibility == View.VISIBLE) { - // Make sure when coming back from onPause, the layout is rotated correctly - checkLayoutFlip(); - } - } - - @Override - public void onDetachedFromWindow () { - super.onDetachedFromWindow(); - if (ApiHelper.HAS_DISPLAY_LISTENER) { - ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE)) - .unregisterDisplayListener((DisplayListener) mDisplayListener); - } - } - - @Override public void onLayout(boolean changed, int l, int t, int r, int b) { - mLastRotation = Util.getDisplayRotation((Activity) getContext()); int orientation = getResources().getConfiguration().orientation; int size = getResources().getDimensionPixelSize(R.dimen.camera_controls_size); int rotation = getUnifiedRotation(); @@ -168,19 +106,6 @@ public class CameraControls extends RotatableLayout { } } - private int getUnifiedRotation() { - // all the layout code assumes camera device orientation to be portrait - // adjust rotation for landscape - int orientation = getResources().getConfiguration().orientation; - int rotation = Util.getDisplayRotation((Activity) getContext()); - int camOrientation = (rotation % 180 == 0) ? Configuration.ORIENTATION_PORTRAIT - : Configuration.ORIENTATION_LANDSCAPE; - if (camOrientation != orientation) { - return (rotation + 90) % 360; - } - return rotation; - } - private void center(View v, int l, int t, int r, int b, int orientation, int rotation, Rect result) { FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) v.getLayoutParams(); int tw = lp.leftMargin + v.getMeasuredWidth() + lp.rightMargin; @@ -313,16 +238,6 @@ public class CameraControls extends RotatableLayout { v.layout(r - v.getMeasuredWidth() - mr, t + mt, r - mr, t + mt + v.getMeasuredHeight()); } - // In reverse landscape and reverse portrait, camera controls will be laid out - // on the wrong side of the screen. We need to make adjustment to move the controls - // to the USB side - public void adjustControlsToRightPosition() { - int orientation = getUnifiedRotation(); - if (orientation >= 180) { - flipChildren(); - } - } - private void adjustBackground() { int rotation = getUnifiedRotation(); // remove current drawable and reset rotation diff --git a/src/com/android/camera/ui/CameraRootView.java b/src/com/android/camera/ui/CameraRootView.java index 76fea2cfa..adda70697 100644 --- a/src/com/android/camera/ui/CameraRootView.java +++ b/src/com/android/camera/ui/CameraRootView.java @@ -20,28 +20,34 @@ import android.app.Activity; import android.content.Context; import android.content.res.Configuration; import android.graphics.Rect; +import android.hardware.display.DisplayManager; +import android.hardware.display.DisplayManager.DisplayListener; import android.util.AttributeSet; -import android.view.Gravity; import android.view.View; import android.widget.FrameLayout; -import android.widget.RelativeLayout; import com.android.camera.Util; -import com.android.gallery3d.R; +import com.android.gallery3d.common.ApiHelper; -public class CameraRootView extends RelativeLayout { +public class CameraRootView extends FrameLayout { private int mTopMargin = 0; private int mBottomMargin = 0; private int mLeftMargin = 0; private int mRightMargin = 0; - private int mOffset = 0; private Rect mCurrentInsets; + private int mOffset = 0; + private Object mDisplayListener; + private MyDisplayListener mListener; + public interface MyDisplayListener { + public void onDisplayChanged(); + } + public CameraRootView(Context context, AttributeSet attrs) { super(context, attrs); - // Layout the window as if we did not need navigation bar - setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN - | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + initDisplayListener(); + setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION + | View.SYSTEM_UI_FLAG_LAYOUT_STABLE); } @Override @@ -60,6 +66,46 @@ public class CameraRootView extends RelativeLayout { return true; } + public void initDisplayListener() { + if (ApiHelper.HAS_DISPLAY_LISTENER) { + mDisplayListener = new DisplayListener() { + + @Override + public void onDisplayAdded(int arg0) {} + + @Override + public void onDisplayChanged(int arg0) { + mListener.onDisplayChanged(); + } + + @Override + public void onDisplayRemoved(int arg0) {} + }; + } + } + + public void setDisplayChangeListener(MyDisplayListener listener) { + mListener = listener; + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + if (ApiHelper.HAS_DISPLAY_LISTENER) { + ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE)) + .registerDisplayListener((DisplayListener) mDisplayListener, null); + } + } + + @Override + public void onDetachedFromWindow () { + super.onDetachedFromWindow(); + if (ApiHelper.HAS_DISPLAY_LISTENER) { + ((DisplayManager) getContext().getSystemService(Context.DISPLAY_SERVICE)) + .unregisterDisplayListener((DisplayListener) mDisplayListener); + } + } + @Override protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int rotation = Util.getDisplayRotation((Activity) getContext()); @@ -102,12 +148,15 @@ public class CameraRootView extends RelativeLayout { // 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++) { diff --git a/src/com/android/camera/ui/FaceView.java b/src/com/android/camera/ui/FaceView.java index 24150497c..7d66dc079 100644 --- a/src/com/android/camera/ui/FaceView.java +++ b/src/com/android/camera/ui/FaceView.java @@ -31,9 +31,7 @@ import android.util.AttributeSet; import android.util.Log; import android.view.View; -import com.android.camera.CameraActivity; -import com.android.camera.CameraScreenNail; -import com.android.camera.NewPhotoUI; +import com.android.camera.PhotoUI; import com.android.camera.Util; import com.android.gallery3d.R; import com.android.gallery3d.common.ApiHelper; @@ -41,7 +39,7 @@ import com.android.gallery3d.common.ApiHelper; @TargetApi(ApiHelper.VERSION_CODES.ICE_CREAM_SANDWICH) public class FaceView extends View implements FocusIndicator, Rotatable, - NewPhotoUI.SurfaceTextureSizeChangedListener { + PhotoUI.SurfaceTextureSizeChangedListener { private static final String TAG = "CAM FaceView"; private final boolean LOGV = false; // The value for android.hardware.Camera.setDisplayOrientation. @@ -190,16 +188,8 @@ public class FaceView extends View protected void onDraw(Canvas canvas) { if (!mBlocked && (mFaces != null) && (mFaces.length > 0)) { int rw, rh; - if (mUncroppedWidth == 0) { - // TODO: This check is temporary. It needs to be removed after the - // refactoring is fully functioning. - final CameraScreenNail sn = ((CameraActivity) getContext()).getCameraScreenNail(); - rw = sn.getUncroppedRenderWidth(); - rh = sn.getUncroppedRenderHeight(); - } else { - rw = mUncroppedWidth; - rh = mUncroppedHeight; - } + rw = mUncroppedWidth; + rh = mUncroppedHeight; // Prepare the matrix. if (((rh > rw) && ((mDisplayOrientation == 0) || (mDisplayOrientation == 180))) || ((rw > rh) && ((mDisplayOrientation == 90) || (mDisplayOrientation == 270)))) { diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java index fb8fd2e31..709d7c666 100644 --- a/src/com/android/camera/ui/FilmStripView.java +++ b/src/com/android/camera/ui/FilmStripView.java @@ -95,7 +95,7 @@ public class FilmStripView extends ViewGroup { public int getWidth(); public int getHeight(); public int getType(); - public boolean isActionSupported(int action); + public boolean isUIActionSupported(int action); // prepare() should be called first time before using it. public void prepare(); @@ -137,6 +137,7 @@ public class FilmStripView extends ViewGroup { public void onDataPromoted(int dataID); public void onDataDemoted(int dataID); public void onDataFullScreenChange(int dataID, boolean full); + public void onSwitchMode(boolean toCamera); } public interface Controller { @@ -455,7 +456,7 @@ public class FilmStripView extends ViewGroup { if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW && !mController.isScalling() && mScale != MAX_SCALE) { - mController.scaleTo(MAX_SCALE, DURATION_GEOMETRY_ADJUST); + mController.gotoFullScreen(); } } if (curr.getID() == mDataAdapter.getTotalNumber() - 1 @@ -1056,10 +1057,21 @@ public class FilmStripView extends ViewGroup { public void gotoFilmStrip() { unlockPosition(); scaleTo(FILM_STRIP_SCALE, DURATION_GEOMETRY_ADJUST); + if (mListener != null) { + mListener.onSwitchMode(false); + } } @Override public void gotoFullScreen() { + if (mListener != null) { + // TODO: After full size images snapping to fill the screen at the + // end of a scroll/fling is implemented, we should only make + // this call when the view on the center of the screen is + // camera preview + mListener.onSwitchMode(true); + } + if (mScale == 1f) return; scaleTo(1f, DURATION_GEOMETRY_ADJUST); } @@ -1139,11 +1151,11 @@ public class FilmStripView extends ViewGroup { int id = mViewInfo[i].getID(); if (mDataAdapter.getImageData(id) - .isActionSupported(ImageData.ACTION_DEMOTE) + .isUIActionSupported(ImageData.ACTION_DEMOTE) && transY > halfH) { demoteData(i, id); } else if (mDataAdapter.getImageData(id) - .isActionSupported(ImageData.ACTION_PROMOTE) + .isUIActionSupported(ImageData.ACTION_PROMOTE) && transY < -halfH) { promoteData(i, id); } else { @@ -1180,10 +1192,10 @@ public class FilmStripView extends ViewGroup { ImageData data = mDataAdapter.getImageData(mViewInfo[hit].getID()); float transY = mViewInfo[hit].getTranslationY(mScale) - dy / mScale; - if (!data.isActionSupported(ImageData.ACTION_DEMOTE) && transY > 0f) { + if (!data.isUIActionSupported(ImageData.ACTION_DEMOTE) && transY > 0f) { transY = 0f; } - if (!data.isActionSupported(ImageData.ACTION_PROMOTE) && transY < 0f) { + if (!data.isUIActionSupported(ImageData.ACTION_PROMOTE) && transY < 0f) { transY = 0f; } mViewInfo[hit].setTranslationY(transY, mScale); @@ -1229,10 +1241,7 @@ public class FilmStripView extends ViewGroup { @Override public void onScaleEnd() { if (mScaleTrend >= 1f) { - if (mScale != 1f) { - mController.gotoFullScreen(); - } - + mController.gotoFullScreen(); if (getCurrentType() == ImageData.TYPE_CAMERA_PREVIEW) { if (isAnchoredTo(0)) { mController.lockAtCurrentView(); diff --git a/src/com/android/camera/ui/NewCameraRootView.java b/src/com/android/camera/ui/NewCameraRootView.java deleted file mode 100644 index bf8192564..000000000 --- a/src/com/android/camera/ui/NewCameraRootView.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * 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.util.AttributeSet; -import android.view.Gravity; -import android.view.MotionEvent; -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); - } - } - } - - @Override - public boolean dispatchTouchEvent(MotionEvent ev) { - //TODO: This scale check is temporary, should be removed once full screen notification - // is implemented - if (((View) getParent()).getScaleX() == 1.0f) { - return super.dispatchTouchEvent(ev); - } else { - return false; - } - } -} diff --git a/src/com/android/camera/ui/RenderOverlay.java b/src/com/android/camera/ui/RenderOverlay.java index 0bda10a23..d82ce18b6 100644 --- a/src/com/android/camera/ui/RenderOverlay.java +++ b/src/com/android/camera/ui/RenderOverlay.java @@ -23,7 +23,7 @@ import android.view.MotionEvent; import android.view.View; import android.widget.FrameLayout; -import com.android.camera.NewPreviewGestures; +import com.android.camera.PreviewGestures; import java.util.ArrayList; import java.util.List; @@ -44,7 +44,7 @@ public class RenderOverlay extends FrameLayout { private RenderView mRenderView; private List<Renderer> mClients; - private NewPreviewGestures mGestures; + private PreviewGestures mGestures; // reverse list of touch clients private List<Renderer> mTouchClients; private int[] mPosition = new int[2]; @@ -59,7 +59,7 @@ public class RenderOverlay extends FrameLayout { setWillNotDraw(false); } - public void setGestures(NewPreviewGestures gestures) { + public void setGestures(PreviewGestures gestures) { mGestures = gestures; } @@ -89,7 +89,10 @@ public class RenderOverlay extends FrameLayout { @Override public boolean dispatchTouchEvent(MotionEvent m) { - if (mGestures != null) mGestures.dispatchTouch(m); + if (mGestures != null) { + if (!mGestures.isEnabled()) return false; + mGestures.dispatchTouch(m); + } return true; } diff --git a/src/com/android/camera/ui/RotatableLayout.java b/src/com/android/camera/ui/RotatableLayout.java index 8355c8826..965d62a90 100644 --- a/src/com/android/camera/ui/RotatableLayout.java +++ b/src/com/android/camera/ui/RotatableLayout.java @@ -65,9 +65,11 @@ public class RotatableLayout extends FrameLayout { mPrevRotation = Util.getDisplayRotation((Activity) getContext()); // check if there is any rotation before the view is attached to window int currentOrientation = getResources().getConfiguration().orientation; - if (mInitialOrientation == currentOrientation) { + int orientation = getUnifiedRotation(); + if (mInitialOrientation == currentOrientation && orientation < 180) { return; } + if (mInitialOrientation == Configuration.ORIENTATION_LANDSCAPE && currentOrientation == Configuration.ORIENTATION_PORTRAIT) { rotateLayout(true); @@ -75,19 +77,62 @@ public class RotatableLayout extends FrameLayout { && currentOrientation == Configuration.ORIENTATION_LANDSCAPE) { rotateLayout(false); } + // In reverse landscape and reverse portrait, camera controls will be laid out + // on the wrong side of the screen. We need to make adjustment to move the controls + // to the USB side + if (orientation >= 180) { + flipChildren(); + } + } + + protected int getUnifiedRotation() { + // all the layout code assumes camera device orientation to be portrait + // adjust rotation for landscape + int orientation = getResources().getConfiguration().orientation; + int rotation = Util.getDisplayRotation((Activity) getContext()); + int camOrientation = (rotation % 180 == 0) ? Configuration.ORIENTATION_PORTRAIT + : Configuration.ORIENTATION_LANDSCAPE; + if (camOrientation != orientation) { + return (rotation + 90) % 360; + } + return rotation; + } + + public void checkLayoutFlip() { + int currentRotation = Util.getDisplayRotation((Activity) getContext()); + if ((currentRotation - mPrevRotation + 360) % 360 == 180) { + mPrevRotation = currentRotation; + flipChildren(); + getParent().requestLayout(); + } + } + + @Override + public void onWindowVisibilityChanged(int visibility) { + if (visibility == View.VISIBLE) { + // Make sure when coming back from onPause, the layout is rotated correctly + checkLayoutFlip(); + } } @Override public void onConfigurationChanged(Configuration config) { super.onConfigurationChanged(config); int rotation = Util.getDisplayRotation((Activity) getContext()); - if ((rotation - mPrevRotation + 360) % 180 == 0) { + int diff = (rotation - mPrevRotation + 360) % 360; + if ( diff == 0) { + // No rotation + return; + } else if (diff == 180) { + // 180-degree rotation mPrevRotation = rotation; + flipChildren(); return; } + // 90 or 270-degree rotation boolean clockwise = isClockWiseRotation(mPrevRotation, rotation); - rotateLayout(clockwise); mPrevRotation = rotation; + rotateLayout(clockwise); } protected void rotateLayout(boolean clockwise) { @@ -113,7 +158,6 @@ public class RotatableLayout extends FrameLayout { } protected void flipChildren() { - mPrevRotation = Util.getDisplayRotation((Activity) getContext()); int childCount = getChildCount(); for (int i = 0; i < childCount; i++) { View child = getChildAt(i); diff --git a/src/com/android/gallery3d/app/StateManager.java b/src/com/android/gallery3d/app/StateManager.java index c0c84c950..53c3fc228 100644 --- a/src/com/android/gallery3d/app/StateManager.java +++ b/src/com/android/gallery3d/app/StateManager.java @@ -64,14 +64,10 @@ public class StateManager { StateTransitionAnimation.Transition.Incoming); if (mIsResumed) top.onPause(); } - // Ignore the filmstrip used for the root of the camera app - boolean ignoreHit = (mActivity instanceof CameraActivity) - && mStack.isEmpty(); - if (!ignoreHit) { - UsageStatistics.onContentViewChanged( - UsageStatistics.COMPONENT_GALLERY, - klass.getSimpleName()); - } + + UsageStatistics.onContentViewChanged( + UsageStatistics.COMPONENT_GALLERY, + klass.getSimpleName()); state.initialize(mActivity, data); mStack.push(new StateEntry(data, state)); diff --git a/src/com/android/gallery3d/data/LocalImage.java b/src/com/android/gallery3d/data/LocalImage.java index 1ed67ecf4..a7c98af77 100644 --- a/src/com/android/gallery3d/data/LocalImage.java +++ b/src/com/android/gallery3d/data/LocalImage.java @@ -35,9 +35,9 @@ import com.android.gallery3d.app.PanoramaMetadataSupport; import com.android.gallery3d.app.StitchingProgressManager; import com.android.gallery3d.common.ApiHelper; import com.android.gallery3d.common.BitmapUtils; -import com.android.gallery3d.common.Utils; import com.android.gallery3d.exif.ExifInterface; import com.android.gallery3d.exif.ExifTag; +import com.android.gallery3d.filtershow.tools.SaveCopyTask; import com.android.gallery3d.util.GalleryUtils; import com.android.gallery3d.util.ThreadPool.Job; import com.android.gallery3d.util.ThreadPool.JobContext; @@ -46,8 +46,6 @@ import com.android.gallery3d.util.UpdateHelper; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.channels.FileChannel.MapMode; // LocalImage represents an image in the local storage. public class LocalImage extends LocalMediaItem { @@ -271,7 +269,9 @@ public class LocalImage extends LocalMediaItem { public void delete() { GalleryUtils.assertNotInRenderThread(); Uri baseUri = Images.Media.EXTERNAL_CONTENT_URI; - mApplication.getContentResolver().delete(baseUri, "_id=?", + ContentResolver contentResolver = mApplication.getContentResolver(); + SaveCopyTask.deleteAuxFiles(contentResolver, getContentUri()); + contentResolver.delete(baseUri, "_id=?", new String[]{String.valueOf(id)}); } diff --git a/src/com/android/gallery3d/filtershow/FilterShowActivity.java b/src/com/android/gallery3d/filtershow/FilterShowActivity.java index fbe09b999..289a4c37b 100644 --- a/src/com/android/gallery3d/filtershow/FilterShowActivity.java +++ b/src/com/android/gallery3d/filtershow/FilterShowActivity.java @@ -37,6 +37,7 @@ import android.support.v4.app.FragmentActivity; import android.support.v4.app.FragmentTransaction; import android.util.DisplayMetrics; import android.util.TypedValue; +import android.util.Log; import android.view.Display; import android.view.Menu; import android.view.MenuItem; @@ -91,6 +92,7 @@ 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; +import com.android.gallery3d.util.UsageStatistics; import com.android.photos.data.GalleryBitmapPool; import java.io.File; @@ -136,6 +138,8 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL private Uri mOriginalImageUri = null; private ImagePreset mOriginalPreset = null; + private Uri mSelectedImageUri = null; + private CategoryAdapter mCategoryLooksAdapter = null; private CategoryAdapter mCategoryBordersAdapter = null; private CategoryAdapter mCategoryGeometryAdapter = null; @@ -166,6 +170,9 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL extractXMPData(); processIntent(); + UsageStatistics.onContentViewChanged(UsageStatistics.COMPONENT_EDITOR, "Main"); + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + UsageStatistics.CATEGORY_LIFECYCLE, UsageStatistics.LIFECYCLE_START); } public boolean isShowingImageStatePanel() { @@ -305,12 +312,13 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL } mAction = intent.getAction(); - Uri srcUri = intent.getData(); + mSelectedImageUri = intent.getData(); + Uri loadUri = mSelectedImageUri; if (mOriginalImageUri != null) { - srcUri = mOriginalImageUri; + loadUri = mOriginalImageUri; } - if (srcUri != null) { - startLoadBitmap(srcUri); + if (loadUri != null) { + startLoadBitmap(loadUri); } else { pickImage(); } @@ -511,6 +519,10 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL cannotLoadImage(); } + if (null == CachingPipeline.getRenderScriptContext()){ + Log.v(LOGTAG,"RenderScript context destroyed during load"); + return; + } final View loading = findViewById(R.id.loading); loading.setVisibility(View.GONE); final View imageShow = findViewById(R.id.imageShow); @@ -745,6 +757,8 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL mMasterImage.onHistoryItemClick(position); backToMain(); invalidateViews(); + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + UsageStatistics.CATEGORY_BUTTON_PRESS, "Undo"); return true; } case R.id.redoButton: { @@ -752,14 +766,21 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL int position = adapter.redo(); mMasterImage.onHistoryItemClick(position); invalidateViews(); + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + UsageStatistics.CATEGORY_BUTTON_PRESS, "Redo"); return true; } case R.id.resetHistoryButton: { resetHistory(); + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + UsageStatistics.CATEGORY_BUTTON_PRESS, "ResetHistory"); return true; } case R.id.showImageStateButton: { toggleImageStatePanel(); + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + UsageStatistics.CATEGORY_BUTTON_PRESS, + mShowingImageStatePanel ? "ShowPanel" : "HidePanel"); return true; } case android.R.id.home: { @@ -907,11 +928,13 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setMessage(R.string.unsaved).setTitle(R.string.save_before_exit); builder.setPositiveButton(R.string.save_and_exit, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int id) { saveImage(); } }); builder.setNegativeButton(R.string.exit, new DialogInterface.OnClickListener() { + @Override public void onClick(DialogInterface dialog, int id) { done(); } @@ -965,7 +988,7 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL public void saveImage() { if (mImageShow.hasModifications()) { // Get the name of the album, to which the image will be saved - File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mImageLoader.getUri()); + File saveDir = SaveCopyTask.getFinalSaveDirectory(this, mSelectedImageUri); int bucketId = GalleryUtils.getBucketId(saveDir.getPath()); String albumName = LocalAlbum.getLocalizedName(getResources(), bucketId, null); showSavingProgress(albumName); @@ -978,13 +1001,12 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL public void done() { hideSavingProgress(); + if (mLoadBitmapTask != null) { + mLoadBitmapTask.cancel(false); + } finish(); } - static { - System.loadLibrary("jni_filtershow_filters"); - } - private void extractXMPData() { XMresults res = XmpPresets.extractXMPData( getBaseContext(), mMasterImage, getIntent().getData()); @@ -994,4 +1016,14 @@ public class FilterShowActivity extends FragmentActivity implements OnItemClickL mOriginalImageUri = res.originalimage; mOriginalPreset = res.preset; } + + public Uri getSelectedImageUri() { + return mSelectedImageUri; + } + + static { + System.loadLibrary("jni_filtershow_filters"); + } + + } diff --git a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java index 7ddd9bebe..491340e0d 100644 --- a/src/com/android/gallery3d/filtershow/cache/ImageLoader.java +++ b/src/com/android/gallery3d/filtershow/cache/ImageLoader.java @@ -394,7 +394,9 @@ public class ImageLoader { public void saveImage(ImagePreset preset, final FilterShowActivity filterShowActivity, File destination) { - new SaveCopyTask(mContext, mUri, destination, new SaveCopyTask.Callback() { + Uri selectedImageUri = filterShowActivity.getSelectedImageUri(); + new SaveCopyTask(mContext, mUri, selectedImageUri, destination, + new SaveCopyTask.Callback() { @Override public void onComplete(Uri result) { diff --git a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java index 4186b9337..daa599940 100644 --- a/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java +++ b/src/com/android/gallery3d/filtershow/filters/BaseFiltersManager.java @@ -162,9 +162,23 @@ public abstract class BaseFiltersManager implements FiltersManagerInterface { R.string.ffx_x_process }; + // Do not localize. + String[] serializationNames = { + "LUT3D_PUNCH", + "LUT3D_VINTAGE", + "LUT3D_BW", + "LUT3D_BLEACH", + "LUT3D_INSTANT", + "LUT3D_WASHOUT", + "LUT3D_BLUECRUSH", + "LUT3D_WASHOUT", + "LUT3D_XPROCESS" + }; + for (int i = 0; i < drawid.length; i++) { FilterFxRepresentation fx = new FilterFxRepresentation( context.getString(fxNameid[i]), drawid[i], fxNameid[i]); + fx.setSerializationName(serializationNames[i]); representations.add(fx); addRepresentation(fx); } diff --git a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java index 2dbff94bd..abe69d110 100644 --- a/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java +++ b/src/com/android/gallery3d/filtershow/filters/FilterFxRepresentation.java @@ -19,7 +19,6 @@ package com.android.gallery3d.filtershow.filters; import com.android.gallery3d.filtershow.editors.ImageOnlyEditor; public class FilterFxRepresentation extends FilterRepresentation { - 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) @@ -28,7 +27,6 @@ public class FilterFxRepresentation extends FilterRepresentation { public FilterFxRepresentation(String name, int bitmapResource, int nameResource) { super(name); - setSerializationName(SERIALIZATION_NAME + "_" + name); setFilterClass(ImageFilterFx.class); mBitmapResource = bitmapResource; mNameResource = nameResource; @@ -58,6 +56,7 @@ public class FilterFxRepresentation extends FilterRepresentation { if (a instanceof FilterFxRepresentation) { FilterFxRepresentation representation = (FilterFxRepresentation) a; setName(representation.getName()); + setSerializationName(representation.getSerializationName()); setBitmapResource(representation.getBitmapResource()); setNameResource(representation.getNameResource()); } diff --git a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java index 8f490ca5e..4ea944b7b 100644 --- a/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java +++ b/src/com/android/gallery3d/filtershow/imageshow/MasterImage.java @@ -140,7 +140,9 @@ public class MasterImage implements RenderingRequestCaller { } public synchronized void setPreset(ImagePreset preset, boolean addToHistory) { - preset.showFilters(); + if (DEBUG) { + preset.showFilters(); + } mPreset = preset; mPreset.setImageLoader(mLoader); setGeometry(); diff --git a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java index f06e048a2..ebefa015f 100644 --- a/src/com/android/gallery3d/filtershow/presets/ImagePreset.java +++ b/src/com/android/gallery3d/filtershow/presets/ImagePreset.java @@ -471,7 +471,7 @@ public class ImagePreset { bitmap = environment.applyRepresentation(mBorder, bitmap); if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) { UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, - "SaveBorder", mBorder.getName(), 1); + "SaveBorder", mBorder.getSerializationName(), 1); } } return bitmap; @@ -489,6 +489,10 @@ public class ImagePreset { if (to == -1) { to = mFilters.size(); } + if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + "SaveFilters", "Total", to - from + 1); + } for (int i = from; i < to; i++) { FilterRepresentation representation = null; synchronized (mFilters) { @@ -498,7 +502,7 @@ public class ImagePreset { bitmap = environment.applyRepresentation(representation, bitmap); if (environment.getQuality() == FilterEnvironment.QUALITY_FINAL) { UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, - "SaveFilter", representation.getName(), 1); + "SaveFilter", representation.getSerializationName(), 1); } if (environment.needsStop()) { return bitmap; diff --git a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java index 9f7cba30b..9e96e96c3 100644 --- a/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java +++ b/src/com/android/gallery3d/filtershow/tools/SaveCopyTask.java @@ -25,6 +25,7 @@ import android.graphics.BitmapFactory; import android.net.Uri; import android.os.AsyncTask; import android.os.Environment; +import android.provider.MediaStore; import android.provider.MediaStore.Images; import android.provider.MediaStore.Images.ImageColumns; import android.util.Log; @@ -35,10 +36,12 @@ import com.android.gallery3d.filtershow.cache.CachingPipeline; import com.android.gallery3d.filtershow.cache.ImageLoader; import com.android.gallery3d.filtershow.filters.FiltersManager; import com.android.gallery3d.filtershow.presets.ImagePreset; +import com.android.gallery3d.util.UsageStatistics; import com.android.gallery3d.util.XmpUtilHelper; import java.io.File; import java.io.FileNotFoundException; +import java.io.FilenameFilter; import java.io.IOException; import java.io.InputStream; import java.sql.Date; @@ -60,7 +63,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { void onComplete(Uri result); } - private interface ContentResolverQueryCallback { + public interface ContentResolverQueryCallback { void onCursorResult(Cursor cursor); } @@ -69,26 +72,68 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { private static final String PREFIX_PANO = "PANO"; private static final String PREFIX_IMG = "IMG"; private static final String POSTFIX_JPG = ".jpg"; + private static final String AUX_DIR_NAME = ".aux"; + + private final Context mContext; + private final Uri mSourceUri; + private final Callback mCallback; + private final File mDestinationFile; + private final Uri mSelectedImageUri; + + // In order to support the new edit-save behavior such that user won't see + // the edited image together with the original image, we are adding a new + // auxiliary directory for the edited image. Basically, the original image + // will be hidden in that directory after edit and user will see the edited + // image only. + // Note that deletion on the edited image will also cause the deletion of + // the original image under auxiliary directory. + // + // There are several situations we need to consider: + // 1. User edit local image local01.jpg. A local02.jpg will be created in the + // same directory, and original image will be moved to auxiliary directory as + // ./.aux/local02.jpg. + // If user edit the local02.jpg, local03.jpg will be created in the local + // directory and ./.aux/local02.jpg will be renamed to ./.aux/local03.jpg + // + // 2. User edit remote image remote01.jpg from picassa or other server. + // remoteSavedLocal01.jpg will be saved under proper local directory. + // In remoteSavedLocal01.jpg, there will be a reference pointing to the + // remote01.jpg. There will be no local copy of remote01.jpg. + // If user edit remoteSavedLocal01.jpg, then a new remoteSavedLocal02.jpg + // will be generated and still pointing to the remote01.jpg + // + // 3. User delete any local image local.jpg. + // Since the filenames are kept consistent in auxiliary directory, every + // time a local.jpg get deleted, the files in auxiliary directory whose + // names starting with "local." will be deleted. + // This pattern will facilitate the multiple images deletion in the auxiliary + // directory. + // + // TODO: Move the saving into a background service. - private final Context context; - private final Uri sourceUri; - private final Callback callback; - private final String saveFileName; - private final File destinationFile; - - public SaveCopyTask(Context context, Uri sourceUri, File destination, Callback callback) { - this.context = context; - this.sourceUri = sourceUri; - this.callback = callback; - + /** + * @param context + * @param sourceUri The Uri for the original image, which can be the hidden + * image under the auxiliary directory or the same as selectedImageUri. + * @param selectedImageUri The Uri for the image selected by the user. + * In most cases, it is a content Uri for local image or remote image. + * @param destination Destinaton File, if this is null, a new file will be + * created under the same directory as selectedImageUri. + * @param callback Let the caller know the saving has completed. + * @return the newSourceUri + */ + public SaveCopyTask(Context context, Uri sourceUri, Uri selectedImageUri, + File destination, Callback callback) { + mContext = context; + mSourceUri = sourceUri; + mCallback = callback; if (destination == null) { - this.destinationFile = getNewFile(context, sourceUri); + mDestinationFile = getNewFile(context, selectedImageUri); } else { - this.destinationFile = destination; + mDestinationFile = destination; } - saveFileName = PREFIX_IMG + new SimpleDateFormat(TIME_STAMP_NAME).format(new Date( - System.currentTimeMillis())); + mSelectedImageUri = selectedImageUri; } public static File getFinalSaveDirectory(Context context, Uri sourceUri) { @@ -113,12 +158,64 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { return new File(saveDirectory, PREFIX_IMG + filename + POSTFIX_JPG); } + /** + * Remove the files in the auxiliary directory whose names are the same as + * the source image. + * @param contentResolver The application's contentResolver + * @param srcContentUri The content Uri for the source image. + */ + public static void deleteAuxFiles(ContentResolver contentResolver, + Uri srcContentUri) { + final String[] fullPath = new String[1]; + String[] queryProjection = new String[] { ImageColumns.DATA }; + querySourceFromContentResolver(contentResolver, + srcContentUri, queryProjection, + new ContentResolverQueryCallback() { + @Override + public void onCursorResult(Cursor cursor) { + fullPath[0] = cursor.getString(0); + } + } + ); + if (fullPath[0] != null) { + // Construct the auxiliary directory given the source file's path. + // Then select and delete all the files starting with the same name + // under the auxiliary directory. + File currentFile = new File(fullPath[0]); + + String filename = currentFile.getName(); + int firstDotPos = filename.indexOf("."); + final String filenameNoExt = (firstDotPos == -1) ? filename : + filename.substring(0, firstDotPos); + File auxDir = getLocalAuxDirectory(currentFile); + if (auxDir.exists()) { + FilenameFilter filter = new FilenameFilter() { + @Override + public boolean accept(File dir, String name) { + if (name.startsWith(filenameNoExt + ".")) { + return true; + } else { + return false; + } + } + }; + + // Delete all auxiliary files whose name is matching the + // current local image. + File[] auxFiles = auxDir.listFiles(filter); + for (File file : auxFiles) { + file.delete(); + } + } + } + } + public Object getPanoramaXMPData(Uri source, ImagePreset preset) { Object xmp = null; if (preset.isPanoramaSafe()) { InputStream is = null; try { - is = context.getContentResolver().openInputStream(source); + is = mContext.getContentResolver().openInputStream(source); xmp = XmpUtilHelper.extractXMPMeta(is); } catch (FileNotFoundException e) { Log.w(LOGTAG, "Failed to get XMP data from image: ", e); @@ -138,11 +235,11 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { public ExifInterface getExifData(Uri source) { ExifInterface exif = new ExifInterface(); - String mimeType = context.getContentResolver().getType(sourceUri); + String mimeType = mContext.getContentResolver().getType(mSelectedImageUri); if (mimeType.equals(ImageLoader.JPEG_MIME_TYPE)) { InputStream inStream = null; try { - inStream = context.getContentResolver().openInputStream(source); + inStream = mContext.getContentResolver().openInputStream(source); exif.readExif(inStream); } catch (FileNotFoundException e) { Log.w(LOGTAG, "Cannot find file: " + source, e); @@ -174,7 +271,7 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { @Override protected Uri doInBackground(ImagePreset... params) { // TODO: Support larger dimensions for photo saving. - if (params[0] == null || sourceUri == null) { + if (params[0] == null || mSourceUri == null || mSelectedImageUri == null) { return null; } ImagePreset preset = params[0]; @@ -182,19 +279,25 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { Uri uri = null; boolean noBitmap = true; int num_tries = 0; + + // If necessary, move the source file into the auxiliary directory, + // newSourceUri is then pointing to the new location. + // If no file is moved, newSourceUri will be the same as mSourceUri. + Uri newSourceUri = moveSrcToAuxIfNeeded(mSourceUri, mDestinationFile); + // Stopgap fix for low-memory devices. while (noBitmap) { try { // Try to do bitmap operations, downsample if low-memory - Bitmap bitmap = ImageLoader.loadMutableBitmap(context, sourceUri, options); + Bitmap bitmap = ImageLoader.loadMutableBitmap(mContext, newSourceUri, options); if (bitmap == null) { return null; } CachingPipeline pipeline = new CachingPipeline(FiltersManager.getManager(), "Saving"); bitmap = pipeline.renderFinalImage(bitmap, preset); - Object xmp = getPanoramaXMPData(sourceUri, preset); - ExifInterface exif = getExifData(sourceUri); + Object xmp = getPanoramaXMPData(mSelectedImageUri, preset); + ExifInterface exif = getExifData(mSelectedImageUri); // Set tags long time = System.currentTimeMillis(); @@ -207,14 +310,27 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { exif.removeCompressedThumbnail(); // If we succeed in writing the bitmap as a jpeg, return a uri. - if (putExifData(this.destinationFile, exif, bitmap)) { - putPanoramaXMPData(this.destinationFile, xmp); - uri = insertContent(context, sourceUri, this.destinationFile, saveFileName, + if (putExifData(mDestinationFile, exif, bitmap)) { + putPanoramaXMPData(mDestinationFile, xmp); + uri = insertContent(mContext, mSelectedImageUri, mDestinationFile, time); } - XmpPresets.writeFilterXMP(context, sourceUri, this.destinationFile, preset); + + // mDestinationFile will save the newSourceUri info in the XMP. + XmpPresets.writeFilterXMP(mContext, newSourceUri, mDestinationFile, preset); + + // Since we have a new image inserted to media store, we can + // safely remove the old one which is selected by the user. + String scheme = mSelectedImageUri.getScheme(); + if (scheme != null && scheme.equals(ContentResolver.SCHEME_CONTENT)) { + if (mSelectedImageUri.getAuthority().equals(MediaStore.AUTHORITY)) { + mContext.getContentResolver().delete(mSelectedImageUri, null, null); + } + } noBitmap = false; + UsageStatistics.onEvent(UsageStatistics.COMPONENT_EDITOR, + "SaveComplete", null); } catch (java.lang.OutOfMemoryError e) { // Try 5 times before failing for good. if (++num_tries >= 5) { @@ -227,17 +343,73 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { return uri; } + /** + * Move the source file to auxiliary directory if needed and return the Uri + * pointing to this new source file. + * @param srcUri Uri to the source image. + * @param dstFile Providing the destination file info to help to build the + * auxiliary directory and new source file's name. + * @return the newSourceUri pointing to the new source image. + */ + private Uri moveSrcToAuxIfNeeded(Uri srcUri, File dstFile) { + File srcFile = getFileFromUri(mContext, srcUri); + if (srcFile == null) { + Log.d(LOGTAG, "Source file is not a local file, no update."); + return srcUri; + } + + // Get the destination directory and create the auxilliary directory + // if necessary. + File auxDiretory = getLocalAuxDirectory(dstFile); + if (!auxDiretory.exists()) { + auxDiretory.mkdirs(); + } + + // Make sure there is a .nomedia file in the auxiliary directory, such + // that MediaScanner will not report those files under this directory. + File noMedia = new File(auxDiretory, ".nomedia"); + if (!noMedia.exists()) { + try { + noMedia.createNewFile(); + } catch (IOException e) { + Log.e(LOGTAG, "Can't create the nomedia"); + return srcUri; + } + } + // We are using the destination file name such that photos sitting in + // the auxiliary directory are matching the parent directory. + File newSrcFile = new File(auxDiretory, dstFile.getName()); + + if (!newSrcFile.exists()) { + srcFile.renameTo(newSrcFile); + } + + return Uri.fromFile(newSrcFile); + + } + + private static File getLocalAuxDirectory(File dstFile) { + File dstDirectory = dstFile.getParentFile(); + File auxDiretory = new File(dstDirectory + "/" + AUX_DIR_NAME); + return auxDiretory; + } @Override protected void onPostExecute(Uri result) { - if (callback != null) { - callback.onComplete(result); + if (mCallback != null) { + mCallback.onComplete(result); } } private static void querySource(Context context, Uri sourceUri, String[] projection, ContentResolverQueryCallback callback) { ContentResolver contentResolver = context.getContentResolver(); + querySourceFromContentResolver(contentResolver, sourceUri, projection, callback); + } + + private static void querySourceFromContentResolver( + ContentResolver contentResolver, Uri sourceUri, String[] projection, + ContentResolverQueryCallback callback) { Cursor cursor = null; try { cursor = contentResolver.query(sourceUri, projection, null, null, @@ -255,18 +427,51 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { } private static File getSaveDirectory(Context context, Uri sourceUri) { - final File[] dir = new File[1]; - querySource(context, sourceUri, new String[] { - ImageColumns.DATA - }, - new ContentResolverQueryCallback() { + File file = getFileFromUri(context, sourceUri); + if (file != null) { + return file.getParentFile(); + } else { + return null; + } + } - @Override - public void onCursorResult(Cursor cursor) { - dir[0] = new File(cursor.getString(0)).getParentFile(); - } - }); - return dir[0]; + /** + * Construct a File object based on the srcUri. + * @return The file object. Return null if srcUri is invalid or not a local + * file. + */ + private static File getFileFromUri(Context context, Uri srcUri) { + if (srcUri == null) { + Log.e(LOGTAG, "srcUri is null."); + return null; + } + + String scheme = srcUri.getScheme(); + if (scheme == null) { + Log.e(LOGTAG, "scheme is null."); + return null; + } + + final File[] file = new File[1]; + // sourceUri can be a file path or a content Uri, it need to be handled + // differently. + if (scheme.equals(ContentResolver.SCHEME_CONTENT)) { + if (srcUri.getAuthority().equals(MediaStore.AUTHORITY)) { + querySource(context, srcUri, new String[] { + ImageColumns.DATA + }, + new ContentResolverQueryCallback() { + + @Override + public void onCursorResult(Cursor cursor) { + file[0] = new File(cursor.getString(0)); + } + }); + } + } else if (scheme.equals(ContentResolver.SCHEME_FILE)) { + file[0] = new File(srcUri.getPath()); + } + return file[0]; } /** @@ -299,16 +504,16 @@ public class SaveCopyTask extends AsyncTask<ImagePreset, Void, Uri> { /** * Insert the content (saved file) with proper source photo properties. */ - public static Uri insertContent(Context context, Uri sourceUri, File file, String saveFileName, + private static Uri insertContent(Context context, Uri sourceUri, File file, long time) { time /= 1000; final ContentValues values = new ContentValues(); - values.put(Images.Media.TITLE, saveFileName); + values.put(Images.Media.TITLE, file.getName()); values.put(Images.Media.DISPLAY_NAME, file.getName()); values.put(Images.Media.MIME_TYPE, "image/jpeg"); values.put(Images.Media.DATE_TAKEN, time); - values.put(Images.Media.DATE_MODIFIED, time); + values.put(Images.Media.DATE_MODIFIED, System.currentTimeMillis()); values.put(Images.Media.DATE_ADDED, time); values.put(Images.Media.ORIENTATION, 0); values.put(Images.Media.DATA, file.getAbsolutePath()); diff --git a/src/com/android/gallery3d/ingest/ImportTask.java b/src/com/android/gallery3d/ingest/ImportTask.java index d850bb8e1..7d2d641a5 100644 --- a/src/com/android/gallery3d/ingest/ImportTask.java +++ b/src/com/android/gallery3d/ingest/ImportTask.java @@ -65,6 +65,7 @@ public class ImportTask implements Runnable { List<MtpObjectInfo> objectsNotImported = new LinkedList<MtpObjectInfo>(); int visited = 0; int total = mObjectsToImport.size(); + mListener.onImportProgress(visited, total, null); File dest = new File(Environment.getExternalStorageDirectory(), mDestAlbumName); dest.mkdirs(); for (MtpObjectInfo object : mObjectsToImport) { diff --git a/src/com/android/gallery3d/ingest/IngestActivity.java b/src/com/android/gallery3d/ingest/IngestActivity.java index ffc4b50cd..687e9fd44 100644 --- a/src/com/android/gallery3d/ingest/IngestActivity.java +++ b/src/com/android/gallery3d/ingest/IngestActivity.java @@ -75,6 +75,14 @@ public class IngestActivity extends Activity implements private MenuItem mMenuSwitcherItem; private MenuItem mActionMenuSwitcherItem; + // The MTP framework components don't give us fine-grained file copy + // progress updates, so for large photos and videos, we will be stuck + // with a dialog not updating for a long time. To give the user feedback, + // we switch to the animated indeterminate progress bar after the timeout + // specified by INDETERMINATE_SWITCH_TIMEOUT_MS. On the next update from + // the framework, we switch back to the normal progress bar. + private static final int INDETERMINATE_SWITCH_TIMEOUT_MS = 3000; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -437,6 +445,9 @@ public class IngestActivity extends Activity implements mProgressState.current = visitedCount; mProgressState.title = getResources().getString(R.string.ingest_importing); mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_UPDATE); + mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE); + mHandler.sendEmptyMessageDelayed(ItemListHandler.MSG_PROGRESS_INDETERMINATE, + INDETERMINATE_SWITCH_TIMEOUT_MS); } @Override @@ -444,6 +455,7 @@ public class IngestActivity extends Activity implements int numVisited) { // Not guaranteed to be called on the UI thread mHandler.sendEmptyMessage(ItemListHandler.MSG_PROGRESS_HIDE); + mHandler.removeMessages(ItemListHandler.MSG_PROGRESS_INDETERMINATE); // TODO: maybe show an extra dialog listing the ones that failed // importing, if any? } @@ -477,6 +489,11 @@ public class IngestActivity extends Activity implements } } + private void makeProgressDialogIndeterminate() { + ProgressDialog dialog = getProgressDialog(); + dialog.setIndeterminate(true); + } + private void cleanupProgressDialog() { if (mProgressDialog != null) { mProgressDialog.hide(); @@ -490,6 +507,7 @@ public class IngestActivity extends Activity implements public static final int MSG_PROGRESS_HIDE = 1; public static final int MSG_NOTIFY_CHANGED = 2; public static final int MSG_BULK_CHECKED_CHANGE = 3; + public static final int MSG_PROGRESS_INDETERMINATE = 4; WeakReference<IngestActivity> mParentReference; @@ -515,6 +533,9 @@ public class IngestActivity extends Activity implements case MSG_BULK_CHECKED_CHANGE: parent.mPositionMappingCheckBroker.onBulkCheckedChange(); break; + case MSG_PROGRESS_INDETERMINATE: + parent.makeProgressDialogIndeterminate(); + break; default: break; } diff --git a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java index e873dd1ca..d30f94a87 100644 --- a/src/com/android/gallery3d/ingest/MtpDeviceIndex.java +++ b/src/com/android/gallery3d/ingest/MtpDeviceIndex.java @@ -26,8 +26,10 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.Stack; /** @@ -62,6 +64,27 @@ import java.util.Stack; */ public class MtpDeviceIndex { + public static final int FORMAT_MOV = 0x300D; // For some reason this is not in MtpConstants + + public static final Set<Integer> SUPPORTED_IMAGE_FORMATS; + public static final Set<Integer> SUPPORTED_VIDEO_FORMATS; + + static { + SUPPORTED_IMAGE_FORMATS = new HashSet<Integer>(); + SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_JFIF); + SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_EXIF_JPEG); + SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_PNG); + SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_GIF); + SUPPORTED_IMAGE_FORMATS.add(MtpConstants.FORMAT_BMP); + + SUPPORTED_VIDEO_FORMATS = new HashSet<Integer>(); + SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_3GP_CONTAINER); + SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_AVI); + SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MP4_CONTAINER); + SUPPORTED_VIDEO_FORMATS.add(MtpConstants.FORMAT_MPEG); + // TODO: add FORMAT_MOV once Media Scanner supports .mov files + } + @Override public int hashCode() { final int prime = 31; @@ -492,14 +515,12 @@ public class MtpDeviceIndex { for (int objectHandle : mDevice.getObjectHandles(storageId, 0, dirHandle)) { MtpObjectInfo objectInfo = mDevice.getObjectInfo(objectHandle); if (objectInfo == null) throw new IndexingException(); - switch (objectInfo.getFormat()) { - case MtpConstants.FORMAT_JFIF: - case MtpConstants.FORMAT_EXIF_JPEG: - addObject(objectInfo); - break; - case MtpConstants.FORMAT_ASSOCIATION: - pendingDirectories.add(objectHandle); - break; + int format = objectInfo.getFormat(); + if (format == MtpConstants.FORMAT_ASSOCIATION) { + pendingDirectories.add(objectHandle); + } else if (SUPPORTED_IMAGE_FORMATS.contains(format) + || SUPPORTED_VIDEO_FORMATS.contains(format)) { + addObject(objectInfo); } } } diff --git a/src/com/android/gallery3d/ingest/ui/MtpImageView.java b/src/com/android/gallery3d/ingest/ui/MtpImageView.java index 67414c6c4..80c105126 100644 --- a/src/com/android/gallery3d/ingest/ui/MtpImageView.java +++ b/src/com/android/gallery3d/ingest/ui/MtpImageView.java @@ -17,7 +17,9 @@ package com.android.gallery3d.ingest.ui; import android.content.Context; +import android.graphics.Canvas; import android.graphics.Matrix; +import android.graphics.drawable.Drawable; import android.mtp.MtpDevice; import android.mtp.MtpObjectInfo; import android.os.Handler; @@ -27,12 +29,17 @@ import android.os.Message; import android.util.AttributeSet; import android.widget.ImageView; +import com.android.gallery3d.R; +import com.android.gallery3d.ingest.MtpDeviceIndex; import com.android.gallery3d.ingest.data.BitmapWithMetadata; import com.android.gallery3d.ingest.data.MtpBitmapFetch; import java.lang.ref.WeakReference; public class MtpImageView extends ImageView { + // We will use the thumbnail for images larger than this threshold + private static final int MAX_FULLSIZE_PREVIEW_SIZE = 8388608; // 8 megabytes + private int mObjectHandle; private int mGeneration; @@ -42,6 +49,8 @@ public class MtpImageView extends ImageView { private MtpObjectInfo mFetchObjectInfo; private MtpDevice mFetchDevice; private Object mFetchResult; + private Drawable mOverlayIcon; + private boolean mShowOverlayIcon; private static final FetchImageHandler sFetchHandler = FetchImageHandler.createOnNewThread(); private static final ShowImageHandler sFetchCompleteHandler = new ShowImageHandler(); @@ -78,6 +87,11 @@ public class MtpImageView extends ImageView { showPlaceholder(); mGeneration = gen; mObjectHandle = handle; + mShowOverlayIcon = MtpDeviceIndex.SUPPORTED_VIDEO_FORMATS.contains(object.getFormat()); + if (mShowOverlayIcon && mOverlayIcon == null) { + mOverlayIcon = getResources().getDrawable(R.drawable.ic_control_play); + updateOverlayIconBounds(); + } synchronized (mFetchLock) { mFetchObjectInfo = object; mFetchDevice = device; @@ -89,7 +103,12 @@ public class MtpImageView extends ImageView { } protected Object fetchMtpImageDataFromDevice(MtpDevice device, MtpObjectInfo info) { - return MtpBitmapFetch.getFullsize(device, info); + if (info.getCompressedSize() <= MAX_FULLSIZE_PREVIEW_SIZE + && MtpDeviceIndex.SUPPORTED_IMAGE_FORMATS.contains(info.getFormat())) { + return MtpBitmapFetch.getFullsize(device, info); + } else { + return new BitmapWithMetadata(MtpBitmapFetch.getThumbnail(device, info), 0); + } } private float mLastBitmapWidth; @@ -134,12 +153,46 @@ public class MtpImageView extends ImageView { setImageMatrix(mDrawMatrix); } + private static final int OVERLAY_ICON_SIZE_DENOMINATOR = 4; + + private void updateOverlayIconBounds() { + int iheight = mOverlayIcon.getIntrinsicHeight(); + int iwidth = mOverlayIcon.getIntrinsicWidth(); + int vheight = getHeight(); + int vwidth = getWidth(); + float scale_height = ((float) vheight) / (iheight * OVERLAY_ICON_SIZE_DENOMINATOR); + float scale_width = ((float) vwidth) / (iwidth * OVERLAY_ICON_SIZE_DENOMINATOR); + if (scale_height >= 1f && scale_width >= 1f) { + mOverlayIcon.setBounds((vwidth - iwidth) / 2, + (vheight - iheight) / 2, + (vwidth + iwidth) / 2, + (vheight + iheight) / 2); + } else { + float scale = Math.min(scale_height, scale_width); + mOverlayIcon.setBounds((int) (vwidth - scale * iwidth) / 2, + (int) (vheight - scale * iheight) / 2, + (int) (vwidth + scale * iwidth) / 2, + (int) (vheight + scale * iheight) / 2); + } + } + @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); if (changed && getScaleType() == ScaleType.MATRIX) { updateDrawMatrix(); } + if (mShowOverlayIcon && changed && mOverlayIcon != null) { + updateOverlayIconBounds(); + } + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (mShowOverlayIcon && mOverlayIcon != null) { + mOverlayIcon.draw(canvas); + } } protected void onMtpImageDataFetchedFromDevice(Object result) { diff --git a/src/com/android/gallery3d/util/SaveVideoFileUtils.java b/src/com/android/gallery3d/util/SaveVideoFileUtils.java index e2c5f51b9..da0970b1d 100644 --- a/src/com/android/gallery3d/util/SaveVideoFileUtils.java +++ b/src/com/android/gallery3d/util/SaveVideoFileUtils.java @@ -25,17 +25,13 @@ import android.os.Environment; import android.provider.MediaStore.Video; import android.provider.MediaStore.Video.VideoColumns; +import com.android.gallery3d.filtershow.tools.SaveCopyTask.ContentResolverQueryCallback; + import java.io.File; import java.sql.Date; import java.text.SimpleDateFormat; public class SaveVideoFileUtils { - // Copy from SaveCopyTask.java in terms of how to handle the destination - // path and filename : querySource() and getSaveDirectory(). - public interface ContentResolverQueryCallback { - void onCursorResult(Cursor cursor); - } - // This function can decide which folder to save the video file, and generate // the needed information for the video file including filename. public static SaveVideoFileInfo getDstMp4FileInfo(String fileNameFormat, diff --git a/src/com/android/photos/data/BitmapDecoder.java b/src/com/android/photos/data/BitmapDecoder.java index c5101cdfb..0671e73ca 100644 --- a/src/com/android/photos/data/BitmapDecoder.java +++ b/src/com/android/photos/data/BitmapDecoder.java @@ -23,6 +23,7 @@ import android.util.Log; import android.util.Pools.Pool; import android.util.Pools.SynchronizedPool; +import com.android.gallery3d.common.BitmapUtils; import com.android.gallery3d.common.Utils; import java.io.BufferedInputStream; @@ -43,6 +44,7 @@ public class BitmapDecoder { private static final int POOL_SIZE = 4; private static final int TEMP_STORAGE_SIZE_BYTES = 16 * 1024; private static final int HEADER_MAX_SIZE = 128 * 1024; + private static final int NO_SCALING = -1; private static final Pool<BitmapFactory.Options> sOptions = new SynchronizedPool<BitmapFactory.Options>(POOL_SIZE); @@ -95,7 +97,7 @@ public class BitmapDecoder { } }; - private static <T> Bitmap delegateDecode(Decoder<T> decoder, T input) { + private static <T> Bitmap delegateDecode(Decoder<T> decoder, T input, int width, int height) { BitmapFactory.Options options = getOptions(); GalleryBitmapPool pool = GalleryBitmapPool.getInstance(); try { @@ -104,7 +106,12 @@ public class BitmapDecoder { return null; } options.inJustDecodeBounds = false; - Bitmap reuseBitmap = pool.get(options.outWidth, options.outHeight); + Bitmap reuseBitmap = null; + if (width != NO_SCALING && options.outWidth >= width && options.outHeight >= height) { + setScaling(options, width, height); + } else { + reuseBitmap = pool.get(options.outWidth, options.outHeight); + } options.inBitmap = reuseBitmap; Bitmap decodedBitmap = decoder.decode(input, options); if (reuseBitmap != null && decodedBitmap != reuseBitmap) { @@ -130,7 +137,7 @@ public class BitmapDecoder { if (!in.markSupported()) { in = new BufferedInputStream(in); } - return delegateDecode(sStreamDecoder, in); + return delegateDecode(sStreamDecoder, in, NO_SCALING, NO_SCALING); } finally { Utils.closeSilently(in); } @@ -141,27 +148,77 @@ public class BitmapDecoder { } public static Bitmap decodeFile(String path) { - return delegateDecode(sFileDecoder, path); + return delegateDecode(sFileDecoder, path, NO_SCALING, NO_SCALING); } public static Bitmap decodeByteArray(byte[] data) { - return delegateDecode(sByteArrayDecoder, data); + return delegateDecode(sByteArrayDecoder, data, NO_SCALING, NO_SCALING); } public static void put(Bitmap bitmap) { GalleryBitmapPool.getInstance().put(bitmap); } + /** + * Decodes to a specific size. If the dimensions of the image don't match + * width x height, the resulting image will be in the proportions of the + * decoded image, but will be scaled to fill the dimensions. For example, if + * width and height are 10x10 and the image is 200x100, the resulting image + * will be scaled/sampled to 20x10. + */ + public static Bitmap decodeFile(String path, int width, int height) { + return delegateDecode(sFileDecoder, path, width, height); + } + + /** @see #decodeFile(String, int, int) */ + public static Bitmap decodeByteArray(byte[] data, int width, int height) { + return delegateDecode(sByteArrayDecoder, data, width, height); + } + + /** @see #decodeFile(String, int, int) */ + public static Bitmap decode(InputStream in, int width, int height) { + try { + if (!in.markSupported()) { + in = new BufferedInputStream(in); + } + return delegateDecode(sStreamDecoder, in, width, height); + } finally { + Utils.closeSilently(in); + } + } + private static BitmapFactory.Options getOptions() { BitmapFactory.Options opts = sOptions.acquire(); if (opts == null) { opts = new BitmapFactory.Options(); opts.inMutable = true; opts.inPreferredConfig = Config.ARGB_8888; - opts.inSampleSize = 1; opts.inTempStorage = new byte[TEMP_STORAGE_SIZE_BYTES]; } + opts.inSampleSize = 1; + opts.inDensity = 1; + opts.inTargetDensity = 1; return opts; } + + // Sets the options to sample then scale the image so that the image's + // minimum dimension will match side. + private static void setScaling(BitmapFactory.Options options, int width, int height) { + float widthScale = ((float)options.outWidth)/ width; + float heightScale = ((float) options.outHeight)/height; + int side = (widthScale < heightScale) ? width : height; + options.inSampleSize = BitmapUtils.computeSampleSize(options.outWidth, options.outHeight, + side, BitmapUtils.UNCONSTRAINED); + int constraint; + if (options.outWidth < options.outHeight) { + // Width is the constraint. Scale so that width = side. + constraint = options.outWidth; + } else { + // Height is the constraint. Scale so that height = side. + constraint = options.outHeight; + } + options.inDensity = constraint / options.inSampleSize; + options.inTargetDensity = side; + } } diff --git a/src/com/android/photos/drawables/MtpThumbnailDrawable.java b/src/com/android/photos/drawables/MtpThumbnailDrawable.java deleted file mode 100644 index e35e06943..000000000 --- a/src/com/android/photos/drawables/MtpThumbnailDrawable.java +++ /dev/null @@ -1,61 +0,0 @@ -/* - * 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.photos.drawables; - -import android.mtp.MtpDevice; -import android.mtp.MtpObjectInfo; - -import com.android.gallery3d.ingest.MtpDeviceIndex; - -import java.io.InputStream; - -public class MtpThumbnailDrawable extends AutoThumbnailDrawable<MtpObjectInfo> { - public void setImage(MtpObjectInfo data) { - if (data == null) { - setImage(null, 0, 0); - } else { - setImage(data, data.getImagePixWidth(), data.getImagePixHeight()); - } - } - - @Override - protected byte[] getPreferredImageBytes(MtpObjectInfo data) { - if (data == null) { - return null; - } - MtpDevice device = MtpDeviceIndex.getInstance().getDevice(); - if (device != null) { - return device.getThumbnail(data.getObjectHandle()); - } else { - return null; - } - } - - @Override - protected InputStream getFallbackImageStream(MtpObjectInfo data) { - // No fallback - return null; - } - - @Override - protected boolean dataChangedLocked(MtpObjectInfo data) { - // We only fetch the MtpObjectInfo once when creating - // the index so checking the reference is enough - return mData == data; - } - -} |