summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSanthosh Kumar H E <skhara@codeaurora.org>2013-12-05 16:36:52 +0530
committerSanthosh Kumar H E <skhara@codeaurora.org>2013-12-06 18:58:24 +0530
commitdbdb7620da136374eea95170261032f910015db0 (patch)
tree007242cdb44941ecb855751c85da69aa97cc6a43 /src
parent1c7734057201ffd1b6d165d9e24793ef3d213043 (diff)
parentdf2b0819907c440a660e228414a18184732816d1 (diff)
downloadandroid_packages_apps_Snap-dbdb7620da136374eea95170261032f910015db0.tar.gz
android_packages_apps_Snap-dbdb7620da136374eea95170261032f910015db0.tar.bz2
android_packages_apps_Snap-dbdb7620da136374eea95170261032f910015db0.zip
Merge remote-tracking branch into merge_branch
Delay onResume tasks to speed up lockscreen onResume->onPause->onResume launch sequence. Import translations. DO NOT MERGE gcam: Clean up placeholders, and add deletion robustness. Fix issue of focus indicator staying on without being hidden Ensure view size gets updated after phone decors change. Show the 100% state of the progress at least one frame. Add parameters and deduplicate parameter changes Revert parallel opening camera in photo mode. Differentiate the InProgressData from the normal PhotoData. Close mode menus if another control is touched Start gcam module directly when handling capture intent. Import translations. DO NOT MERGE Add a null check to fix NPE Add logging to various actions Ensure mOpenCameraThread has been setup before dereferencing. Add logging to various actions Ensure mOpenCameraThread has been setup before dereferencing. Add GCam progress indicator. hide preview cover on arrival of new preview data if hidden Fix photo mode is getting stuck in a single CameraState. ... Conflicts: res/layout/photo_module.xml res/values/arrays.xml src/com/android/camera/CameraActivity.java src/com/android/camera/PhotoUI.java src/com/android/camera/Storage.java src/com/android/camera/WideAnglePanoramaModule.java src/com/android/camera/ui/FilmStripView.java Change-Id: Ic41b4e7e07b2b0ed7936b78a6c5f05270d05985f
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;
- }
}