summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/AndroidCameraManagerImpl.java15
-rw-r--r--src/com/android/camera/CameraActivity.java158
-rw-r--r--src/com/android/camera/ComboPreferences.java1
-rw-r--r--src/com/android/camera/FocusOverlayManager.java42
-rw-r--r--src/com/android/camera/ListPreference.java2
-rw-r--r--src/com/android/camera/PhotoController.java3
-rw-r--r--src/com/android/camera/PhotoModule.java97
-rw-r--r--src/com/android/camera/PhotoUI.java70
-rw-r--r--src/com/android/camera/Storage.java133
-rw-r--r--src/com/android/camera/SurfaceTextureRenderer.java6
-rw-r--r--src/com/android/camera/VideoModule.java1
-rw-r--r--src/com/android/camera/VideoUI.java12
-rw-r--r--src/com/android/camera/WideAnglePanoramaModule.java14
-rw-r--r--src/com/android/camera/WideAnglePanoramaUI.java10
-rw-r--r--src/com/android/camera/app/AppManagerFactory.java8
-rw-r--r--src/com/android/camera/app/PlaceholderManager.java185
-rw-r--r--src/com/android/camera/data/CameraDataAdapter.java7
-rw-r--r--src/com/android/camera/data/InProgressDataWrapper.java22
-rw-r--r--src/com/android/camera/ui/CameraRootView.java23
-rw-r--r--src/com/android/camera/ui/FilmStripView.java53
-rw-r--r--src/com/android/camera/ui/PieRenderer.java59
-rw-r--r--src/com/android/camera/ui/ProgressRenderer.java127
-rw-r--r--src/com/android/camera/util/CameraUtil.java48
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;
- }
}