diff options
Diffstat (limited to 'src/com')
23 files changed, 915 insertions, 181 deletions
diff --git a/src/com/android/camera/AndroidCameraManagerImpl.java b/src/com/android/camera/AndroidCameraManagerImpl.java index b72fbd0da..5b70897e3 100644 --- a/src/com/android/camera/AndroidCameraManagerImpl.java +++ b/src/com/android/camera/AndroidCameraManagerImpl.java @@ -348,8 +348,14 @@ class AndroidCameraManagerImpl implements CameraManager { } mCamera = null; } else if (mCamera == null) { - Log.w(TAG, "Cannot handle message, mCamera is null."); - return; + if (msg.what == OPEN_CAMERA) { + if (msg.obj != null) { + ((CameraOpenErrorCallback) msg.obj).onDeviceOpenFailure(msg.arg1); + } + } else { + Log.w(TAG, "Cannot handle message, mCamera is null."); + } + return; } throw e; } @@ -848,7 +854,10 @@ class AndroidCameraManagerImpl implements CameraManager { private CameraOpenErrorCallbackForward( Handler h, CameraOpenErrorCallback cb) { - mHandler = h; + // Given that we are using the main thread handler, we can create it + // here instead of holding onto the PhotoModule objects. In this + // way, we can avoid memory leak. + mHandler = new Handler(Looper.getMainLooper()); mCallback = cb; } diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java index df2195844..9e5ddb0b2 100644 --- a/src/com/android/camera/CameraActivity.java +++ b/src/com/android/camera/CameraActivity.java @@ -64,6 +64,7 @@ import android.widget.ProgressBar; import android.widget.ShareActionProvider; import com.android.camera.app.AppManagerFactory; +import com.android.camera.app.PlaceholderManager; import com.android.camera.app.PanoramaStitchingManager; import com.android.camera.crop.CropActivity; import com.android.camera.data.CameraDataAdapter; @@ -83,15 +84,20 @@ import com.android.camera.ui.FilmStripView; import com.android.camera.util.ApiHelper; import com.android.camera.util.CameraUtil; import com.android.camera.util.GcamHelper; +import com.android.camera.util.IntentHelper; import com.android.camera.util.PhotoSphereHelper; import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; +import com.android.camera.util.UsageStatistics; import com.android.camera2.R; +import java.io.File; + import static com.android.camera.CameraManager.CameraOpenErrorCallback; public class CameraActivity extends Activity implements ModuleSwitcher.ModuleSwitchListener, - ActionBar.OnMenuVisibilityListener { + ActionBar.OnMenuVisibilityListener, + ShareActionProvider.OnShareTargetSelectedListener { private static final String TAG = "CAM_Activity"; @@ -143,6 +149,7 @@ public class CameraActivity extends Activity private LocalDataAdapter mWrappedDataAdapter; private PanoramaStitchingManager mPanoramaManager; + private PlaceholderManager mPlaceholderManager; private int mCurrentModuleIndex; private CameraModule mCurrentModule; private FrameLayout mAboveFilmstripControlLayout; @@ -226,18 +233,27 @@ public class CameraActivity extends Activity new CameraOpenErrorCallback() { @Override public void onCameraDisabled(int cameraId) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_OPEN_FAIL, "security"); + CameraUtil.showErrorAndFinish(CameraActivity.this, R.string.camera_disabled); } @Override public void onDeviceOpenFailure(int cameraId) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_OPEN_FAIL, "open"); + CameraUtil.showErrorAndFinish(CameraActivity.this, R.string.cannot_connect_camera); } @Override public void onReconnectionFailure(CameraManager mgr) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_OPEN_FAIL, "reconnect"); + CameraUtil.showErrorAndFinish(CameraActivity.this, R.string.cannot_connect_camera); } @@ -290,15 +306,30 @@ public class CameraActivity extends Activity sFirstStartAfterScreenOn = false; } + private String fileNameFromDataID(int dataID) { + final LocalData localData = mDataAdapter.getLocalData(dataID); + + File localFile = new File(localData.getPath()); + return localFile.getName(); + } + private FilmStripView.Listener mFilmStripListener = new FilmStripView.Listener() { @Override public void onDataPromoted(int dataID) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_DELETE, "promoted", 0, + UsageStatistics.hashFileName(fileNameFromDataID(dataID))); + removeData(dataID); } @Override public void onDataDemoted(int dataID) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_DELETE, "demoted", 0, + UsageStatistics.hashFileName(fileNameFromDataID(dataID))); + removeData(dataID); } @@ -340,6 +371,7 @@ public class CameraActivity extends Activity @Override public void onReload() { setPreviewControlsVisibility(true); + CameraActivity.this.setSystemBarsVisibility(false); } @Override @@ -351,6 +383,7 @@ public class CameraActivity extends Activity if(!arePreviewControlsVisible()) { setPreviewControlsVisibility(true); + CameraActivity.this.setSystemBarsVisibility(false); } } @@ -457,6 +490,9 @@ public class CameraActivity extends Activity }; public void gotoGallery() { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_FILMSTRIP, + "thumbnailTap"); + mFilmStripView.getController().goToNextItem(); } @@ -476,13 +512,17 @@ public class CameraActivity extends Activity */ private void setSystemBarsVisibility(boolean visible, boolean hideLater) { mMainHandler.removeMessages(HIDE_ACTION_BAR); - boolean currentlyVisible = mActionBar.isShowing(); - if (visible != currentlyVisible) { - int visibility = DEFAULT_SYSTEM_UI_VISIBILITY | (visible ? View.SYSTEM_UI_FLAG_VISIBLE - : View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN); - mAboveFilmstripControlLayout.setSystemUiVisibility(visibility); + int currentSystemUIVisibility = mAboveFilmstripControlLayout.getSystemUiVisibility(); + int newSystemUIVisibility = DEFAULT_SYSTEM_UI_VISIBILITY | + (visible ? View.SYSTEM_UI_FLAG_VISIBLE : + View.SYSTEM_UI_FLAG_LOW_PROFILE | View.SYSTEM_UI_FLAG_FULLSCREEN); + if (newSystemUIVisibility != currentSystemUIVisibility) { + mAboveFilmstripControlLayout.setSystemUiVisibility(newSystemUIVisibility); + } + boolean currentActionBarVisibility = mActionBar.isShowing(); + if (visible != currentActionBarVisibility) { if (visible) { mActionBar.show(); } else { @@ -593,6 +633,18 @@ public class CameraActivity extends Activity } } + @Override + public boolean onShareTargetSelected(ShareActionProvider shareActionProvider, Intent intent) { + int currentDataId = mFilmStripView.getCurrentId(); + if (currentDataId < 0) { + return false; + } + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, UsageStatistics.ACTION_SHARE, + intent.getComponent().getPackageName(), 0, + UsageStatistics.hashFileName(fileNameFromDataID(currentDataId))); + return true; + } + /** * According to the data type, make the menu items for supported operations * visible. @@ -701,6 +753,41 @@ public class CameraActivity extends Activity item.setVisible(visible); } + private ImageTaskManager.TaskListener mPlaceholderListener = + new ImageTaskManager.TaskListener() { + + @Override + public void onTaskQueued(String filePath, final Uri imageUri) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + notifyNewMedia(imageUri); + int dataID = mDataAdapter.findDataByContentUri(imageUri); + if (dataID != -1) { + LocalData d = mDataAdapter.getLocalData(dataID); + InProgressDataWrapper newData = new InProgressDataWrapper(d, true); + mDataAdapter.updateData(dataID, newData); + } + } + }); + } + + @Override + public void onTaskDone(String filePath, final Uri imageUri) { + mMainHandler.post(new Runnable() { + @Override + public void run() { + mDataAdapter.refresh(getContentResolver(), imageUri); + } + }); + } + + @Override + public void onTaskProgress(String filePath, Uri imageUri, int progress) { + // Do nothing + } + }; + private ImageTaskManager.TaskListener mStitchingListener = new ImageTaskManager.TaskListener() { @Override @@ -774,6 +861,8 @@ public class CameraActivity extends Activity mDataAdapter.addNewPhoto(cr, uri); } else if (mimeType.startsWith("application/stitching-preview")) { mDataAdapter.addNewPhoto(cr, uri); + } else if (mimeType.startsWith(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) { + mDataAdapter.addNewPhoto(cr, uri); } else { android.util.Log.w(TAG, "Unknown new media with MIME type:" + mimeType + ", uri:" + uri); @@ -826,6 +915,9 @@ public class CameraActivity extends Activity mPanoramaShareActionProvider.setShareIntent(mPanoramaShareIntent); } + mStandardShareActionProvider.setOnShareTargetSelectedListener(this); + mPanoramaShareActionProvider.setOnShareTargetSelectedListener(this); + return super.onCreateOptionsMenu(menu); } @@ -842,18 +934,22 @@ public class CameraActivity extends Activity case android.R.id.home: // ActionBar's Up/Home button was clicked try { - if (!CameraUtil.launchGallery(CameraActivity.this)) { - mFilmStripView.getController().goToFirstItem(); - } + startActivity(IntentHelper.getGalleryIntent(this)); return true; } catch (ActivityNotFoundException e) { - Log.w(TAG, "No activity found to handle APP_GALLERY category!"); + Log.w(TAG, "Failed to launch gallery activity, closing"); finish(); } case R.id.action_delete: + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_DELETE, null, 0, + UsageStatistics.hashFileName(fileNameFromDataID(currentDataId))); removeData(currentDataId); return true; case R.id.action_edit: + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_EDIT, null, 0, + UsageStatistics.hashFileName(fileNameFromDataID(currentDataId))); launchEditor(localData); return true; case R.id.action_trim: { @@ -874,6 +970,9 @@ public class CameraActivity extends Activity localData.rotate90Degrees(this, mDataAdapter, currentDataId, true); return true; case R.id.action_crop: { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_CROP, null, 0, + UsageStatistics.hashFileName(fileNameFromDataID(currentDataId))); Intent intent = new Intent(CropActivity.CROP_ACTION); intent.setClass(this, CropActivity.class); intent.setDataAndType(localData.getContentUri(), localData.getMimeType()) @@ -979,7 +1078,10 @@ public class CameraActivity extends Activity this.setSystemBarsVisibility(false); mPanoramaManager = AppManagerFactory.getInstance(this) .getPanoramaStitchingManager(); + mPlaceholderManager = AppManagerFactory.getInstance(this) + .getGcamProcessingManager(); mPanoramaManager.addTaskListener(mStitchingListener); + mPlaceholderManager.addTaskListener(mPlaceholderListener); LayoutInflater inflater = getLayoutInflater(); View rootLayout = inflater.inflate(R.layout.camera, null, false); mCameraModuleRootView = rootLayout.findViewById(R.id.camera_app_root); @@ -1008,8 +1110,14 @@ public class CameraActivity extends Activity moduleIndex = ModuleSwitcher.VIDEO_MODULE_INDEX; } else if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(getIntent().getAction()) || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(getIntent() - .getAction()) - || MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) + .getAction())) { + moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + if (prefs.getInt(CameraSettings.KEY_STARTUP_MODULE_INDEX, -1) + == ModuleSwitcher.GCAM_MODULE_INDEX && GcamHelper.hasGcamCapture()) { + moduleIndex = ModuleSwitcher.GCAM_MODULE_INDEX; + } + } else if (MediaStore.ACTION_IMAGE_CAPTURE.equals(getIntent().getAction()) || MediaStore.ACTION_IMAGE_CAPTURE_SECURE.equals(getIntent().getAction())) { moduleIndex = ModuleSwitcher.PHOTO_MODULE_INDEX; } else { @@ -1041,7 +1149,13 @@ public class CameraActivity extends Activity v.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { - CameraUtil.launchGallery(CameraActivity.this); + try { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_GALLERY, null); + startActivity(IntentHelper.getGalleryIntent(CameraActivity.this)); + } catch (ActivityNotFoundException e) { + Log.w(TAG, "Failed to launch gallery activity, closing"); + } finish(); } }); @@ -1138,6 +1252,10 @@ public class CameraActivity extends Activity setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR); mAutoRotateScreen = true; } + + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_FOREGROUNDED, this.getClass().getSimpleName()); + mOrientationListener.enable(); mCurrentModule.onResumeBeforeSuper(); super.onResume(); @@ -1364,8 +1482,12 @@ public class CameraActivity extends Activity Intent intent = new Intent(Intent.ACTION_EDIT) .setDataAndType(data.getContentUri(), data.getMimeType()) .setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); - startActivityForResult(Intent.createChooser(intent, null), - REQ_CODE_DONT_SWITCH_TO_PREVIEW); + try { + startActivityForResult(intent, REQ_CODE_DONT_SWITCH_TO_PREVIEW); + } catch (ActivityNotFoundException e) { + startActivityForResult(Intent.createChooser(intent, null), + REQ_CODE_DONT_SWITCH_TO_PREVIEW); + } mIsEditActivityInProgress = true; } } @@ -1404,7 +1526,10 @@ public class CameraActivity extends Activity } hideUndoDeletionBar(false); mDataAdapter.executeDeletion(CameraActivity.this); - updateActionBarMenu(mFilmStripView.getCurrentId()); + + int currentId = mFilmStripView.getCurrentId(); + updateActionBarMenu(currentId); + mFilmStripListener.onCurrentDataCentered(currentId); } public void showUndoDeletionBar() { @@ -1485,6 +1610,7 @@ public class CameraActivity extends Activity @Override public void onShowSwitcherPopup() { + mCurrentModule.onShowSwitcherPopup(); } /** diff --git a/src/com/android/camera/ComboPreferences.java b/src/com/android/camera/ComboPreferences.java index 142f6984c..42cf62423 100644 --- a/src/com/android/camera/ComboPreferences.java +++ b/src/com/android/camera/ComboPreferences.java @@ -330,6 +330,5 @@ public class ComboPreferences implements listener.onSharedPreferenceChanged(this, key); } BackupManager.dataChanged(mPackageName); - UsageStatistics.onEvent("CameraSettingsChange", null, key); } } diff --git a/src/com/android/camera/FocusOverlayManager.java b/src/com/android/camera/FocusOverlayManager.java index 37d632745..9558944c0 100644 --- a/src/com/android/camera/FocusOverlayManager.java +++ b/src/com/android/camera/FocusOverlayManager.java @@ -29,6 +29,7 @@ import android.os.Message; import android.util.Log; import com.android.camera.util.CameraUtil; +import com.android.camera.util.UsageStatistics; import java.util.ArrayList; import java.util.List; @@ -76,8 +77,6 @@ public class FocusOverlayManager { private boolean mAeAwbLock; private Matrix mMatrix; - private int mPreviewWidth; // The width of the preview frame layout. - private int mPreviewHeight; // The height of the preview frame layout. private boolean mMirror; // true if the camera is front-facing. private int mDisplayOrientation; private List<Object> mFocusArea; // focus area in driver format @@ -94,6 +93,7 @@ public class FocusOverlayManager { private boolean mZslEnabled = false; //QCom Parameter to disable focus for ZSL private FocusUI mUI; + private final Rect mPreviewRect = new Rect(0, 0, 0, 0); public interface FocusUI { public boolean hasFaces(); @@ -160,13 +160,25 @@ public class FocusOverlayManager { } public void setPreviewSize(int previewWidth, int previewHeight) { - if (mPreviewWidth != previewWidth || mPreviewHeight != previewHeight) { - mPreviewWidth = previewWidth; - mPreviewHeight = previewHeight; + if (mPreviewRect.width() != previewWidth || mPreviewRect.height() != previewHeight) { + setPreviewRect(new Rect(0, 0, previewWidth, previewHeight)); + } + } + + /** This setter should be the only way to mutate mPreviewRect. */ + public void setPreviewRect(Rect previewRect) { + if (!mPreviewRect.equals(previewRect)) { + mPreviewRect.set(previewRect); setMatrix(); } } + /** Returns a copy of mPreviewRect so that outside class cannot modify preview + * rect except deliberately doing so through the setter. */ + public Rect getPreviewRect() { + return new Rect(mPreviewRect); + } + public void setMirror(boolean mirror) { mMirror = mirror; setMatrix(); @@ -178,10 +190,9 @@ public class FocusOverlayManager { } private void setMatrix() { - if (mPreviewWidth != 0 && mPreviewHeight != 0) { + if (mPreviewRect.width() != 0 && mPreviewRect.height() != 0) { Matrix matrix = new Matrix(); - CameraUtil.prepareMatrix(matrix, mMirror, mDisplayOrientation, - mPreviewWidth, mPreviewHeight); + CameraUtil.prepareMatrix(matrix, mMirror, mDisplayOrientation, getPreviewRect()); // In face detection, the matrix converts the driver coordinates to UI // coordinates. In tap focus, the inverted matrix converts the UI // coordinates to driver coordinates. @@ -348,12 +359,15 @@ public class FocusOverlayManager { public void onSingleTapUp(int x, int y) { if (!mInitialized || mState == STATE_FOCUSING_SNAP_ON_FINISH) return; + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_TOUCH_FOCUS, x + "," + y); + // Let users be able to cancel previous touch focus. if ((!mFocusDefault) && (mState == STATE_FOCUSING || mState == STATE_SUCCESS || mState == STATE_FAIL)) { cancelAutoFocus(); } - if (mPreviewWidth == 0 || mPreviewHeight == 0) return; + if (mPreviewRect.width() == 0 || mPreviewRect.height() == 0) return; mFocusDefault = false; // Initialize mFocusArea. if (mFocusAreaSupported) { @@ -512,7 +526,7 @@ public class FocusOverlayManager { mMeteringArea = null; if (mFocusAreaSupported) { - initializeFocusAreas(mPreviewWidth / 2, mPreviewHeight / 2); + initializeFocusAreas(mPreviewRect.centerX(), mPreviewRect.centerY()); } // Reset metering area when no specific region is selected. if (mMeteringAreaSupported) { @@ -523,8 +537,10 @@ public class FocusOverlayManager { private void calculateTapArea(int x, int y, float areaMultiple, Rect rect) { int areaSize = (int) (getAreaSize() * areaMultiple); - int left = CameraUtil.clamp(x - areaSize / 2, 0, mPreviewWidth - areaSize); - int top = CameraUtil.clamp(y - areaSize / 2, 0, mPreviewHeight - areaSize); + int left = CameraUtil.clamp(x - areaSize / 2, mPreviewRect.left, + mPreviewRect.right - areaSize); + int top = CameraUtil.clamp(y - areaSize / 2, mPreviewRect.top, + mPreviewRect.bottom - areaSize); RectF rectF = new RectF(left, top, left + areaSize, top + areaSize); mMatrix.mapRect(rectF); @@ -534,7 +550,7 @@ public class FocusOverlayManager { private int getAreaSize() { // Recommended focus area size from the manufacture is 1/8 of the image // width (i.e. longer edge of the image) - return Math.max(mPreviewWidth, mPreviewHeight) / 8; + return Math.max(mPreviewRect.width(), mPreviewRect.height()) / 8; } /* package */ int getFocusState() { diff --git a/src/com/android/camera/ListPreference.java b/src/com/android/camera/ListPreference.java index 40f9bfe74..2a33fb098 100644 --- a/src/com/android/camera/ListPreference.java +++ b/src/com/android/camera/ListPreference.java @@ -27,6 +27,7 @@ import android.util.Log; import android.util.TypedValue; import com.android.camera.util.CameraUtil; +import com.android.camera.util.UsageStatistics; import com.android.camera2.R; /** @@ -161,6 +162,7 @@ public class ListPreference extends CameraPreference { SharedPreferences.Editor editor = getSharedPreferences().edit(); editor.putString(mKey, value); editor.apply(); + UsageStatistics.onEvent("CameraSettingsChange", value, mKey); } @Override diff --git a/src/com/android/camera/PhotoController.java b/src/com/android/camera/PhotoController.java index f6898a34f..c1c3a8562 100644 --- a/src/com/android/camera/PhotoController.java +++ b/src/com/android/camera/PhotoController.java @@ -16,6 +16,7 @@ package com.android.camera; +import android.graphics.Rect; import android.view.View; import com.android.camera.ShutterButton.OnShutterButtonListener; @@ -58,6 +59,8 @@ public interface PhotoController extends OnShutterButtonListener { public void onScreenSizeChanged(int width, int height); + public void onPreviewRectChanged(Rect previewRect); + public void updateCameraOrientation(); public void enableRecordingLocation(boolean enable); diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java index 176885ce4..4444e4d38 100644 --- a/src/com/android/camera/PhotoModule.java +++ b/src/com/android/camera/PhotoModule.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.SharedPreferences.Editor; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.hardware.Camera.CameraInfo; import android.hardware.Camera.Parameters; @@ -140,9 +141,9 @@ public class PhotoModule 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 + // This is the delay before we execute onResume tasks when coming + // from the lock screen, to allow time for onPause to execute. + private static final int ON_RESUME_TASKS_DELAY_MSEC = 20; private static final String DEBUG_IMAGE_PREFIX = "DEBUG_"; @@ -297,6 +298,7 @@ public class PhotoModule private String mCurrTouchAfAec = Parameters.TOUCH_AF_AEC_ON; private final Handler mHandler = new MainHandler(); + private PreferenceGroup mPreferenceGroup; private boolean mQuickCapture; @@ -343,6 +345,10 @@ public class PhotoModule * application */ private class MainHandler extends Handler { + public MainHandler() { + super(Looper.getMainLooper()); + } + @Override public void handleMessage(Message msg) { switch (msg.what) { @@ -576,6 +582,7 @@ public class PhotoModule mCameraDevice = CameraUtil.openCamera( mActivity, mCameraId, mHandler, mActivity.getCameraOpenErrorCallback()); + if (mCameraDevice == null) { Log.e(TAG, "Failed to open camera:" + mCameraId + ", aborting."); return; @@ -624,6 +631,11 @@ public class PhotoModule if (mFocusManager != null) mFocusManager.setPreviewSize(width, height); } + @Override + public void onPreviewRectChanged(Rect previewRect) { + if (mFocusManager != null) mFocusManager.setPreviewRect(previewRect); + } + private void resetExposureCompensation() { String value = mPreferences.getString(CameraSettings.KEY_EXPOSURE, CameraSettings.EXPOSURE_DEFAULT_VALUE); @@ -1216,6 +1228,7 @@ public class PhotoModule // is full then ignore. if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS || mCameraState == SWITCHING_CAMERA + || mActivity.getMediaSaveService() == null || mActivity.getMediaSaveService().isQueueFull()) { return false; } @@ -1295,7 +1308,9 @@ public class PhotoModule mFaceDetectionStarted = false; } UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, - UsageStatistics.ACTION_CAPTURE_DONE, "Photo"); + UsageStatistics.ACTION_CAPTURE_DONE, "Photo", 0, + UsageStatistics.hashFileName(mNamedImages.mQueue.lastElement().title + ".jpg"), + mParameters.flatten()); return true; } @@ -1616,14 +1631,10 @@ public class PhotoModule mPaused = false; } - /** - * Opens the camera device. - * - * @return Whether the camera was opened successfully. - */ private boolean prepareCamera() { // We need to check whether the activity is paused before long // operations to ensure that onPause() can be done ASAP. + Log.v(TAG, "Open camera device."); mCameraDevice = CameraUtil.openCamera( mActivity, mCameraId, mHandler, mActivity.getCameraOpenErrorCallback()); @@ -1644,9 +1655,30 @@ public class PhotoModule return true; } - @Override public void onResumeAfterSuper() { + // Add delay on resume from lock screen only, in order to to speed up + // the onResume --> onPause --> onResume cycle from lock screen. + // Don't do always because letting go of thread can cause delay. + String action = mActivity.getIntent().getAction(); + if (MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA.equals(action) + || MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action)) { + Log.v(TAG, "On resume, from lock screen."); + // Note: onPauseAfterSuper() will delete this runnable, so we will + // at most have 1 copy queued up. + mHandler.postDelayed(new Runnable() { + public void run() { + onResumeTasks(); + } + }, ON_RESUME_TASKS_DELAY_MSEC); + } else { + Log.v(TAG, "On resume."); + onResumeTasks(); + } + } + + private void onResumeTasks() { + Log.v(TAG, "Executing onResumeTasks."); if (mOpenCameraFail || mCameraDisabled) return; mJpegPictureCallbackTime = 0; @@ -1702,20 +1734,17 @@ public class PhotoModule @Override public void onPauseAfterSuper() { - // 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() - && CameraActivity.isFirstStartAfterScreenOn()) { - CameraActivity.resetFirstStartAfterScreenOn(); - CameraHolder.instance().keep(KEEP_CAMERA_TIMEOUT); - } + Log.v(TAG, "On pause."); + mUI.showPreviewCover(); + // Reset the focus first. Camera CTS does not guarantee that // cancelAutoFocus is allowed after preview stops. if (mCameraDevice != null && mCameraState != PREVIEW_STOPPED) { mCameraDevice.cancelAutoFocus(); } + // If the camera has not been opened asynchronously yet, + // and startPreview hasn't been called, then this is a no-op. + // (e.g. onResume -> onPause -> onResume). stopPreview(); mNamedImages = null; @@ -1931,11 +1960,19 @@ public class PhotoModule } private void closeCamera() { + Log.v(TAG, "Close camera device."); if (mCameraDevice != null) { mCameraDevice.setZoomChangeListener(null); mCameraDevice.setFaceDetectionCallback(null, null); mCameraDevice.setErrorCallback(null); - CameraHolder.instance().release(); + + if (mActivity.isSecureCamera() && !CameraActivity.isFirstStartAfterScreenOn()) { + // Blocks until camera is actually released. + CameraHolder.instance().strongRelease(); + } else { + CameraHolder.instance().release(); + } + mFaceDetectionStarted = false; mCameraDevice = null; setCameraState(PREVIEW_STOPPED); @@ -1963,26 +2000,31 @@ public class PhotoModule startPreview(); } - // This can only be called by UI Thread. + /** This can run on a background thread, post any view updates to MainHandler. */ private void startPreview() { - if (mPaused) { + if (mPaused || mCameraDevice == null) { return; } + + // Any decisions we make based on the surface texture state + // need to be protected. SurfaceTexture st = mUI.getSurfaceTexture(); if (st == null) { Log.w(TAG, "startPreview: surfaceTexture is not ready."); return; } + if (!mCameraPreviewParamsReady) { Log.w(TAG, "startPreview: parameters for preview is not ready."); return; } mCameraDevice.setErrorCallback(mErrorCallback); - - // ICS camera frameworks has a bug. Face detection state is not cleared + // ICS camera frameworks has a bug. Face detection state is not cleared 1589 // after taking a picture. Stop the preview to work around it. The bug // was fixed in JB. - if (mCameraState != PREVIEW_STOPPED) stopPreview(); + if (mCameraState != PREVIEW_STOPPED) { + stopPreview(); + } setDisplayOrientation(); @@ -2436,6 +2478,11 @@ public class PhotoModule Log.v(TAG, "Preview Size changed. Restart Preview"); mRestartPreview = true; } + + if(optimalSize.width != 0 && optimalSize.height != 0) { + mUI.updatePreviewAspectRatio((float) optimalSize.width + / (float) optimalSize.height); + } Log.v(TAG, "Preview size is " + optimalSize.width + "x" + optimalSize.height); // Since changing scene mode may change supported values, set scene mode diff --git a/src/com/android/camera/PhotoUI.java b/src/com/android/camera/PhotoUI.java index 66db1d08b..fb5853da2 100644 --- a/src/com/android/camera/PhotoUI.java +++ b/src/com/android/camera/PhotoUI.java @@ -23,6 +23,7 @@ import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.Color; import android.graphics.Matrix; +import android.graphics.RectF; import android.graphics.SurfaceTexture; import android.graphics.drawable.ColorDrawable; import android.hardware.Camera; @@ -121,6 +122,8 @@ public class PhotoUI implements PieListener, private boolean mOrientationResize; private boolean mPrevOrientationResize; + private View mPreviewCover; + private final Object mSurfaceTextureLock = new Object(); public interface SurfaceTextureSizeChangedListener { public void onSurfaceTextureSizeChanged(int uncroppedWidth, int uncroppedHeight); @@ -162,11 +165,11 @@ public class PhotoUI implements PieListener, Bitmap bitmap = CameraUtil.downSample(mData, DOWN_SAMPLE_FACTOR); if ((mOrientation != 0 || mMirror) && (bitmap != null)) { Matrix m = new Matrix(); - m.preRotate(mOrientation); if (mMirror) { // Flip horizontally m.setScale(-1f, 1f); } + m.preRotate(mOrientation); return Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), m, false); } @@ -205,6 +208,7 @@ public class PhotoUI implements PieListener, (ViewGroup) mRootView, true); mRenderOverlay = (RenderOverlay) mRootView.findViewById(R.id.render_overlay); mFlashOverlay = mRootView.findViewById(R.id.flash_overlay); + mPreviewCover = mRootView.findViewById(R.id.preview_cover); // display the view mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content); mTextureView.setSurfaceTextureListener(this); @@ -254,6 +258,24 @@ public class PhotoUI implements PieListener, mSurfaceTextureSizeListener = listener; } + public void updatePreviewAspectRatio(float aspectRatio) { + if (aspectRatio <= 0) { + Log.e(TAG, "Invalid aspect ratio: " + aspectRatio); + return; + } + if (aspectRatio < 1f) { + aspectRatio = 1f / aspectRatio; + } + + if (mAspectRatio != aspectRatio) { + mAspectRatio = aspectRatio; + // Update transform matrix with the new aspect ratio. + if (mPreviewWidth != 0 && mPreviewHeight != 0) { + setTransformMatrix(mPreviewWidth, mPreviewHeight); + } + } + } + private void setTransformMatrix(int width, int height) { mMatrix = mTextureView.getTransform(mMatrix); float scaleX = 1f, scaleY = 1f; @@ -293,17 +315,28 @@ public class PhotoUI implements PieListener, scaleY = scaledTextureHeight / height; mMatrix.setScale(scaleX, scaleY, (float) width / 2, (float) height / 2); mTextureView.setTransform(mMatrix); + + // Calculate the new preview rectangle. + RectF previewRect = new RectF(0, 0, width, height); + mMatrix.mapRect(previewRect); + mController.onPreviewRectChanged(CameraUtil.rectFToRect(previewRect)); + } + + protected Object getSurfaceTextureLock() { + return mSurfaceTextureLock; } @Override public void onSurfaceTextureAvailable(SurfaceTexture surface, int width, int height) { - Log.v(TAG, "SurfaceTexture ready."); - mSurfaceTexture = surface; - mController.onPreviewUIReady(); - // Workaround for b/11168275, see b/10981460 for more details - if (mPreviewWidth != 0 && mPreviewHeight != 0) { - // Re-apply transform matrix for new surface texture - setTransformMatrix(mPreviewWidth, mPreviewHeight); + synchronized (mSurfaceTextureLock) { + Log.v(TAG, "SurfaceTexture ready."); + mSurfaceTexture = surface; + mController.onPreviewUIReady(); + // Workaround for b/11168275, see b/10981460 for more details + if (mPreviewWidth != 0 && mPreviewHeight != 0) { + // Re-apply transform matrix for new surface texture + setTransformMatrix(mPreviewWidth, mPreviewHeight); + } } } @@ -314,15 +347,20 @@ public class PhotoUI implements PieListener, @Override public boolean onSurfaceTextureDestroyed(SurfaceTexture surface) { - mSurfaceTexture = null; - mController.onPreviewUIDestroyed(); - Log.w(TAG, "SurfaceTexture destroyed"); - return true; + synchronized (mSurfaceTextureLock) { + mSurfaceTexture = null; + mController.onPreviewUIDestroyed(); + Log.w(TAG, "SurfaceTexture destroyed"); + return true; + } } @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { - // Do nothing. + // Make sure preview cover is hidden if preview data is available. + if (mPreviewCover.getVisibility() != View.GONE) { + mPreviewCover.setVisibility(View.GONE); + } } public View getRootView() { @@ -752,6 +790,8 @@ public class PhotoUI implements PieListener, if (mFaceView != null) { mFaceView.setBlockDraw(true); } + // Close module selection menu when pie menu is opened. + mSwitcher.closePopup(); } @Override @@ -802,6 +842,10 @@ public class PhotoUI implements PieListener, mNotSelectableToast.show(); } + public void showPreviewCover() { + mPreviewCover.setVisibility(View.VISIBLE); + } + public void onPause() { cancelCountDown(); diff --git a/src/com/android/camera/Storage.java b/src/com/android/camera/Storage.java index 49435e4c0..29dad8aa4 100644 --- a/src/com/android/camera/Storage.java +++ b/src/com/android/camera/Storage.java @@ -44,6 +44,7 @@ public class Storage { public static final String DIRECTORY = DCIM + "/Camera"; public static final String RAW_DIRECTORY = DCIM + "/Camera/raw"; + public static final String JPEG_POSTFIX = ".jpg"; // Match the code in MediaProvider.computeBucketValues(). public static final String BUCKET_ID = @@ -63,6 +64,24 @@ public class Storage { } } + public static void writeFile(String path, byte[] jpeg, ExifInterface exif, + String mimeType) { + if (exif != null && (mimeType == null || + mimeType.equalsIgnoreCase("jpeg"))) { + try { + exif.writeExif(jpeg, path); + } catch (Exception e) { + Log.e(TAG, "Failed to write data", e); + } + } else if (jpeg != null) { + if (!(mimeType.equalsIgnoreCase("jpeg") || mimeType == null)) { + File dir = new File(RAW_DIRECTORY); + dir.mkdirs(); + } + writeFile(path, jpeg); + } + } + public static void writeFile(String path, byte[] data) { FileOutputStream out = null; try { @@ -74,48 +93,30 @@ public class Storage { try { out.close(); } catch (Exception e) { + Log.e(TAG, "Failed to close file after write", e); } } } - // Save the image and add it to media store. - public static Uri addImage(ContentResolver resolver, String title, - long date, Location location, int orientation, ExifInterface exif, - byte[] jpeg, int width, int height, String pictureFormat) { - int jpegLength = 0; - - if (jpeg != null) { - jpegLength = jpeg.length; - } + // Save the image with a given mimeType and add it the MediaStore. + public static Uri addImage(ContentResolver resolver, String title, long date, + Location location, int orientation, ExifInterface exif, byte[] jpeg, int width, + int height, String mimeType) { - // Save the image. - String path = generateFilepath(title, pictureFormat); - if (exif != null && (pictureFormat == null || - pictureFormat.equalsIgnoreCase("jpeg"))) { - try { - exif.writeExif(jpeg, path); - } catch (Exception e) { - Log.e(TAG, "Failed to write data", e); - } - } else if (jpeg != null) { - if (!(pictureFormat.equalsIgnoreCase("jpeg") || pictureFormat == null)) { - File dir = new File(RAW_DIRECTORY); - dir.mkdirs(); - } - writeFile(path, jpeg); - } + String path = generateFilepath(title, mimeType); + writeFile(path, jpeg, exif, mimeType); return addImage(resolver, title, date, location, orientation, - jpegLength, path, width, height, pictureFormat); + jpeg.length, path, width, height, mimeType); } - // Add the image to media store. - public static Uri addImage(ContentResolver resolver, String title, + // Get a ContentValues object for the given photo data + public static ContentValues getContentValuesForData(String title, long date, Location location, int orientation, int jpegLength, - String path, int width, int height, String pictureFormat) { + String path, int width, int height, String mimeType) { // Insert into MediaStore. ContentValues values = new ContentValues(9); values.put(ImageColumns.TITLE, title); - if (pictureFormat.equalsIgnoreCase("jpeg") || pictureFormat == null) { + if (mimeType.equalsIgnoreCase("jpeg") || mimeType == null) { values.put(ImageColumns.DISPLAY_NAME, title + ".jpg"); } else { values.put(ImageColumns.DISPLAY_NAME, title + ".raw"); @@ -133,19 +134,54 @@ public class Storage { values.put(ImageColumns.LATITUDE, location.getLatitude()); values.put(ImageColumns.LONGITUDE, location.getLongitude()); } + return values; + } - Uri uri = null; - try { - uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values); - } catch (Throwable th) { - // This can happen when the external volume is already mounted, but - // MediaScanner has not notify MediaProvider to add that volume. - // The picture is still safe and MediaScanner will find it and - // insert it into MediaProvider. The only problem is that the user - // cannot click the thumbnail to review the picture. - Log.e(TAG, "Failed to write MediaStore" + th); + // Add the image to media store. + public static Uri addImage(ContentResolver resolver, String title, + long date, Location location, int orientation, int jpegLength, + String path, int width, int height, String mimeType) { + // Insert into MediaStore. + ContentValues values = + getContentValuesForData(title, date, location, orientation, jpegLength, path, + width, height, mimeType); + + return insertImage(resolver, values); + } + + // Overwrites the file and updates the MediaStore, or inserts the image if + // one does not already exist. + public static void updateImage(Uri imageUri, ContentResolver resolver, String title, long date, + Location location, int orientation, ExifInterface exif, byte[] jpeg, int width, + int height, String mimeType) { + String path = generateFilepath(title, mimeType); + writeFile(path, jpeg, exif, mimeType); + updateImage(imageUri, resolver, title, date, location, orientation, jpeg.length, path, + width, height, mimeType); + } + + // Updates the image values in MediaStore, or inserts the image if one does + // not already exist. + public static void updateImage(Uri imageUri, ContentResolver resolver, String title, + long date, Location location, int orientation, int jpegLength, + String path, int width, int height, String mimeType) { + + ContentValues values = + getContentValuesForData(title, date, location, orientation, jpegLength, path, + width, height, mimeType); + + // Update the MediaStore + int rowsModified = resolver.update(imageUri, values, null, null); + + if (rowsModified == 0) { + // If no prior row existed, insert a new one. + Log.w(TAG, "updateImage called with no prior image at uri: " + imageUri); + insertImage(resolver, values); + } else if (rowsModified != 1) { + // This should never happen + throw new IllegalStateException("Bad number of rows (" + rowsModified + + ") updated for uri: " + imageUri); } - return uri; } public static void deleteImage(ContentResolver resolver, Uri uri) { @@ -199,4 +235,19 @@ public class Storage { Log.e(TAG, "Failed to create " + nnnAAAAA.getPath()); } } + + private static Uri insertImage(ContentResolver resolver, ContentValues values) { + Uri uri = null; + try { + uri = resolver.insert(Images.Media.EXTERNAL_CONTENT_URI, values); + } catch (Throwable th) { + // This can happen when the external volume is already mounted, but + // MediaScanner has not notify MediaProvider to add that volume. + // The picture is still safe and MediaScanner will find it and + // insert it into MediaProvider. The only problem is that the user + // cannot click the thumbnail to review the picture. + Log.e(TAG, "Failed to write MediaStore" + th); + } + return uri; + } } diff --git a/src/com/android/camera/SurfaceTextureRenderer.java b/src/com/android/camera/SurfaceTextureRenderer.java index 66f7aa219..331504393 100644 --- a/src/com/android/camera/SurfaceTextureRenderer.java +++ b/src/com/android/camera/SurfaceTextureRenderer.java @@ -51,8 +51,10 @@ public class SurfaceTextureRenderer { @Override public void run() { synchronized (mRenderLock) { - mFrameDrawer.onDrawFrame(mGl); - mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); + if (mEglDisplay != null && mEglSurface != null) { + mFrameDrawer.onDrawFrame(mGl); + mEgl.eglSwapBuffers(mEglDisplay, mEglSurface); + } mRenderLock.notifyAll(); } } diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java index 057332b09..f8f416786 100644 --- a/src/com/android/camera/VideoModule.java +++ b/src/com/android/camera/VideoModule.java @@ -965,6 +965,7 @@ public class VideoModule implements CameraModule, public void onPauseBeforeSuper() { mPaused = true; + mUI.showPreviewCover(); if (mMediaRecorderRecording) { // Camera will be released in onStopVideoRecording. onStopVideoRecording(); diff --git a/src/com/android/camera/VideoUI.java b/src/com/android/camera/VideoUI.java index 7547bdd87..bb270b7f8 100644 --- a/src/com/android/camera/VideoUI.java +++ b/src/com/android/camera/VideoUI.java @@ -96,6 +96,7 @@ public class VideoUI implements PieRenderer.PieListener, private boolean mOrientationResize; private boolean mPrevOrientationResize; + private View mPreviewCover; private SurfaceView mSurfaceView = null; private int mPreviewWidth = 0; private int mPreviewHeight = 0; @@ -141,6 +142,10 @@ public class VideoUI implements PieRenderer.PieListener, } }; + public void showPreviewCover() { + mPreviewCover.setVisibility(View.VISIBLE); + } + private class SettingsPopup extends PopupWindow { public SettingsPopup(View popup) { super(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT); @@ -175,6 +180,7 @@ public class VideoUI implements PieRenderer.PieListener, mController = controller; mRootView = parent; mActivity.getLayoutInflater().inflate(R.layout.video_module, (ViewGroup) mRootView, true); + mPreviewCover = mRootView.findViewById(R.id.preview_cover); mTextureView = (TextureView) mRootView.findViewById(R.id.preview_content); mTextureView.setSurfaceTextureListener(this); mTextureView.addOnLayoutChangeListener(mLayoutListener); @@ -592,6 +598,8 @@ public class VideoUI implements PieRenderer.PieListener, @Override public void onPieOpened(int centerX, int centerY) { setSwipingEnabled(false); + // Close module selection menu when pie menu is opened. + mSwitcher.closePopup(); } @Override @@ -781,6 +789,10 @@ public class VideoUI implements PieRenderer.PieListener, @Override public void onSurfaceTextureUpdated(SurfaceTexture surface) { + // Make sure preview cover is hidden if preview data is available. + if (mPreviewCover.getVisibility() != View.GONE) { + mPreviewCover.setVisibility(View.GONE); + } } // SurfaceHolder callbacks diff --git a/src/com/android/camera/WideAnglePanoramaModule.java b/src/com/android/camera/WideAnglePanoramaModule.java index c4080df4c..85f983ea8 100644 --- a/src/com/android/camera/WideAnglePanoramaModule.java +++ b/src/com/android/camera/WideAnglePanoramaModule.java @@ -46,6 +46,7 @@ import android.view.WindowManager; import com.android.camera.PhotoModule; import com.android.camera.CameraManager.CameraProxy; import com.android.camera.app.OrientationManager; +import com.android.camera.data.LocalData; import com.android.camera.exif.ExifInterface; import com.android.camera.util.CameraUtil; import com.android.camera.util.UsageStatistics; @@ -745,12 +746,12 @@ public class WideAnglePanoramaModule } private void resetToPreviewIfPossible() { + reset(); if (!mMosaicFrameProcessorInitialized || mUI.getSurfaceTexture() == null || !mMosaicPreviewConfigured) { return; } - reset(); if (!mPaused) { startCameraPreview(); } @@ -767,6 +768,10 @@ public class WideAnglePanoramaModule String filepath = Storage.generateFilepath(filename, PhotoModule.PIXEL_FORMAT_JPEG); + UsageStatistics.onEvent(UsageStatistics.COMPONENT_PANORAMA, + UsageStatistics.ACTION_CAPTURE_DONE, null, 0, + UsageStatistics.hashFileName(filename + ".jpg")); + Location loc = mLocationManager.getCurrentLocation(); ExifInterface exif = new ExifInterface(); try { @@ -783,9 +788,8 @@ public class WideAnglePanoramaModule Storage.writeFile(filepath, jpegData); } int jpegLength = (int) (new File(filepath).length()); - return Storage.addImage(mContentResolver, filename, mTimeTaken, - loc, orientation, jpegLength, filepath, width, height, - PhotoModule.PIXEL_FORMAT_JPEG); + return Storage.addImage(mContentResolver, filename, mTimeTaken, loc, orientation, + jpegLength, filepath, width, height, LocalData.MIME_TYPE_JPEG); } return null; } @@ -837,7 +841,7 @@ public class WideAnglePanoramaModule stopCapture(true); reset(); } - + mUI.showPreviewCover(); releaseCamera(); synchronized (mRendererLock) { mCameraTexture = null; diff --git a/src/com/android/camera/WideAnglePanoramaUI.java b/src/com/android/camera/WideAnglePanoramaUI.java index 02ce5516f..2cf27576d 100644 --- a/src/com/android/camera/WideAnglePanoramaUI.java +++ b/src/com/android/camera/WideAnglePanoramaUI.java @@ -89,6 +89,7 @@ public class WideAnglePanoramaUI implements private int mIndicatorColorFast; private int mReviewBackground; private SurfaceTexture mSurfaceTexture; + private View mPreviewCover; /** Constructor. */ public WideAnglePanoramaUI( @@ -243,6 +244,10 @@ public class WideAnglePanoramaUI implements @Override public void onSurfaceTextureUpdated(SurfaceTexture surfaceTexture) { + // Make sure preview cover is hidden if preview data is available. + if (mPreviewCover.getVisibility() != View.GONE) { + mPreviewCover.setVisibility(View.GONE); + } } private void hideDirectionIndicators() { @@ -349,6 +354,7 @@ public class WideAnglePanoramaUI implements mReviewBackground = appRes.getColor(R.color.review_background); mIndicatorColorFast = appRes.getColor(R.color.pano_progress_indication_fast); + mPreviewCover = mRootView.findViewById(R.id.preview_cover); mPreviewLayout = mRootView.findViewById(R.id.pano_preview_layout); mReviewControl = (ViewGroup) mRootView.findViewById(R.id.pano_review_control); mReviewLayout = mRootView.findViewById(R.id.pano_review_layout); @@ -461,6 +467,10 @@ public class WideAnglePanoramaUI implements ((CameraRootView) mRootView).removeDisplayChangeListener(); } + public void showPreviewCover() { + mPreviewCover.setVisibility(View.VISIBLE); + } + private class DialogHelper { private ProgressDialog mProgressDialog; private AlertDialog mAlertDialog; diff --git a/src/com/android/camera/app/AppManagerFactory.java b/src/com/android/camera/app/AppManagerFactory.java index 9c047aa55..43d2a00cd 100644 --- a/src/com/android/camera/app/AppManagerFactory.java +++ b/src/com/android/camera/app/AppManagerFactory.java @@ -16,9 +16,9 @@ package com.android.camera.app; -import android.app.Application; import android.content.Context; + /** * A singleton class which provides application level utility * classes. @@ -35,13 +35,19 @@ public class AppManagerFactory { } private PanoramaStitchingManager mPanoramaStitchingManager; + private PlaceholderManager mGcamProcessingManager; /** No public constructor. */ private AppManagerFactory(Context ctx) { mPanoramaStitchingManager = new PanoramaStitchingManager(ctx); + mGcamProcessingManager = new PlaceholderManager(ctx); } public PanoramaStitchingManager getPanoramaStitchingManager() { return mPanoramaStitchingManager; } + + public PlaceholderManager getGcamProcessingManager() { + return mGcamProcessingManager; + } } diff --git a/src/com/android/camera/app/PlaceholderManager.java b/src/com/android/camera/app/PlaceholderManager.java new file mode 100644 index 000000000..326f0be80 --- /dev/null +++ b/src/com/android/camera/app/PlaceholderManager.java @@ -0,0 +1,185 @@ +/* + * 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.app; + +import android.content.Context; +import android.graphics.BitmapFactory; +import android.location.Location; +import android.net.Uri; + +import com.android.camera.ImageTaskManager; +import com.android.camera.Storage; +import com.android.camera.exif.ExifInterface; +import com.android.camera.util.CameraUtil; + +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.Iterator; + +public class PlaceholderManager implements ImageTaskManager { + private static final String TAG = "PlaceholderManager"; + + public static final String PLACEHOLDER_MIME_TYPE = "application/placeholder-image"; + private final Context mContext; + + final private ArrayList<WeakReference<TaskListener>> mListenerRefs; + + public static class Session { + String outputTitle; + Uri outputUri; + long time; + + Session(String title, Uri uri, long timestamp) { + outputTitle = title; + outputUri = uri; + time = timestamp; + } + } + + public PlaceholderManager(Context context) { + mContext = context; + mListenerRefs = new ArrayList<WeakReference<TaskListener>>(); + } + + @Override + public void addTaskListener(TaskListener l) { + synchronized (mListenerRefs) { + if (findTaskListener(l) == -1) { + mListenerRefs.add(new WeakReference<TaskListener>(l)); + } + } + } + + @Override + public void removeTaskListener(TaskListener l) { + synchronized (mListenerRefs) { + int i = findTaskListener(l); + if (i != -1) { + mListenerRefs.remove(i); + } + } + } + + @Override + public int getTaskProgress(Uri uri) { + return 0; + } + + private int findTaskListener(TaskListener listener) { + int index = -1; + for (int i = 0; i < mListenerRefs.size(); i++) { + TaskListener l = mListenerRefs.get(i).get(); + if (l != null && l == listener) { + index = i; + break; + } + } + return index; + } + + private Iterable<TaskListener> getListeners() { + return new Iterable<TaskListener>() { + @Override + public Iterator<TaskListener> iterator() { + return new ListenerIterator(); + } + }; + } + + private class ListenerIterator implements Iterator<TaskListener> { + private int mIndex = 0; + private TaskListener mNext = null; + + @Override + public boolean hasNext() { + while (mNext == null && mIndex < mListenerRefs.size()) { + mNext = mListenerRefs.get(mIndex).get(); + if (mNext == null) { + mListenerRefs.remove(mIndex); + } + } + return mNext != null; + } + + @Override + public TaskListener next() { + hasNext(); // Populates mNext + mIndex++; + TaskListener next = mNext; + mNext = null; + return next; + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + } + + public Session insertPlaceholder(String title, byte[] placeholder, long timestamp) { + if (title == null || placeholder == null) { + throw new IllegalArgumentException("Null argument passed to insertPlaceholder"); + } + + // Decode bounds + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inJustDecodeBounds = true; + BitmapFactory.decodeByteArray(placeholder, 0, placeholder.length, options); + int width = options.outWidth; + int height = options.outHeight; + + if (width <= 0 || height <= 0) { + throw new IllegalArgumentException("Image had bad height/width"); + } + + Uri uri = + Storage.addImage(mContext.getContentResolver(), title, timestamp, null, 0, null, + placeholder, width, height, PLACEHOLDER_MIME_TYPE); + + if (uri == null) { + return null; + } + + String filePath = uri.getPath(); + synchronized (mListenerRefs) { + for (TaskListener l : getListeners()) { + l.onTaskQueued(filePath, uri); + } + } + + return new Session(title, uri, timestamp); + } + + public void replacePlaceholder(Session session, Location location, int orientation, + ExifInterface exif, byte[] jpeg, int width, int height, String mimeType) { + + Storage.updateImage(session.outputUri, mContext.getContentResolver(), session.outputTitle, + session.time, location, orientation, exif, jpeg, width, height, mimeType); + + synchronized (mListenerRefs) { + for (TaskListener l : getListeners()) { + l.onTaskDone(session.outputUri.getPath(), session.outputUri); + } + } + CameraUtil.broadcastNewPicture(mContext, session.outputUri); + } + + public void removePlaceholder(Session session) { + Storage.deleteImage(mContext.getContentResolver(), session.outputUri); + } + +} diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java index f59b2099c..99bde4181 100644 --- a/src/com/android/camera/data/CameraDataAdapter.java +++ b/src/com/android/camera/data/CameraDataAdapter.java @@ -28,6 +28,7 @@ import android.util.Log; import android.view.View; import com.android.camera.Storage; +import com.android.camera.app.PlaceholderManager; import com.android.camera.ui.FilmStripView.ImageData; import java.util.ArrayList; @@ -285,7 +286,11 @@ public class CameraDataAdapter implements LocalDataAdapter { while (true) { LocalData data = LocalMediaData.PhotoData.buildFromCursor(c); if (data != null) { - l.add(data); + if (data.getMimeType().equals(PlaceholderManager.PLACEHOLDER_MIME_TYPE)) { + l.add(new InProgressDataWrapper(data, true)); + } else { + l.add(data); + } } else { Log.e(TAG, "Error loading data:" + c.getString(LocalMediaData.PhotoData.COL_DATA)); diff --git a/src/com/android/camera/data/InProgressDataWrapper.java b/src/com/android/camera/data/InProgressDataWrapper.java index 7de617bae..61e87b722 100644 --- a/src/com/android/camera/data/InProgressDataWrapper.java +++ b/src/com/android/camera/data/InProgressDataWrapper.java @@ -22,8 +22,10 @@ import android.content.Context; import android.graphics.drawable.Drawable; import android.net.Uri; import android.view.View; +import android.widget.FrameLayout; import com.android.camera.util.PhotoSphereHelper; +import com.android.camera2.R; /** * A wrapper class for in-progress data. Data that's still being processed @@ -34,16 +36,34 @@ import com.android.camera.util.PhotoSphereHelper; public class InProgressDataWrapper implements LocalData { final LocalData mLocalData; + private boolean mHasProgressBar; public InProgressDataWrapper(LocalData wrappedData) { mLocalData = wrappedData; } + public InProgressDataWrapper(LocalData wrappedData, boolean hasProgressBar) { + this(wrappedData); + mHasProgressBar = hasProgressBar; + } + @Override public View getView( Activity a, int width, int height, Drawable placeHolder, LocalDataAdapter adapter) { - return mLocalData.getView(a, width, height, placeHolder, adapter); + View v = mLocalData.getView(a, width, height, placeHolder, adapter); + + if (mHasProgressBar) { + // Return a framelayout with the progressbar and imageview. + FrameLayout frame = new FrameLayout(a); + frame.setLayoutParams(new FrameLayout.LayoutParams(width, height)); + frame.addView(v); + a.getLayoutInflater() + .inflate(R.layout.placeholder_progressbar, frame); + return frame; + } + + return v; } @Override diff --git a/src/com/android/camera/ui/CameraRootView.java b/src/com/android/camera/ui/CameraRootView.java index 505549c80..daaefc027 100644 --- a/src/com/android/camera/ui/CameraRootView.java +++ b/src/com/android/camera/ui/CameraRootView.java @@ -37,7 +37,7 @@ public class CameraRootView extends FrameLayout { private int mBottomMargin = 0; private int mLeftMargin = 0; private int mRightMargin = 0; - private Rect mCurrentInsets; + private final Rect mCurrentInsets = new Rect(0, 0, 0, 0); private int mOffset = 0; private Object mDisplayListener; private MyDisplayListener mListener; @@ -53,19 +53,24 @@ public class CameraRootView extends FrameLayout { @Override protected boolean fitSystemWindows(Rect 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 (mCurrentInsets.equals(insets)) { + // Local copy of the insets is up to date. No need to do anything. + return false; } - if (insets.bottom > 0) { - mOffset = insets.bottom; - } else if (insets.right > 0) { - mOffset = insets.right; + if (mOffset == 0) { + if (insets.bottom > 0) { + mOffset = insets.bottom; + } else if (insets.right > 0) { + mOffset = insets.right; + } } - return true; + mCurrentInsets.set(insets); + // Make sure onMeasure will be called to adapt to the new insets. + requestLayout(); + return false; } public void initDisplayListener() { diff --git a/src/com/android/camera/ui/FilmStripView.java b/src/com/android/camera/ui/FilmStripView.java index 9945952ee..89da0184e 100644 --- a/src/com/android/camera/ui/FilmStripView.java +++ b/src/com/android/camera/ui/FilmStripView.java @@ -43,6 +43,7 @@ import com.android.camera.ui.FilmStripView.ImageData.PanoramaSupportCallback; import com.android.camera.ui.FilmstripBottomControls.BottomControlsListener; import com.android.camera.util.CameraUtil; import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper; +import com.android.camera.util.UsageStatistics; import com.android.camera2.R; import java.util.Arrays; @@ -1453,6 +1454,13 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { mController.setSurroundingViewsVisible(true); } + private void hideZoomView() { + if (mController.isZoomStarted()) { + mController.cancelLoadingZoomedImage(); + mZoomView.setVisibility(GONE); + } + } + // Keeps the view in the view hierarchy if it's camera preview. // Remove from the hierarchy otherwise. private void checkForRemoval(ImageData data, View v) { @@ -1785,6 +1793,8 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { && deltaX < mSlop * (-1)) { // intercept left swipe if (Math.abs(deltaX) >= Math.abs(deltaY) * 2) { + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_FILMSTRIP, null); return true; } } @@ -1908,10 +1918,19 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { mController.stopScrolling(true); mController.stopScale(); mDataIdOnUserScrolling = 0; - // Remove all views from the mViewItem buffer, except the camera view. + // Reload has a side effect that after this call, it will show the + // camera preview. So we want to know whether it starts from the camera + // preview to decide whether we need to call onDataFocusChanged. + boolean stayInPreview = false; + if (mListener != null && mViewItem[mCurrentItem] != null) { - mListener.onDataFocusChanged(mViewItem[mCurrentItem].getId(), false); + stayInPreview = mViewItem[mCurrentItem].getId() == 0; + if (!stayInPreview) { + mListener.onDataFocusChanged(mViewItem[mCurrentItem].getId(), false); + } } + + // Remove all views from the mViewItem buffer, except the camera view. for (int i = 0; i < mViewItem.length; i++) { if (mViewItem[i] == null) { continue; @@ -1920,8 +1939,9 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { if (v != mCameraView) { removeView(v); } - if (mDataAdapter.getImageData(mViewItem[i].getId()) != null) { - mDataAdapter.getImageData(mViewItem[i].getId()).recycle(); + ImageData imageData = mDataAdapter.getImageData(mViewItem[i].getId()); + if (imageData != null) { + imageData.recycle(); } } @@ -1954,7 +1974,9 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { if (mListener != null) { mListener.onReload(); - mListener.onDataFocusChanged(mViewItem[mCurrentItem].getId(), true); + if (!stayInPreview) { + mListener.onDataFocusChanged(mViewItem[mCurrentItem].getId(), true); + } } } @@ -2625,13 +2647,7 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { if (!mController.stopScrolling(false)) { return false; } - // A down event is usually followed by a gesture, we apply gesture on - // the lower-res image during a gesture to ensure a responsive experience. - // TODO: Delay this until gesture starts. - if (mController.isZoomStarted()) { - mController.cancelLoadingZoomedImage(); - mZoomView.setVisibility(GONE); - } + return true; } @@ -2712,12 +2728,16 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { @Override public boolean onScroll(float x, float y, float dx, float dy) { - if (mViewItem[mCurrentItem] == null) { + ViewItem currItem = mViewItem[mCurrentItem]; + if (currItem == null) { return false; } + if (!mDataAdapter.canSwipeInFullScreen(currItem.getId())) { + return false; + } + hideZoomView(); // When image is zoomed in to be bigger than the screen if (mController.isZoomStarted()) { - mController.cancelLoadingZoomedImage(); ViewItem curr = mViewItem[mCurrentItem]; float transX = curr.getTranslationX() - dx; float transY = curr.getTranslationY() - dy; @@ -2777,6 +2797,9 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { if (currItem == null) { return false; } + if (!mDataAdapter.canSwipeInFullScreen(currItem.getId())) { + return false; + } if (mController.isZoomStarted()) { // Fling within the zoomed image mController.flingInsideZoomView(velocityX, velocityY); @@ -2837,6 +2860,8 @@ public class FilmStripView extends ViewGroup implements BottomControlsListener { if (inCameraFullscreen()) { return false; } + + hideZoomView(); mScaleTrend = 1f; // If the image is smaller than screen size, we should allow to zoom // in to full screen size diff --git a/src/com/android/camera/ui/PieRenderer.java b/src/com/android/camera/ui/PieRenderer.java index 008bc40ca..0039aa22c 100644 --- a/src/com/android/camera/ui/PieRenderer.java +++ b/src/com/android/camera/ui/PieRenderer.java @@ -16,9 +16,6 @@ package com.android.camera.ui; -import java.util.ArrayList; -import java.util.List; - import android.animation.Animator; import android.animation.Animator.AnimatorListener; import android.animation.ValueAnimator; @@ -40,12 +37,19 @@ import android.view.animation.Animation; import android.view.animation.Transformation; import com.android.camera.drawable.TextDrawable; +import com.android.camera.ui.ProgressRenderer.VisibilityListener; import com.android.camera2.R; +import java.util.ArrayList; +import java.util.List; + +/** + * An overlay renderer that is used to display focus state and progress state. + */ public class PieRenderer extends OverlayRenderer implements FocusIndicator { - private static final String TAG = "CAM Pie"; + private static final String TAG = "PieRenderer"; // Sometimes continuous autofocus starts and stops several times quickly. // These states are used to make sure the animation is run for at least some @@ -143,7 +147,7 @@ public class PieRenderer extends OverlayRenderer private int mAngleZone; private float mCenterAngle; - + private ProgressRenderer mProgressRenderer; private Handler mHandler = new Handler() { public void handleMessage(Message msg) { @@ -227,6 +231,7 @@ public class PieRenderer extends OverlayRenderer mLabel.setDropShadow(true); mDeadZone = res.getDimensionPixelSize(R.dimen.pie_deadzone_width); mAngleZone = res.getDimensionPixelSize(R.dimen.pie_anglezone_width); + mProgressRenderer = new ProgressRenderer(ctx); } private PieItem getRoot() { @@ -309,6 +314,10 @@ public class PieRenderer extends OverlayRenderer return mState == STATE_PIE && isVisible(); } + public void setProgress(int percent) { + mProgressRenderer.setProgress(percent); + } + private void fadeIn() { mFadeIn = new ValueAnimator(); mFadeIn.setFloatValues(0f, 1f); @@ -524,6 +533,8 @@ public class PieRenderer extends OverlayRenderer @Override public void onDraw(Canvas canvas) { + mProgressRenderer.onDraw(canvas, mFocusX, mFocusY); + float alpha = 1; if (mXFade != null) { alpha = (Float) mXFade.getAnimatedValue(); @@ -706,6 +717,11 @@ public class PieRenderer extends OverlayRenderer return false; } + @Override + public boolean isVisible() { + return super.isVisible() || mProgressRenderer.isVisible(); + } + private boolean pulledToCenter(PointF polarCoords) { return polarCoords.y < mArcRadius - mRadiusInc; } @@ -918,17 +934,6 @@ public class PieRenderer extends OverlayRenderer setCircle(mFocusX, mFocusY); } - public void alignFocus(int x, int y) { - mOverlay.removeCallbacks(mDisappear); - mAnimation.cancel(); - mAnimation.reset(); - mFocusX = x; - mFocusY = y; - mDialAngle = DIAL_HORIZONTAL; - setCircle(x, y); - mFocused = false; - } - public int getSize() { return 2 * mCircleSize; } @@ -1022,11 +1027,27 @@ public class PieRenderer extends OverlayRenderer mState = STATE_IDLE; } + public void clear(boolean waitUntilProgressIsHidden) { + if (mState == STATE_PIE) + return; + cancelFocus(); + + if (waitUntilProgressIsHidden) { + mProgressRenderer.setVisibilityListener(new VisibilityListener() { + @Override + public void onHidden() { + mOverlay.post(mDisappear); + } + }); + } else { + mOverlay.post(mDisappear); + mProgressRenderer.setVisibilityListener(null); + } + } + @Override public void clear() { - if (mState == STATE_PIE) return; - cancelFocus(); - mOverlay.post(mDisappear); + clear(false); } private void startAnimation(long duration, boolean timeout, diff --git a/src/com/android/camera/ui/ProgressRenderer.java b/src/com/android/camera/ui/ProgressRenderer.java new file mode 100644 index 000000000..500293133 --- /dev/null +++ b/src/com/android/camera/ui/ProgressRenderer.java @@ -0,0 +1,127 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.camera.ui; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Paint; +import android.graphics.RectF; + +import com.android.camera2.R; + +/** + * Renders a circular progress bar on the screen. + */ +public class ProgressRenderer { + + public static interface VisibilityListener { + public void onHidden(); + } + + private final int mProgressRadius; + private final Paint mProgressBasePaint; + private final Paint mProgressPaint; + + private RectF mArcBounds = new RectF(0, 0, 1, 1); + private int mProgressAngleDegrees = 270; + private boolean mVisible = false; + private VisibilityListener mVisibilityListener; + + /** + * After we reach 100%, keep on painting the progress for another x milliseconds + * before hiding it. + */ + private static final int SHOW_PROGRESS_X_ADDITIONAL_MS = 100; + + /** When to hide the progress indicator. */ + private long mTimeToHide = 0; + + public ProgressRenderer(Context context) { + mProgressRadius = context.getResources().getDimensionPixelSize(R.dimen.pie_progress_radius); + int pieProgressWidth = context.getResources().getDimensionPixelSize( + R.dimen.pie_progress_width); + mProgressBasePaint = createProgressPaint(pieProgressWidth, 0.2f); + mProgressPaint = createProgressPaint(pieProgressWidth, 1.0f); + } + + /** + * Sets or replaces a visiblity listener. + */ + public void setVisibilityListener(VisibilityListener listener) { + mVisibilityListener = listener; + } + + /** + * Shows a progress indicator. If the progress is '100', the progress + * indicator will be hidden. + * + * @param percent the progress in percent (0-100). + */ + public void setProgress(int percent) { + // Clamp the value. + percent = Math.min(100, Math.max(percent, 0)); + mProgressAngleDegrees = (int) ((360f / 100) * percent); + + // We hide the progress once we drew the 100% state once. + if (percent < 100) { + mVisible = true; + mTimeToHide = System.currentTimeMillis() + SHOW_PROGRESS_X_ADDITIONAL_MS; + } + } + + /** + * Draw the current progress (if < 100%) centered at the given location. + */ + public void onDraw(Canvas canvas, int centerX, int centerY) { + if (!mVisible) { + return; + } + mArcBounds = new RectF(centerX - mProgressRadius, centerY - mProgressRadius, centerX + + mProgressRadius, + centerY + mProgressRadius); + + canvas.drawCircle(centerX, centerY, mProgressRadius, mProgressBasePaint); + canvas.drawArc(mArcBounds, -90, mProgressAngleDegrees, false, mProgressPaint); + + // After we reached 100%, we paint the progress renderer for another x + // milliseconds until we hide it. + if (mProgressAngleDegrees == 360 && System.currentTimeMillis() > mTimeToHide) { + mVisible = false; + if (mVisibilityListener != null) { + mVisibilityListener.onHidden(); + } + } + } + + /** + * @return Whether the progress renderer is visible. + */ + public boolean isVisible() { + return mVisible; + } + + private static Paint createProgressPaint(int width, float alpha) { + Paint paint = new Paint(); + paint.setAntiAlias(true); + // 20% alpha. + paint.setColor(Color.argb((int) (alpha * 255), 255, 255, 255)); + paint.setStrokeWidth(width); + paint.setStyle(Paint.Style.STROKE); + return paint; + } +} diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java index dbd078d14..68211d648 100644 --- a/src/com/android/camera/util/CameraUtil.java +++ b/src/com/android/camera/util/CameraUtil.java @@ -679,6 +679,16 @@ public class CameraUtil { rect.bottom = Math.round(rectF.bottom); } + public static Rect rectFToRect(RectF rectF) { + Rect rect = new Rect(); + rectFToRect(rectF, rect); + return rect; + } + + public static RectF rectToRectF(Rect r) { + return new RectF(r.left, r.top, r.right, r.bottom); + } + public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, int viewWidth, int viewHeight) { // Need mirror for front camera. @@ -691,6 +701,21 @@ public class CameraUtil { matrix.postTranslate(viewWidth / 2f, viewHeight / 2f); } + public static void prepareMatrix(Matrix matrix, boolean mirror, int displayOrientation, + Rect previewRect) { + // Need mirror for front camera. + matrix.setScale(mirror ? -1 : 1, 1); + // This is the value for android.hardware.Camera.setDisplayOrientation. + matrix.postRotate(displayOrientation); + + // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). + // We need to map camera driver coordinates to preview rect coordinates + Matrix mapping = new Matrix(); + mapping.setRectToRect(new RectF(-1000, -1000, 1000, 1000), rectToRectF(previewRect), + Matrix.ScaleToFit.FILL); + matrix.setConcat(mapping, matrix); + } + public static String createJpegName(long dateTaken) { synchronized (sImageFileNamer) { return sImageFileNamer.generateName(dateTaken); @@ -820,7 +845,10 @@ public class CameraUtil { * the right range. */ public static int[] getPhotoPreviewFpsRange(Parameters params) { - List<int[]> frameRates = params.getSupportedPreviewFpsRange(); + return getPhotoPreviewFpsRange(params.getSupportedPreviewFpsRange()); + } + + public static int[] getPhotoPreviewFpsRange(List<int[]> frameRates) { if (frameRates.size() == 0) { Log.e(TAG, "No suppoted frame rates returned!"); return null; @@ -902,6 +930,8 @@ public class CameraUtil { public static void playVideo(Activity activity, Uri uri, String title) { try { boolean isSecureCamera = ((CameraActivity)activity).isSecureCamera(); + UsageStatistics.onEvent(UsageStatistics.COMPONENT_CAMERA, + UsageStatistics.ACTION_PLAY_VIDEO, null); if (!isSecureCamera) { Intent intent = IntentHelper.getVideoPlayerIntent(activity, uri) .putExtra(Intent.EXTRA_TITLE, title) @@ -966,20 +996,4 @@ public class CameraUtil { } return ret; } - - /** - * Launches apps supporting action {@link Intent.ACTION_MAIN} of category - * {@link Intent.CATEGORY_APP_GALLERY}. Note that - * {@link Intent.CATEGORY_APP_GALLERY} is only available on API level 15+. - * - * @param ctx The {@link android.content.Context} to launch the app. - * @return {@code true} on success. - */ - public static boolean launchGallery(Context ctx) { - if (ApiHelper.HAS_APP_GALLERY) { - ctx.startActivity(IntentHelper.getGalleryIntent(ctx)); - return true; - } - return false; - } } |