summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorSteve Kondik <steve@cyngn.com>2015-03-24 07:23:19 -0700
committerSteve Kondik <steve@cyngn.com>2015-03-24 07:23:19 -0700
commit6eaafbef54de3a9b70ec338512b537cd0f7b5d13 (patch)
tree69646095b714fc45fb7c4d8aeeae6dcaf8aa3d49 /src
parent708b6b29c2cde2510b6cee475bbb91f0ac06c74f (diff)
parent6cb15e279f0fcecfab919a51b6a542bcef5bd2f1 (diff)
downloadandroid_packages_apps_Camera2-6eaafbef54de3a9b70ec338512b537cd0f7b5d13.tar.gz
android_packages_apps_Camera2-6eaafbef54de3a9b70ec338512b537cd0f7b5d13.tar.bz2
android_packages_apps_Camera2-6eaafbef54de3a9b70ec338512b537cd0f7b5d13.zip
Merge branch 'lollipop-mr1-release' of https://android.googlesource.com/platform/packages/apps/Camera2 into cm-12.1staging/cm-12.1
Change-Id: I24fa423d91d8d441cbf5d9e75b98037584dd2fbc
Diffstat (limited to 'src')
-rw-r--r--src/com/android/camera/ButtonManager.java37
-rw-r--r--src/com/android/camera/CameraActivity.java235
-rw-r--r--src/com/android/camera/CameraErrorCallback.java36
-rw-r--r--src/com/android/camera/CaptureActivity.java23
-rw-r--r--src/com/android/camera/CaptureModule.java61
-rw-r--r--src/com/android/camera/FocusOverlayManager.java27
-rw-r--r--src/com/android/camera/MultiToggleImageButton.java4
-rw-r--r--src/com/android/camera/OnScreenHint.java22
-rw-r--r--src/com/android/camera/PhotoModule.java158
-rw-r--r--src/com/android/camera/VideoModule.java48
-rw-r--r--src/com/android/camera/app/CameraApp.java14
-rw-r--r--src/com/android/camera/app/CameraAppUI.java138
-rw-r--r--src/com/android/camera/app/CameraController.java14
-rw-r--r--src/com/android/camera/app/CameraProvider.java5
-rw-r--r--src/com/android/camera/data/CameraDataAdapter.java38
-rw-r--r--src/com/android/camera/data/FixedFirstDataAdapter.java9
-rw-r--r--src/com/android/camera/data/FixedLastDataAdapter.java12
-rw-r--r--src/com/android/camera/data/LocalData.java13
-rw-r--r--src/com/android/camera/data/LocalDataUtil.java4
-rw-r--r--src/com/android/camera/data/LocalMediaData.java82
-rw-r--r--src/com/android/camera/data/LocalSessionData.java8
-rw-r--r--src/com/android/camera/data/SimpleViewData.java6
-rw-r--r--src/com/android/camera/debug/DebugCameraProxy.java12
-rw-r--r--src/com/android/camera/debug/Log.java19
-rw-r--r--src/com/android/camera/filmstrip/DataAdapter.java4
-rw-r--r--src/com/android/camera/one/OneCamera.java7
-rw-r--r--src/com/android/camera/one/OneCameraException.java29
-rw-r--r--src/com/android/camera/one/OneCameraManager.java42
-rw-r--r--src/com/android/camera/one/Settings3A.java12
-rw-r--r--src/com/android/camera/one/v2/AutoFocusHelper.java23
-rw-r--r--src/com/android/camera/one/v2/ImageCaptureManager.java20
-rw-r--r--src/com/android/camera/settings/AppUpgrader.java69
-rw-r--r--src/com/android/camera/settings/CameraSettingsActivity.java30
-rw-r--r--src/com/android/camera/settings/Keys.java2
-rw-r--r--src/com/android/camera/settings/SettingsManager.java37
-rw-r--r--src/com/android/camera/settings/SettingsUpgrader.java21
-rw-r--r--src/com/android/camera/settings/SettingsUtil.java17
-rw-r--r--src/com/android/camera/ui/BottomBar.java93
-rw-r--r--src/com/android/camera/ui/ModeListView.java35
-rw-r--r--src/com/android/camera/util/ApiHelper.java13
-rw-r--r--src/com/android/camera/util/CameraUtil.java42
-rw-r--r--src/com/android/camera/util/QuickActivity.java227
-rw-r--r--src/com/android/camera/widget/FilmstripView.java78
-rw-r--r--src/com/android/camera/widget/VideoRecordingHints.java140
44 files changed, 1319 insertions, 647 deletions
diff --git a/src/com/android/camera/ButtonManager.java b/src/com/android/camera/ButtonManager.java
index b7fa08f2e..3250c01f4 100644
--- a/src/com/android/camera/ButtonManager.java
+++ b/src/com/android/camera/ButtonManager.java
@@ -417,6 +417,19 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
*/
public void disableButton(int buttonId) {
MultiToggleImageButton button = getButtonOrError(buttonId);
+
+ // HDR and HDR+ buttons share the same button object,
+ // but change actual image icons at runtime.
+ // This extra check is to ensure the correct icons are used
+ // in the case of the HDR[+] button being disabled at startup,
+ // e.g. app startup with front-facing camera.
+ // b/18104680
+ if (buttonId == BUTTON_HDR_PLUS) {
+ initializeHdrPlusButtonIcons(button, R.array.pref_camera_hdr_plus_icons);
+ } else if (buttonId == BUTTON_HDR) {
+ initializeHdrButtonIcons(button, R.array.pref_camera_hdr_icons);
+ }
+
if (button.isEnabled()) {
button.setEnabled(false);
if (mListener != null) {
@@ -709,10 +722,7 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
private void initializeHdrPlusButton(MultiToggleImageButton button,
final ButtonCallback cb, int resIdImages) {
- if (resIdImages > 0) {
- button.overrideImageIds(resIdImages);
- }
- button.overrideContentDescriptions(R.array.hdr_plus_descriptions);
+ initializeHdrPlusButtonIcons(button, resIdImages);
int index = mSettingsManager.getIndexOfCurrentValue(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_CAMERA_HDR_PLUS);
@@ -730,16 +740,20 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
});
}
+ private void initializeHdrPlusButtonIcons(MultiToggleImageButton button, int resIdImages) {
+ if (resIdImages > 0) {
+ button.overrideImageIds(resIdImages);
+ }
+ button.overrideContentDescriptions(R.array.hdr_plus_descriptions);
+ }
+
/**
* Initialize an hdr button.
*/
private void initializeHdrButton(MultiToggleImageButton button,
final ButtonCallback cb, int resIdImages) {
- if (resIdImages > 0) {
- button.overrideImageIds(resIdImages);
- }
- button.overrideContentDescriptions(R.array.hdr_descriptions);
+ initializeHdrButtonIcons(button, resIdImages);
int index = mSettingsManager.getIndexOfCurrentValue(SettingsManager.SCOPE_GLOBAL,
Keys.KEY_CAMERA_HDR);
@@ -757,6 +771,13 @@ public class ButtonManager implements SettingsManager.OnSettingChangedListener {
});
}
+ private void initializeHdrButtonIcons(MultiToggleImageButton button, int resIdImages) {
+ if (resIdImages > 0) {
+ button.overrideImageIds(resIdImages);
+ }
+ button.overrideContentDescriptions(R.array.hdr_descriptions);
+ }
+
/**
* Initialize a countdown timer button.
*/
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index be0832222..f96d63433 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -20,7 +20,6 @@ package com.android.camera;
import android.animation.Animator;
import android.annotation.TargetApi;
import android.app.ActionBar;
-import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ActivityNotFoundException;
@@ -104,6 +103,7 @@ import com.android.camera.hardware.HardwareSpec;
import com.android.camera.hardware.HardwareSpecImpl;
import com.android.camera.module.ModuleController;
import com.android.camera.module.ModulesInfo;
+import com.android.camera.one.OneCameraException;
import com.android.camera.one.OneCameraManager;
import com.android.camera.session.CaptureSession;
import com.android.camera.session.CaptureSessionManager;
@@ -128,13 +128,15 @@ import com.android.camera.util.GcamHelper;
import com.android.camera.util.GoogleHelpHelper;
import com.android.camera.util.IntentHelper;
import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
-import com.android.camera.util.ReleaseDialogHelper;
+import com.android.camera.util.QuickActivity;
+import com.android.camera.util.ReleaseHelper;
import com.android.camera.util.UsageStatistics;
import com.android.camera.widget.FilmstripView;
import com.android.camera.widget.Preloader;
import com.android.camera2.R;
import com.android.ex.camera2.portability.CameraAgent;
import com.android.ex.camera2.portability.CameraAgentFactory;
+import com.android.ex.camera2.portability.CameraExceptionHandler;
import com.android.ex.camera2.portability.CameraSettings;
import com.bumptech.glide.Glide;
import com.bumptech.glide.GlideBuilder;
@@ -154,7 +156,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.concurrent.TimeUnit;
-public class CameraActivity extends Activity
+public class CameraActivity extends QuickActivity
implements AppController, CameraAgent.CameraOpenCallback,
ShareActionProvider.OnShareTargetSelectedListener,
OrientationManager.OnOrientationChangeListener, SettingsManager.OnSettingChangedListener {
@@ -185,6 +187,13 @@ public class CameraActivity extends Activity
private Context mAppContext;
/**
+ * Camera fatal error handling:
+ * 1) Present error dialog to guide users to exit the app.
+ * 2) If users hit home button, onPause should just call finish() to exit the app.
+ */
+ private boolean mCameraFatalError = false;
+
+ /**
* Whether onResume should reset the view to the preview.
*/
private boolean mResetToPreviewOnResume = true;
@@ -275,23 +284,17 @@ public class CameraActivity extends Activity
return mModuleManager;
}
- // close activity when screen turns off
- private final BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
+ /**
+ * Close activity when secure app passes lock screen or screen turns
+ * off.
+ */
+ private final BroadcastReceiver mShutdownReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
finish();
}
};
- private final ActionBar.OnMenuVisibilityListener mOnMenuVisibilityListener =
- new ActionBar.OnMenuVisibilityListener() {
- @Override
- public void onMenuVisibilityChanged(boolean isVisible) {
- // TODO: Remove this or bring back the original implementation: cancel
- // auto-hide actionbar.
- }
- };
-
/**
* Whether the screen is kept turned on.
*/
@@ -384,8 +387,8 @@ public class CameraActivity extends Activity
fileAgeFromDataID(currentDataId));
// If applicable, show release information before this item
// is shared.
- if (ReleaseDialogHelper.shouldShowReleaseInfoDialogOnShare(data)) {
- ReleaseDialogHelper.showReleaseInfoDialog(CameraActivity.this,
+ if (ReleaseHelper.shouldShowReleaseInfoDialogOnShare(data)) {
+ ReleaseHelper.showReleaseInfoDialog(CameraActivity.this,
new Callback<Void>() {
@Override
public void onCallback(Void result) {
@@ -537,8 +540,9 @@ public class CameraActivity extends Activity
@Override
public void onCameraDisabled(int cameraId) {
- UsageStatistics.instance().cameraFailure(eventprotos.CameraFailure.FailureReason.SECURITY,
- null);
+ UsageStatistics.instance().cameraFailure(
+ eventprotos.CameraFailure.FailureReason.SECURITY, null,
+ UsageStatistics.NONE, UsageStatistics.NONE);
Log.w(TAG, "Camera disabled: " + cameraId);
CameraUtil.showErrorAndFinish(this, R.string.camera_disabled);
}
@@ -546,7 +550,8 @@ public class CameraActivity extends Activity
@Override
public void onDeviceOpenFailure(int cameraId, String info) {
UsageStatistics.instance().cameraFailure(
- eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info);
+ eventprotos.CameraFailure.FailureReason.OPEN_FAILURE, info,
+ UsageStatistics.NONE, UsageStatistics.NONE);
Log.w(TAG, "Camera open failure: " + info);
CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
}
@@ -560,7 +565,8 @@ public class CameraActivity extends Activity
@Override
public void onReconnectionFailure(CameraAgent mgr, String info) {
UsageStatistics.instance().cameraFailure(
- eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null);
+ eventprotos.CameraFailure.FailureReason.RECONNECT_FAILURE, null,
+ UsageStatistics.NONE, UsageStatistics.NONE);
Log.w(TAG, "Camera reconnection failure:" + info);
CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
}
@@ -1325,31 +1331,77 @@ public class CameraActivity extends Activity
}
}
- private final CameraAgent.CameraExceptionCallback mCameraDefaultExceptionCallback
- = new CameraAgent.CameraExceptionCallback() {
+ /**
+ * Note: Make sure this callback is unregistered properly when the activity
+ * is destroyed since we're otherwise leaking the Activity reference.
+ */
+ private final CameraExceptionHandler.CameraExceptionCallback mCameraExceptionCallback
+ = new CameraExceptionHandler.CameraExceptionCallback() {
+ @Override
+ public void onCameraError(int errorCode) {
+ // Not a fatal error. only do Log.e().
+ Log.e(TAG, "Camera error callback. error=" + errorCode);
+ }
+ @Override
+ public void onCameraException(
+ RuntimeException ex, String commandHistory, int action, int state) {
+ Log.e(TAG, "Camera Exception", ex);
+ UsageStatistics.instance().cameraFailure(
+ eventprotos.CameraFailure.FailureReason.API_RUNTIME_EXCEPTION,
+ commandHistory, action, state);
+ onFatalError();
+ }
@Override
- public void onCameraException(RuntimeException e) {
- Log.e(TAG, "Camera Exception", e);
- CameraUtil.showErrorAndFinish(CameraActivity.this,
- R.string.cannot_connect_camera);
+ public void onDispatchThreadException(RuntimeException ex) {
+ Log.e(TAG, "DispatchThread Exception", ex);
+ UsageStatistics.instance().cameraFailure(
+ eventprotos.CameraFailure.FailureReason.API_TIMEOUT,
+ null, UsageStatistics.NONE, UsageStatistics.NONE);
+ onFatalError();
+ }
+ private void onFatalError() {
+ if (mCameraFatalError) {
+ return;
+ }
+ mCameraFatalError = true;
+
+ // If the activity receives exception during onPause, just exit the app.
+ if (mPaused && !isFinishing()) {
+ Log.e(TAG, "Fatal error during onPause, call Activity.finish()");
+ finish();
+ } else {
+ CameraUtil.showErrorAndFinish(CameraActivity.this,
+ R.string.cannot_connect_camera);
+ }
}
};
@Override
- public void onCreate(Bundle state) {
+ public void onNewIntentTasks(Intent intent) {
+ onModeSelected(getModeIndex());
+ }
+
+ @Override
+ public void onCreateTasks(Bundle state) {
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_START);
- super.onCreate(state);
+ mAppContext = getApplication().getBaseContext();
+
if (!Glide.isSetup()) {
- Glide.setup(new GlideBuilder(this)
- .setResizeService(new FifoPriorityThreadPoolExecutor(1)));
- Glide.get(this).setMemoryCategory(MemoryCategory.HIGH);
+ Glide.setup(new GlideBuilder(getAndroidContext())
+ .setResizeService(new FifoPriorityThreadPoolExecutor(2)));
+ Glide.get(getAndroidContext()).setMemoryCategory(MemoryCategory.HIGH);
}
mOnCreateTime = System.currentTimeMillis();
- mAppContext = getApplicationContext();
mSoundPlayer = new SoundPlayer(mAppContext);
- mCameraManager = OneCameraManager.get(this);
+ try {
+ mCameraManager = OneCameraManager.get(this);
+ } catch (OneCameraException e) {
+ Log.d(TAG, "Creating camera manager failed.", e);
+ CameraUtil.showErrorAndFinish(this, R.string.cannot_connect_camera);
+ return;
+ }
// TODO: Try to move all the resources allocation to happen as soon as
// possible so we can call module.init() at the earliest time.
@@ -1376,14 +1428,15 @@ public class CameraActivity extends Activity
} else {
mActionBar.setBackgroundDrawable(new ColorDrawable(0x80000000));
}
- mActionBar.addOnMenuVisibilityListener(mOnMenuVisibilityListener);
mMainHandler = new MainHandler(this, getMainLooper());
mCameraController = new CameraController(mAppContext, this, mMainHandler,
- CameraAgentFactory.getAndroidCameraAgent(this, CameraAgentFactory.CameraApi.API_1),
- CameraAgentFactory.getAndroidCameraAgent(this, CameraAgentFactory.CameraApi.AUTO));
- mCameraController.setCameraDefaultExceptionCallback(mCameraDefaultExceptionCallback,
- mMainHandler);
+ CameraAgentFactory.getAndroidCameraAgent(mAppContext,
+ CameraAgentFactory.CameraApi.API_1),
+ CameraAgentFactory.getAndroidCameraAgent(mAppContext,
+ CameraAgentFactory.CameraApi.AUTO));
+ mCameraController.setCameraExceptionHandler(
+ new CameraExceptionHandler(mCameraExceptionCallback, mMainHandler));
mModeListView = (ModeListView) findViewById(R.id.mode_list_layout);
mModeListView.init(mModuleManager.getSupportedModeIndexList());
@@ -1418,10 +1471,17 @@ public class CameraActivity extends Activity
win.setAttributes(params);
// Filter for screen off so that we can finish secure camera
- // activity
- // when screen is off.
- IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
- registerReceiver(mScreenOffReceiver, filter);
+ // activity when screen is off.
+ IntentFilter filter_screen_off = new IntentFilter(Intent.ACTION_SCREEN_OFF);
+ registerReceiver(mShutdownReceiver, filter_screen_off);
+
+ // Filter for phone unlock so that we can finish secure camera
+ // via this UI path:
+ // 1. from secure lock screen, user starts secure camera
+ // 2. user presses home button
+ // 3. user unlocks phone
+ IntentFilter filter_user_unlock = new IntentFilter(Intent.ACTION_USER_PRESENT);
+ registerReceiver(mShutdownReceiver, filter_user_unlock);
}
mCameraAppUI = new CameraAppUI(this,
(MainActivityLayout) findViewById(R.id.activity_root_view), isCaptureIntent());
@@ -1634,7 +1694,7 @@ public class CameraActivity extends Activity
}
@Override
- public void onPause() {
+ public void onPauseTasks() {
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_PAUSE);
/*
@@ -1672,22 +1732,19 @@ public class CameraActivity extends Activity
UsageStatistics.instance().backgrounded();
- // Close the camera and wait for the operation done. But if we time out
- // via RuntimeException, just continue pausing, and request a finish().
- try {
+ // Camera is in fatal state. A fatal dialog is presented to users, but users just hit home
+ // button. Let's just kill the process.
+ if (mCameraFatalError && !isFinishing()) {
+ Log.v(TAG, "onPause when camera is in fatal state, call Activity.finish()");
+ finish();
+ } else {
+ // Close the camera and wait for the operation done.
mCameraController.closeCamera(true);
- } catch (RuntimeException e) {
- Log.e(TAG, "Exception while closing camera", e);
- if (!isFinishing()) {
- finish();
- }
}
-
- super.onPause();
}
@Override
- public void onResume() {
+ public void onResumeTasks() {
CameraPerformanceTracker.onEvent(CameraPerformanceTracker.ACTIVITY_RESUME);
Log.v(TAG, "Build info: " + Build.DISPLAY);
@@ -1754,10 +1811,10 @@ public class CameraActivity extends Activity
}
mOrientationManager.resume();
- super.onResume();
mPeekAnimationThread = new HandlerThread("Peek animation");
mPeekAnimationThread.start();
- mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper());
+ mPeekAnimationHandler = new PeekAnimationHandler(mPeekAnimationThread.getLooper(),
+ mMainHandler, mAboveFilmstripControlLayout);
mCurrentModule.hardResetSettings(mSettingsManager);
mCurrentModule.resume();
@@ -1822,7 +1879,7 @@ public class CameraActivity extends Activity
});
mPanoramaViewHelper.onResume();
- ReleaseDialogHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
+ ReleaseHelper.showReleaseInfoDialogOnStart(this, mSettingsManager);
syncLocationManagerSetting();
final int previewVisibility = getPreviewVisibility();
@@ -1841,8 +1898,7 @@ public class CameraActivity extends Activity
}
@Override
- public void onStart() {
- super.onStart();
+ public void onStartTasks() {
mIsActivityRunning = true;
mPanoramaViewHelper.onStart();
@@ -1868,22 +1924,21 @@ public class CameraActivity extends Activity
}
@Override
- protected void onStop() {
+ protected void onStopTasks() {
mIsActivityRunning = false;
mPanoramaViewHelper.onStop();
mLocationManager.disconnect();
- super.onStop();
}
@Override
- public void onDestroy() {
+ public void onDestroyTasks() {
if (mSecureCamera) {
- unregisterReceiver(mScreenOffReceiver);
+ unregisterReceiver(mShutdownReceiver);
}
- mActionBar.removeOnMenuVisibilityListener(mOnMenuVisibilityListener);
mSettingsManager.removeAllListeners();
mCameraController.removeCallbackReceiver();
+ mCameraController.setCameraExceptionHandler(null);
getContentResolver().unregisterContentObserver(mLocalImagesObserver);
getContentResolver().unregisterContentObserver(mLocalVideosObserver);
getServices().getCaptureSessionManager().removeSessionListener(mSessionListener);
@@ -1894,13 +1949,8 @@ public class CameraActivity extends Activity
mOrientationManager = null;
mButtonManager = null;
mSoundPlayer.release();
- try {
- CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
- CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
- } catch (RuntimeException e) {
- Log.e(TAG, "CameraAgentFactory exception during destroy", e);
- }
- super.onDestroy();
+ CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.API_1);
+ CameraAgentFactory.recycle(CameraAgentFactory.CameraApi.AUTO);
}
@Override
@@ -2041,6 +2091,17 @@ public class CameraActivity extends Activity
return super.onCreateOptionsMenu(menu);
}
+ @Override
+ public boolean onPrepareOptionsMenu(Menu menu) {
+ if (isSecureCamera() && !ApiHelper.isLOrHigher()) {
+ // Compatibility pre-L: launching new activities right above
+ // lockscreen does not reliably work, only show help if not secure
+ menu.removeItem(R.id.action_help_and_feedback);
+ }
+
+ return super.onPrepareOptionsMenu(menu);
+ }
+
protected long getStorageSpaceBytes() {
synchronized (mStorageSpaceLock) {
return mStorageSpaceBytes;
@@ -2114,9 +2175,15 @@ public class CameraActivity extends Activity
}
mStorageHint.show();
UsageStatistics.instance().storageWarning(storageSpace);
+
+ // Disable all user interactions,
+ mCameraAppUI.setDisableAllUserInteractions(true);
} else if (mStorageHint != null) {
mStorageHint.cancel();
mStorageHint = null;
+
+ // Re-enable all user interactions.
+ mCameraAppUI.setDisableAllUserInteractions(false);
}
}
@@ -2182,7 +2249,6 @@ public class CameraActivity extends Activity
mCameraAppUI.resetBottomControls(mCurrentModule, modeIndex);
mCameraAppUI.addShutterListener(mCurrentModule);
- mCameraAppUI.hideLetterboxing();
openModule(mCurrentModule);
mCurrentModule.onOrientationChanged(mLastRawOrientation);
// Store the module index so we can use it the next time the Camera
@@ -2367,10 +2433,12 @@ public class CameraActivity extends Activity
private void openModule(CameraModule module) {
module.init(this, isSecureCamera(), isCaptureIntent());
module.hardResetSettings(mSettingsManager);
- module.resume();
- UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
- NavigationChange.InteractionCause.BUTTON);
- updatePreviewVisibility();
+ if (!mPaused) {
+ module.resume();
+ UsageStatistics.instance().changeScreen(currentUserInterfaceMode(),
+ NavigationChange.InteractionCause.BUTTON);
+ updatePreviewVisibility();
+ }
}
private void closeModule(CameraModule module) {
@@ -2461,6 +2529,11 @@ public class CameraActivity extends Activity
@Override
public void onOrientationChanged(int orientation) {
+ if (orientation != mLastRawOrientation) {
+ Log.v(TAG, "orientation changed (from:to) " + mLastRawOrientation +
+ ":" + orientation);
+ }
+
// We keep the last known orientation. So if the user first orient
// the camera then point the camera to floor or sky, we still have
// the correct orientation.
@@ -2702,7 +2775,7 @@ public class CameraActivity extends Activity
filmstripBottomPanel.setViewerButtonVisibility(viewButtonVisibility);
}
- private class PeekAnimationHandler extends Handler {
+ private static class PeekAnimationHandler extends Handler {
private class DataAndCallback {
LocalData mData;
com.android.camera.util.Callback<Bitmap> mCallback;
@@ -2714,8 +2787,14 @@ public class CameraActivity extends Activity
}
}
- public PeekAnimationHandler(Looper looper) {
+ private final Handler mMainHandler;
+ private final FrameLayout mAboveFilmstripControlLayout;
+
+ public PeekAnimationHandler(Looper looper, Handler mainHandler,
+ FrameLayout aboveFilmstripControlLayout) {
super(looper);
+ mMainHandler = mainHandler;
+ mAboveFilmstripControlLayout = aboveFilmstripControlLayout;
}
/**
diff --git a/src/com/android/camera/CameraErrorCallback.java b/src/com/android/camera/CameraErrorCallback.java
deleted file mode 100644
index 5f69332c4..000000000
--- a/src/com/android/camera/CameraErrorCallback.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * Copyright (C) 2010 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.camera;
-
-import com.android.camera.debug.Log;
-import com.android.ex.camera2.portability.CameraAgent;
-
-public class CameraErrorCallback
- implements CameraAgent.CameraErrorCallback {
- private static final Log.Tag TAG = new Log.Tag("CamErrCallback");
-
- @Override
- public void onError(int error, CameraAgent.CameraProxy camera) {
- Log.e(TAG, "Got camera error callback. error=" + error);
- if (error == android.hardware.Camera.CAMERA_ERROR_SERVER_DIED) {
- // We are not sure about the current state of the app (in preview or
- // snapshot or recording). Closing the app is better than creating a
- // new Camera object.
- throw new RuntimeException("Media server died.");
- }
- }
-}
diff --git a/src/com/android/camera/CaptureActivity.java b/src/com/android/camera/CaptureActivity.java
new file mode 100644
index 000000000..6b9b91594
--- /dev/null
+++ b/src/com/android/camera/CaptureActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * 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;
+
+// Use a different activity for capture intents, so it can have a different
+// task affinity from others. This makes sure the regular camera activity is not
+// reused for IMAGE_CAPTURE or VIDEO_CAPTURE intents from other activities.
+public class CaptureActivity extends CameraActivity {
+}
diff --git a/src/com/android/camera/CaptureModule.java b/src/com/android/camera/CaptureModule.java
index 9b2bb7bcb..744f2ab5d 100644
--- a/src/com/android/camera/CaptureModule.java
+++ b/src/com/android/camera/CaptureModule.java
@@ -19,7 +19,6 @@ package com.android.camera;
import android.app.Activity;
import android.content.Context;
import android.content.res.Configuration;
-import android.graphics.Bitmap;
import android.graphics.Matrix;
import android.graphics.RectF;
import android.graphics.SurfaceTexture;
@@ -140,13 +139,6 @@ public class CaptureModule extends CameraModule
private static final String PHOTO_MODULE_STRING_ID = "PhotoModule";
/** Enable additional debug output. */
private static final boolean DEBUG = true;
- /**
- * This is the delay before we execute onResume tasks when coming from the
- * lock screen, to allow time for onPause to execute.
- * <p>
- * TODO: Make sure this value is in sync with what we see on L.
- */
- private static final int ON_RESUME_TASKS_DELAY_MSEC = 20;
/** Timeout for camera open/close operations. */
private static final int CAMERA_OPEN_CLOSE_TIMEOUT_MILLIS = 2500;
@@ -243,16 +235,6 @@ public class CaptureModule extends CameraModule
/** Whether the module is paused right now. */
private boolean mPaused;
- /** Whether this module was resumed from lockscreen capture intent. */
- private boolean mIsResumeFromLockScreen = false;
-
- private final Runnable mResumeTaskRunnable = new Runnable() {
- @Override
- public void run() {
- onResumeTasks();
- }
- };
-
/** Main thread handler. */
private Handler mMainHandler;
/** Handler thread for camera-related operations. */
@@ -306,7 +288,6 @@ public class CaptureModule extends CameraModule
@Override
public void init(CameraActivity activity, boolean isSecureCamera, boolean isCaptureIntent) {
Log.d(TAG, "init");
- mIsResumeFromLockScreen = isResumeFromLockscreen(activity);
mMainHandler = new Handler(activity.getMainLooper());
HandlerThread thread = new HandlerThread("CaptureModule.mCameraHandler");
thread.start();
@@ -387,17 +368,15 @@ public class CaptureModule extends CameraModule
params.heading = mHeading;
params.debugDataFolder = mDebugDataDir;
params.location = location;
+ params.zoom = mZoomValue;
+ params.timerSeconds = mTimerDuration > 0 ? (float) mTimerDuration : null;
mCamera.takePicture(params, session);
}
@Override
public void onCountDownFinished() {
- if (mIsImageCaptureIntent) {
- mAppController.getCameraAppUI().transitionToIntentReviewLayout();
- } else {
- mAppController.getCameraAppUI().transitionToCapture();
- }
+ mAppController.getCameraAppUI().transitionToCapture();
mAppController.getCameraAppUI().showModeOptions();
if (mPaused) {
return;
@@ -480,8 +459,9 @@ public class CaptureModule extends CameraModule
@Override
public void onRemoteShutterPress() {
+ Log.d(TAG, "onRemoteShutterPress");
// TODO: Check whether shutter is enabled.
- onShutterButtonClick();
+ takePictureNow();
}
@Override
@@ -530,21 +510,6 @@ public class CaptureModule extends CameraModule
@Override
public void resume() {
- // 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.
- if (mIsResumeFromLockScreen) {
- Log.v(TAG, "Delayng onResumeTasks from lock screen. " + System.currentTimeMillis());
- // Note: onPauseAfterSuper() will delete this runnable, so we will
- // at most have 1 copy queued up.
- mMainHandler.postDelayed(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
- } else {
- onResumeTasks();
- }
- }
-
- private void onResumeTasks() {
- Log.d(TAG, "onResumeTasks + " + System.currentTimeMillis());
mPaused = false;
mAppController.getCameraAppUI().onChangeCamera();
mAppController.addPreviewAreaSizeChangedListener(this);
@@ -581,9 +546,10 @@ public class CaptureModule extends CameraModule
@Override
public void pause() {
mPaused = true;
+ getServices().getRemoteShutterListener().onModuleExit();
cancelCountDown();
- resetTextureBufferSize();
closeCamera();
+ resetTextureBufferSize();
mCountdownSoundPlayer.unloadSound(R.raw.timer_final_second);
mCountdownSoundPlayer.unloadSound(R.raw.timer_increment);
// Remove delayed resume trigger, if it hasn't been executed yet.
@@ -883,8 +849,8 @@ public class CaptureModule extends CameraModule
}
@Override
- public void onThumbnailResult(Bitmap bitmap) {
- // TODO
+ public void onThumbnailResult(byte[] jpegData) {
+ getServices().getRemoteShutterListener().onPictureTaken(jpegData);
}
@Override
@@ -1172,7 +1138,6 @@ public class CaptureModule extends CameraModule
- centerY);
mAppController.updatePreviewTransform(mPreviewTranformationMatrix);
- mAppController.getCameraAppUI().hideLetterboxing();
// if (mGcamProxy != null) {
// mGcamProxy.postSetAspectRatio(mFinalAspectRatio);
// }
@@ -1238,6 +1203,12 @@ public class CaptureModule extends CameraModule
} catch (InterruptedException e) {
throw new RuntimeException("Interrupted while waiting to acquire camera-open lock.", e);
}
+ if (mCamera != null) {
+ // If the camera is already open, do nothing.
+ Log.d(TAG, "Camera already open, not re-opening.");
+ mCameraOpenCloseLock.release();
+ return;
+ }
mCameraManager.open(mCameraFacing, useHdr, getPictureSizeFromSettings(),
new OpenCallback() {
@Override
@@ -1327,8 +1298,8 @@ public class CaptureModule extends CameraModule
}
try {
if (mCamera != null) {
- mCamera.setFocusStateListener(null);
mCamera.close(null);
+ mCamera.setFocusStateListener(null);
mCamera = null;
}
} finally {
diff --git a/src/com/android/camera/FocusOverlayManager.java b/src/com/android/camera/FocusOverlayManager.java
index 1c5e00ace..4e4d54c7e 100644
--- a/src/com/android/camera/FocusOverlayManager.java
+++ b/src/com/android/camera/FocusOverlayManager.java
@@ -38,6 +38,7 @@ import com.android.camera.util.CameraUtil;
import com.android.camera.util.UsageStatistics;
import com.android.ex.camera2.portability.CameraCapabilities;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -133,17 +134,33 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
public void setFocusParameters();
}
- private class MainHandler extends Handler {
- public MainHandler(Looper looper) {
+ /**
+ * TODO: Refactor this so that we either don't need a handler or make
+ * mListener not be the activity.
+ */
+ private static class MainHandler extends Handler {
+ /**
+ * The outer mListener at the moment is actually the CameraActivity,
+ * which we would leak if we didn't break the GC path here using a
+ * WeakReference.
+ */
+ final WeakReference<FocusOverlayManager> mManager;
+ public MainHandler(FocusOverlayManager manager, Looper looper) {
super(looper);
+ mManager = new WeakReference<FocusOverlayManager>(manager);
}
@Override
public void handleMessage(Message msg) {
+ FocusOverlayManager manager = mManager.get();
+ if (manager == null) {
+ return;
+ }
+
switch (msg.what) {
case RESET_TOUCH_FOCUS: {
- cancelAutoFocus();
- mListener.startFaceDetection();
+ manager.cancelAutoFocus();
+ manager.mListener.startFaceDetection();
break;
}
}
@@ -155,7 +172,7 @@ public class FocusOverlayManager implements PreviewStatusListener.PreviewAreaCha
Listener listener, boolean mirror, Looper looper, FocusUI ui) {
mAppController = appController;
mSettingsManager = appController.getSettingsManager();
- mHandler = new MainHandler(looper);
+ mHandler = new MainHandler(this, looper);
mMatrix = new Matrix();
mDefaultFocusModes = new ArrayList<CameraCapabilities.FocusMode>(defaultFocusModes);
updateCapabilities(capabilities);
diff --git a/src/com/android/camera/MultiToggleImageButton.java b/src/com/android/camera/MultiToggleImageButton.java
index 8e08a5615..3816819e7 100644
--- a/src/com/android/camera/MultiToggleImageButton.java
+++ b/src/com/android/camera/MultiToggleImageButton.java
@@ -293,6 +293,10 @@ public class MultiToggleImageButton extends ImageButton {
ids.recycle();
}
}
+
+ if (mState >= 0 && mState < mImageIds.length) {
+ setImageByState(mState);
+ }
}
/**
diff --git a/src/com/android/camera/OnScreenHint.java b/src/com/android/camera/OnScreenHint.java
index edc877b6e..c1c36e8e1 100644
--- a/src/com/android/camera/OnScreenHint.java
+++ b/src/com/android/camera/OnScreenHint.java
@@ -20,7 +20,6 @@ import android.app.Activity;
import android.content.Context;
import android.graphics.PixelFormat;
import android.os.Handler;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
@@ -44,10 +43,6 @@ import com.android.camera2.R;
public class OnScreenHint {
static final Log.Tag TAG = new Log.Tag("OnScreenHint");
- int mGravity = Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM;
- int mX, mY;
- float mHorizontalMargin;
- float mVerticalMargin;
View mView;
View mNextView;
@@ -65,8 +60,6 @@ public class OnScreenHint {
*/
private OnScreenHint(Activity activity) {
mWM = (WindowManager) activity.getSystemService(Context.WINDOW_SERVICE);
- mY = activity.getResources().getDimensionPixelSize(
- R.dimen.hint_y_offset);
mParams.height = WindowManager.LayoutParams.WRAP_CONTENT;
mParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
@@ -142,23 +135,10 @@ public class OnScreenHint {
// remove the old view if necessary
handleHide();
mView = mNextView;
- final int gravity = mGravity;
- mParams.gravity = gravity;
- if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK)
- == Gravity.FILL_HORIZONTAL) {
- mParams.horizontalWeight = 1.0f;
- }
- if ((gravity & Gravity.VERTICAL_GRAVITY_MASK)
- == Gravity.FILL_VERTICAL) {
- mParams.verticalWeight = 1.0f;
- }
- mParams.x = mX;
- mParams.y = mY;
- mParams.verticalMargin = mVerticalMargin;
- mParams.horizontalMargin = mHorizontalMargin;
if (mView.getParent() != null) {
mWM.removeView(mView);
}
+
mWM.addView(mView, mParams);
}
}
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index 7544e8bfc..b4b5ee759 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -29,9 +29,7 @@ import android.hardware.SensorEvent;
import android.hardware.SensorEventListener;
import android.hardware.SensorManager;
import android.location.Location;
-import android.media.AudioManager;
import android.media.CameraProfile;
-import android.media.SoundPool;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
@@ -128,10 +126,6 @@ public class PhotoModule
private static final int UPDATE_PARAM_PREFERENCE = 4;
private static final int UPDATE_PARAM_ALL = -1;
- // 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_";
private CameraActivity mActivity;
@@ -197,8 +191,6 @@ public class PhotoModule
// The display rotation in degrees. This is only valid when mCameraState is
// not PREVIEW_STOPPED.
private int mDisplayRotation;
- // The value for android.hardware.Camera.setDisplayOrientation.
- private int mCameraDisplayOrientation;
// The value for UI components like indicators.
private int mDisplayOrientation;
// The value for cameradevice.CameraSettings.setPhotoRotationDegrees.
@@ -226,8 +218,6 @@ public class PhotoModule
? new AutoFocusMoveCallback()
: null;
- private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
-
private long mFocusStartTime;
private long mShutterCallbackTime;
private long mPostViewPictureCallbackTime;
@@ -264,6 +254,9 @@ public class PhotoModule
private final float[] mR = new float[16];
private int mHeading = -1;
+ /** Used to detect motion. We use this to release focus lock early. */
+ private MotionManager mMotionManager;
+
/** True if all the parameters needed to start preview is ready. */
private boolean mCameraPreviewParamsReady = false;
@@ -278,13 +271,6 @@ public class PhotoModule
};
private boolean mShouldResizeTo16x9 = false;
- private final Runnable mResumeTaskRunnable = new Runnable() {
- @Override
- public void run() {
- onResumeTasks();
- }
- };
-
/**
* We keep the flash setting before entering scene modes (HDR)
* and restore it after HDR is off.
@@ -328,6 +314,10 @@ public class PhotoModule
}
private void checkDisplayRotation() {
+ // Need to just be a no-op for the quick resume-pause scenario.
+ if (mPaused) {
+ return;
+ }
// Set the display orientation if display rotation has changed.
// Sometimes this happens when the device is held upside
// down and camera app is opened. Rotation animation will
@@ -471,6 +461,7 @@ public class PhotoModule
}
mAppController.getCameraAppUI().transitionToCapture();
mAppController.getCameraAppUI().showModeOptions();
+ mAppController.setShutterEnabled(true);
}
@Override
@@ -1073,7 +1064,7 @@ public class PhotoModule
mJpegPictureCallbackTime = 0;
final ExifInterface exif = Exif.getExif(originalJpegData);
-
+ final NamedEntity name = mNamedImages.getNextNameEntity();
if (mShouldResizeTo16x9) {
final ResizeBundle dataBundle = new ResizeBundle();
dataBundle.jpegData = originalJpegData;
@@ -1088,17 +1079,17 @@ public class PhotoModule
@Override
protected void onPostExecute(ResizeBundle result) {
- saveFinalPhoto(result.jpegData, result.exif, camera);
+ saveFinalPhoto(result.jpegData, name, result.exif, camera);
}
}.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, dataBundle);
} else {
- saveFinalPhoto(originalJpegData, exif, camera);
+ saveFinalPhoto(originalJpegData, name, exif, camera);
}
}
- void saveFinalPhoto(final byte[] jpegData, final ExifInterface exif, CameraProxy camera) {
-
+ void saveFinalPhoto(final byte[] jpegData, NamedEntity name, final ExifInterface exif,
+ CameraProxy camera) {
int orientation = Exif.getOrientation(exif);
float zoomValue = 1.0f;
@@ -1112,7 +1103,7 @@ public class PhotoModule
boolean gridLinesOn = Keys.areGridLinesOn(mActivity.getSettingsManager());
UsageStatistics.instance().photoCaptureDoneEvent(
eventprotos.NavigationChange.Mode.PHOTO_CAPTURE,
- mNamedImages.mQueue.lastElement().title + ".jpg", exif,
+ name.title + ".jpg", exif,
isCameraFrontFacing(), hdrOn, zoomValue, flashSetting, gridLinesOn,
(float) mTimerDuration, mShutterTouchCoordinate, mVolumeButtonClickedFlag);
mShutterTouchCoordinate = null;
@@ -1137,7 +1128,6 @@ public class PhotoModule
height = s.width();
}
}
- NamedEntity name = mNamedImages.getNextNameEntity();
String title = (name == null) ? null : name.title;
long date = (name == null) ? -1 : name.date;
@@ -1179,6 +1169,7 @@ public class PhotoModule
} else {
mJpegImageData = jpegData;
if (!mQuickCapture) {
+ Log.v(TAG, "showing UI");
mUI.showCapturedImageForReview(jpegData, orientation, mMirror);
} else {
onCaptureDone();
@@ -1280,12 +1271,15 @@ public class PhotoModule
@Override
public boolean capture() {
+ Log.i(TAG, "capture");
// If we are already in the middle of taking a snapshot or the image
// save request is full then ignore.
if (mCameraDevice == null || mCameraState == SNAPSHOT_IN_PROGRESS
- || mCameraState == SWITCHING_CAMERA || !mAppController.isShutterEnabled()) {
+ || mCameraState == SWITCHING_CAMERA) {
return false;
}
+ setCameraState(SNAPSHOT_IN_PROGRESS);
+
mCaptureStartTime = System.currentTimeMillis();
mPostViewPictureCallbackTime = 0;
@@ -1309,9 +1303,10 @@ public class PhotoModule
mJpegRotation = info.getJpegOrientation(orientation);
mCameraDevice.setJpegOrientation(mJpegRotation);
- // We don't want user to press the button again while taking a
- // multi-second HDR photo.
- mAppController.setShutterEnabled(false);
+ Log.v(TAG, "capture orientation (screen:device:used:jpeg) " +
+ mDisplayRotation + ":" + mOrientation + ":" +
+ orientation + ":" + mJpegRotation);
+
mCameraDevice.takePicture(mHandler,
new ShutterCallback(!animateBefore),
mRawPictureCallback, mPostViewPictureCallback,
@@ -1320,7 +1315,6 @@ public class PhotoModule
mNamedImages.nameNewImage(mCaptureStartTime);
mFaceDetectionStarted = false;
- setCameraState(SNAPSHOT_IN_PROGRESS);
return true;
}
@@ -1413,6 +1407,7 @@ public class PhotoModule
@Override
public void onCaptureDone() {
+ Log.i(TAG, "onCaptureDone");
if (mPaused) {
return;
}
@@ -1514,7 +1509,8 @@ public class PhotoModule
@Override
public void onShutterButtonClick() {
if (mPaused || (mCameraState == SWITCHING_CAMERA)
- || (mCameraState == PREVIEW_STOPPED)) {
+ || (mCameraState == PREVIEW_STOPPED)
+ || !mAppController.isShutterEnabled()) {
mVolumeButtonClickedFlag = false;
return;
}
@@ -1529,6 +1525,8 @@ public class PhotoModule
Log.d(TAG, "onShutterButtonClick: mCameraState=" + mCameraState +
" mVolumeButtonClickedFlag=" + mVolumeButtonClickedFlag);
+ mAppController.setShutterEnabled(false);
+
int countDownDuration = mActivity.getSettingsManager()
.getInteger(SettingsManager.SCOPE_GLOBAL, Keys.KEY_COUNTDOWN_DURATION);
mTimerDuration = countDownDuration;
@@ -1574,11 +1572,7 @@ public class PhotoModule
@Override
public void onCountDownFinished() {
- if (mIsImageCaptureIntent) {
- mAppController.getCameraAppUI().transitionToIntentReviewLayout();
- } else {
- mAppController.getCameraAppUI().transitionToCapture();
- }
+ mAppController.getCameraAppUI().transitionToCapture();
mAppController.getCameraAppUI().showModeOptions();
if (mPaused) {
return;
@@ -1586,11 +1580,9 @@ public class PhotoModule
focusAndCapture();
}
- private void onResumeTasks() {
- if (mPaused) {
- return;
- }
- Log.v(TAG, "Executing onResumeTasks.");
+ @Override
+ public void resume() {
+ mPaused = false;
mCountdownSoundPlayer.loadSound(R.raw.timer_final_second);
mCountdownSoundPlayer.loadSound(R.raw.timer_increment);
@@ -1672,9 +1664,9 @@ public class PhotoModule
new FocusOverlayManager(mAppController, defaultFocusModes,
mCameraCapabilities, this, mMirror, mActivity.getMainLooper(),
mUI.getFocusUI());
- MotionManager motionManager = getServices().getMotionManager();
- if (motionManager != null) {
- motionManager.addListener(mFocusManager);
+ mMotionManager = getServices().getMotionManager();
+ if (mMotionManager != null) {
+ mMotionManager.addListener(mFocusManager);
}
}
mAppController.addPreviewAreaSizeChangedListener(mFocusManager);
@@ -1690,27 +1682,9 @@ public class PhotoModule
}
@Override
- public void resume() {
- mPaused = false;
-
- // 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.
- if (isResumeFromLockscreen()) {
- 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(mResumeTaskRunnable, ON_RESUME_TASKS_DELAY_MSEC);
- } else {
- Log.v(TAG, "On resume.");
- onResumeTasks();
- }
- }
-
- @Override
public void pause() {
+ Log.v(TAG, "pause");
mPaused = true;
- mHandler.removeCallbacks(mResumeTaskRunnable);
getServices().getRemoteShutterListener().onModuleExit();
SessionStatsCollector.instance().sessionActive(false);
@@ -1735,7 +1709,8 @@ public class PhotoModule
// (e.g. onResume -> onPause -> onResume).
stopPreview();
cancelCountDown();
- mCountdownSoundPlayer.release();
+ mCountdownSoundPlayer.unloadSound(R.raw.timer_final_second);
+ mCountdownSoundPlayer.unloadSound(R.raw.timer_increment);
mNamedImages = null;
// If we are in an image capture intent and has taken
@@ -1745,6 +1720,11 @@ public class PhotoModule
// Remove the messages and runnables in the queue.
mHandler.removeCallbacksAndMessages(null);
+ if (mMotionManager != null) {
+ mMotionManager.removeListener(mFocusManager);
+ mMotionManager = null;
+ }
+
closeCamera();
mActivity.enableKeepScreenOn(false);
mUI.onPause();
@@ -1763,7 +1743,7 @@ public class PhotoModule
@Override
public void destroy() {
- // TODO: implement this.
+ mCountdownSoundPlayer.release();
}
@Override
@@ -1889,7 +1869,6 @@ public class PhotoModule
stopFaceDetection();
mCameraDevice.setZoomChangeListener(null);
mCameraDevice.setFaceDetectionCallback(null, null);
- mCameraDevice.setErrorCallback(null, null);
mFaceDetectionStarted = false;
mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
@@ -1904,7 +1883,6 @@ public class PhotoModule
Characteristics info =
mActivity.getCameraProvider().getCharacteristics(mCameraId);
mDisplayOrientation = info.getPreviewOrientation(mDisplayRotation);
- mCameraDisplayOrientation = mDisplayOrientation;
mUI.setDisplayOrientation(mDisplayOrientation);
if (mFocusManager != null) {
mFocusManager.setDisplayOrientation(mDisplayOrientation);
@@ -1913,6 +1891,8 @@ public class PhotoModule
if (mCameraDevice != null) {
mCameraDevice.setDisplayOrientation(mDisplayRotation);
}
+ Log.v(TAG, "setDisplayOrientation (screen:preview) " +
+ mDisplayRotation + ":" + mDisplayOrientation);
}
/** Only called by UI thread. */
@@ -1962,7 +1942,6 @@ public class PhotoModule
return;
}
- mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
setDisplayOrientation();
if (!mSnapshotOnIdle) {
@@ -1974,10 +1953,15 @@ public class PhotoModule
}
mFocusManager.setAeAwbLock(false); // Unlock AE and AWB.
}
- setCameraParameters(UPDATE_PARAM_ALL);
+ // Nexus 4 must have picture size set to > 640x480 before other
+ // parameters are set in setCameraParameters, b/18227551. This call to
+ // updateParametersPictureSize should occur before setCameraParameters
+ // to address the issue.
updateParametersPictureSize();
+ setCameraParameters(UPDATE_PARAM_ALL);
+
mCameraDevice.setPreviewTexture(mActivity.getCameraAppUI().getSurfaceTexture());
Log.i(TAG, "startPreview");
@@ -2456,42 +2440,4 @@ public class PhotoModule
}
});
}
-
- /**
- * This class manages the loading/releasing/playing of the sounds needed for
- * countdown timer.
- */
- private class CountdownSoundPlayer {
- private SoundPool mSoundPool;
- private int mTimerIncrement;
- private int mTimerFinalSecond;
-
- void loadSounds() {
- // Load the sounds.
- if (mSoundPool == null) {
- mSoundPool = new SoundPool(1, AudioManager.STREAM_NOTIFICATION, 0);
- mTimerIncrement = mSoundPool.load(mAppController.getAndroidContext(), R.raw.timer_increment, 1);
- mTimerFinalSecond = mSoundPool.load(mAppController.getAndroidContext(), R.raw.timer_final_second, 1);
- }
- }
-
- void onRemainingSecondsChanged(int newVal) {
- if (mSoundPool == null) {
- Log.e(TAG, "Cannot play sound - they have not been loaded.");
- return;
- }
- if (newVal == 1) {
- mSoundPool.play(mTimerFinalSecond, 1.0f, 1.0f, 0, 0, 1.0f);
- } else if (newVal == 2 || newVal == 3) {
- mSoundPool.play(mTimerIncrement, 1.0f, 1.0f, 0, 0, 1.0f);
- }
- }
-
- void release() {
- if (mSoundPool != null) {
- mSoundPool.release();
- mSoundPool = null;
- }
- }
- }
}
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index b16a63f8f..a034a1c76 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -129,8 +129,6 @@ public class VideoModule extends CameraModule
private boolean mIsInReviewMode;
private boolean mSnapshotInProgress = false;
- private final CameraErrorCallback mErrorCallback = new CameraErrorCallback();
-
// Preference must be read before starting preview. We check this before starting
// preview.
private boolean mPreferenceRead;
@@ -604,6 +602,7 @@ public class VideoModule extends CameraModule
mCameraCapabilities.supports(CameraCapabilities.Feature.METERING_AREA);
}
readVideoPreferences();
+ updateDesiredPreviewSize();
resizeForPreviewAspectRatio();
initializeFocusManager();
// TODO: Having focus overlay manager caching the parameters is prone to error,
@@ -700,8 +699,14 @@ public class VideoModule extends CameraModule
boolean stop = mMediaRecorderRecording;
if (stop) {
+ // CameraAppUI mishandles mode option enable/disable
+ // for video, override that
+ mAppController.getCameraAppUI().enableModeOptions();
onStopVideoRecording();
} else {
+ // CameraAppUI mishandles mode option enable/disable
+ // for video, override that
+ mAppController.getCameraAppUI().disableModeOptions();
startVideoRecording();
}
mAppController.setShutterEnabled(false);
@@ -780,17 +785,28 @@ public class VideoModule extends CameraModule
}
mProfile = CamcorderProfile.get(mCameraId, quality);
mPreferenceRead = true;
+ }
+
+ /**
+ * Calculates and sets local class variables for Desired Preview sizes.
+ * This function should be called after every change in preview camera
+ * resolution and/or before the preview starts. Note that these values still
+ * need to be pushed to the CameraSettings to actually change the preview
+ * resolution. Does nothing when camera pointer is null.
+ */
+ private void updateDesiredPreviewSize() {
if (mCameraDevice == null) {
return;
}
+
mCameraSettings = mCameraDevice.getSettings();
Point desiredPreviewSize = getDesiredPreviewSize(mAppController.getAndroidContext(),
mCameraSettings, mCameraCapabilities, mProfile, mUI.getPreviewScreenSize());
mDesiredPreviewWidth = desiredPreviewSize.x;
mDesiredPreviewHeight = desiredPreviewSize.y;
mUI.setPreviewSize(mDesiredPreviewWidth, mDesiredPreviewHeight);
- Log.v(TAG, "mDesiredPreviewWidth=" + mDesiredPreviewWidth +
- ". mDesiredPreviewHeight=" + mDesiredPreviewHeight);
+ Log.v(TAG, "Updated DesiredPreview=" + mDesiredPreviewWidth + "x"
+ + mDesiredPreviewHeight);
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@@ -802,7 +818,9 @@ public class VideoModule extends CameraModule
* com.android.camera.cameradevice.CameraCapabilities#getPreferredPreviewSizeForVideo()}
* but also considers the current preview area size on screen and make sure
* the final preview size will not be smaller than 1/2 of the current
- * on screen preview area in terms of their short sides.</p>
+ * on screen preview area in terms of their short sides. This function has
+ * highest priority of WYSIWYG, 1:1 matching as its best match, even if
+ * there's a larger preview that meets the condition above. </p>
*
* @return The preferred preview size or {@code null} if the camera is not
* opened yet.
@@ -832,6 +850,19 @@ public class VideoModule extends CameraModule
it.remove();
}
}
+
+ // Take highest priority for WYSIWYG when the preview exactly matches
+ // video frame size. The variable sizes is assumed to be filtered
+ // for sizes beyond the UI size.
+ for (Size size : sizes) {
+ if (size.width() == profile.videoFrameWidth
+ && size.height() == profile.videoFrameHeight) {
+ Log.v(TAG, "Selected =" + size.width() + "x" + size.height()
+ + " on WYSIWYG Priority");
+ return new Point(profile.videoFrameWidth, profile.videoFrameHeight);
+ }
+ }
+
Size optimalSize = CameraUtil.getOptimalPreviewSize(context, sizes,
(double) profile.videoFrameWidth / profile.videoFrameHeight);
return new Point(optimalSize.width(), optimalSize.height());
@@ -911,7 +942,6 @@ public class VideoModule extends CameraModule
return;
}
- mCameraDevice.setErrorCallback(mHandler, mErrorCallback);
if (mPreviewing == true) {
stopPreview();
}
@@ -995,7 +1025,6 @@ public class VideoModule extends CameraModule
return;
}
mCameraDevice.setZoomChangeListener(null);
- mCameraDevice.setErrorCallback(null, null);
mActivity.getCameraProvider().releaseCamera(mCameraDevice.getCameraId());
mCameraDevice = null;
mPreviewing = false;
@@ -1607,6 +1636,9 @@ public class VideoModule extends CameraModule
private void setCameraParameters() {
SettingsManager settingsManager = mActivity.getSettingsManager();
+ // Update Desired Preview size in case video camera resolution has changed.
+ updateDesiredPreviewSize();
+
mCameraSettings.setPreviewSize(new Size(mDesiredPreviewWidth, mDesiredPreviewHeight));
// This is required for Samsung SGH-I337 and probably other Samsung S4 versions
if (Build.BRAND.toLowerCase().contains("samsung")) {
@@ -1641,7 +1673,7 @@ public class VideoModule extends CameraModule
// here we determine the picture size based on the preview size.
List<Size> supported = mCameraCapabilities.getSupportedPhotoSizes();
Size optimalSize = CameraUtil.getOptimalVideoSnapshotPictureSize(supported,
- (double) mDesiredPreviewWidth / mDesiredPreviewHeight);
+ mDesiredPreviewWidth, mDesiredPreviewHeight);
Size original = new Size(mCameraSettings.getCurrentPhotoSize());
if (!original.equals(optimalSize)) {
mCameraSettings.setPhotoSize(optimalSize);
diff --git a/src/com/android/camera/app/CameraApp.java b/src/com/android/camera/app/CameraApp.java
index 6c35c5358..9006183f9 100644
--- a/src/com/android/camera/app/CameraApp.java
+++ b/src/com/android/camera/app/CameraApp.java
@@ -19,7 +19,6 @@ package com.android.camera.app;
import android.app.Application;
import android.app.NotificationManager;
import android.content.Context;
-import android.os.Debug;
import com.android.camera.MediaSaverImpl;
import com.android.camera.debug.LogHelper;
@@ -41,15 +40,6 @@ import com.android.camera.util.UsageStatistics;
* to be used across modules.
*/
public class CameraApp extends Application implements CameraServices {
- /**
- * This is for debugging only: If set to true, application will not start
- * until a debugger is attached.
- * <p>
- * Use this if you need to debug code that is executed while the app starts
- * up and it would be too late to attach a debugger afterwards.
- */
- private static final boolean WAIT_FOR_DEBUGGER_ON_START = false;
-
private MediaSaver mMediaSaver;
private CaptureSessionManager mSessionManager;
private SessionStorageManager mSessionStorageManager;
@@ -63,10 +53,6 @@ public class CameraApp extends Application implements CameraServices {
public void onCreate() {
super.onCreate();
- if (WAIT_FOR_DEBUGGER_ON_START) {
- Debug.waitForDebugger();
- }
-
Context context = getApplicationContext();
LogHelper.initialize(context);
diff --git a/src/com/android/camera/app/CameraAppUI.java b/src/com/android/camera/app/CameraAppUI.java
index a1020d0bc..8d8b1e307 100644
--- a/src/com/android/camera/app/CameraAppUI.java
+++ b/src/com/android/camera/app/CameraAppUI.java
@@ -27,7 +27,6 @@ import android.graphics.SurfaceTexture;
import android.hardware.display.DisplayManager;
import android.util.CameraPerformanceTracker;
import android.view.GestureDetector;
-import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.TextureView;
@@ -511,8 +510,6 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
private TextureView mTextureView;
private FrameLayout mModuleUI;
private ShutterButton mShutterButton;
- private View mLetterBoxer1;
- private View mLetterBoxer2;
private BottomBar mBottomBar;
private ModeOptionsOverlay mModeOptionsOverlay;
private IndicatorIconController mIndicatorIconController;
@@ -549,6 +546,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
private boolean mAccessibilityEnabled;
private final View mAccessibilityAffordances;
+ private boolean mDisableAllUserInteractions;
/**
* Provides current preview frame and the controls/overlay from the module that
* are shown on top of the preview.
@@ -1027,7 +1025,9 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
@Override
public void run() {
mModeTransitionView.hideModeCover(null);
- showShimmyDelayed();
+ if (!mDisableAllUserInteractions) {
+ showShimmyDelayed();
+ }
}
};
mModeCoverState = COVER_SHOWN;
@@ -1234,9 +1234,6 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
addShutterListener(mModeOptionsOverlay);
addShutterListener(this);
- mLetterBoxer1 = mCameraRootView.findViewById(R.id.leftLetterBoxer1);
- mLetterBoxer2 = mCameraRootView.findViewById(R.id.leftLetterBoxer2);
-
mGridLines = (GridLines) mCameraRootView.findViewById(R.id.grid_lines);
mTextureViewHelper.addPreviewAreaSizeChangedListener(mGridLines);
@@ -1351,24 +1348,6 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
hideModeCover();
}
- /**
- * Set the mode options toggle clickable.
- */
- public void enableModeOptions() {
- /*
- * For modules using camera 1 api, this gets called in
- * onSurfaceTextureUpdated whenever the preview gets stopped and
- * started after each capture. This also takes care of the
- * case where the mode options might be unclickable when we
- * switch modes
- *
- * For modules using camera 2 api, they're required to call this
- * method when a capture is "completed". Unfortunately this differs
- * per module implementation.
- */
- mModeOptionsOverlay.setToggleClickable(true);
- }
-
@Override
public void onShutterButtonClick() {
/*
@@ -1380,7 +1359,7 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
* can clearly see when the toggle becomes clickable again,
* keep all of that logic at this level.
*/
- mModeOptionsOverlay.setToggleClickable(false);
+ disableModeOptions();
}
@Override
@@ -1394,6 +1373,47 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
}
/**
+ * Set the mode options toggle clickable.
+ */
+ public void enableModeOptions() {
+ /*
+ * For modules using camera 1 api, this gets called in
+ * onSurfaceTextureUpdated whenever the preview gets stopped and
+ * started after each capture. This also takes care of the
+ * case where the mode options might be unclickable when we
+ * switch modes
+ *
+ * For modules using camera 2 api, they're required to call this
+ * method when a capture is "completed". Unfortunately this differs
+ * per module implementation.
+ */
+ if (!mDisableAllUserInteractions) {
+ mModeOptionsOverlay.setToggleClickable(true);
+ }
+ }
+
+ /**
+ * Set the mode options toggle not clickable.
+ */
+ public void disableModeOptions() {
+ mModeOptionsOverlay.setToggleClickable(false);
+ }
+
+ public void setDisableAllUserInteractions(boolean disable) {
+ if (disable) {
+ disableModeOptions();
+ setShutterButtonEnabled(false);
+ setSwipeEnabled(false);
+ mModeListView.hideAnimated();
+ } else {
+ enableModeOptions();
+ setShutterButtonEnabled(true);
+ setSwipeEnabled(true);
+ }
+ mDisableAllUserInteractions = disable;
+ }
+
+ /**
* Gets called when a mode is selected from {@link com.android.camera.ui.ModeListView}
*
* @param modeIndex mode index of the selected mode
@@ -1715,13 +1735,14 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
}
public void setShutterButtonEnabled(final boolean enabled) {
- mBottomBar.post(new Runnable() {
-
- @Override
- public void run() {
- mBottomBar.setShutterButtonEnabled(enabled);
- }
- });
+ if (!mDisableAllUserInteractions) {
+ mBottomBar.post(new Runnable() {
+ @Override
+ public void run() {
+ mBottomBar.setShutterButtonEnabled(enabled);
+ }
+ });
+ }
}
public void setShutterButtonImportantToA11y(boolean important) {
@@ -1751,49 +1772,6 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
mShutterButton.addOnShutterButtonListener(listener);
}
-
- /**
- * This adds letterboxing around the preview, one on each side
- *
- * @param width the width in pixels of each letterboxing cover
- */
- public void addLetterboxing(int width) {
- FrameLayout.LayoutParams params1 = (FrameLayout.LayoutParams) mLetterBoxer1
- .getLayoutParams();
- FrameLayout.LayoutParams params2 = (FrameLayout.LayoutParams) mLetterBoxer2
- .getLayoutParams();
-
- if (mCameraRootView.getWidth() < mCameraRootView.getHeight()) {
- params1.width = width;
- params1.height = mCameraRootView.getHeight();
- params1.gravity = Gravity.LEFT;
- mLetterBoxer1.setVisibility(View.VISIBLE);
-
- params2.width = width;
- params2.height = mCameraRootView.getHeight();
- params2.gravity = Gravity.RIGHT;
- mLetterBoxer2.setVisibility(View.VISIBLE);
- } else {
- params1.height = width;
- params1.width = mCameraRootView.getWidth();
- params1.gravity = Gravity.TOP;
- mLetterBoxer1.setVisibility(View.VISIBLE);
-
- params2.height = width;
- params2.width = mCameraRootView.getWidth();
- params2.gravity = Gravity.BOTTOM;
- mLetterBoxer2.setVisibility(View.VISIBLE);
- }
- }
-
- /**
- * Remove the letter boxing strips if they happen to be present.
- */
- public void hideLetterboxing() {
- mLetterBoxer1.setVisibility(View.GONE);
- mLetterBoxer2.setVisibility(View.GONE);
- }
-
/**
* Remove a {@link #ShutterButton.OnShutterButtonListener} from the shutter button.
*/
@@ -1899,7 +1877,9 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
boolean isSamsung4k = mController.getSettingsManager().getBoolean(
SettingsManager.SCOPE_GLOBAL, Keys.KEY_VIDEOCAMERA_SAMSUNG4K_MODE);
if (bottomBarSpec.hideFlash || !flashBackCamera) {
+ // Hide both flash and torch button in flash disable logic
buttonManager.hideButton(ButtonManager.BUTTON_FLASH);
+ buttonManager.hideButton(ButtonManager.BUTTON_TORCH);
} else {
if (hardwareSpec.isFlashSupported()) {
if (bottomBarSpec.enableFlash) {
@@ -1916,11 +1896,15 @@ public class CameraAppUI implements ModeListView.ModeSwitchListener,
buttonManager.initializeButton(ButtonManager.BUTTON_HDR_PLUS_FLASH,
bottomBarSpec.flashCallback);
} else {
+ // Hide both flash and torch button in flash disable logic
buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
+ buttonManager.disableButton(ButtonManager.BUTTON_TORCH);
}
} else {
- // Disable flash icon if not supported by the hardware.
+ // Disable both flash and torch icon if not supported
+ // by the chosen camera hardware.
buttonManager.disableButton(ButtonManager.BUTTON_FLASH);
+ buttonManager.disableButton(ButtonManager.BUTTON_TORCH);
}
}
diff --git a/src/com/android/camera/app/CameraController.java b/src/com/android/camera/app/CameraController.java
index c87df91d5..c6f5d62e9 100644
--- a/src/com/android/camera/app/CameraController.java
+++ b/src/com/android/camera/app/CameraController.java
@@ -24,8 +24,8 @@ import com.android.camera.debug.Log;
import com.android.camera.util.CameraUtil;
import com.android.camera.util.GservicesHelper;
import com.android.ex.camera2.portability.CameraAgent;
-import com.android.ex.camera2.portability.CameraAgent.CameraExceptionCallback;
import com.android.ex.camera2.portability.CameraDeviceInfo;
+import com.android.ex.camera2.portability.CameraExceptionHandler;
/**
* A class which implements {@link com.android.camera.app.CameraProvider} used
@@ -83,11 +83,10 @@ public class CameraController implements CameraAgent.CameraOpenCallback, CameraP
}
@Override
- public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
- Handler handler) {
- mCameraAgent.setCameraDefaultExceptionCallback(callback, handler);
+ public void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler) {
+ mCameraAgent.setCameraExceptionHandler(exceptionHandler);
if (mCameraAgentNg != null) {
- mCameraAgentNg.setCameraDefaultExceptionCallback(callback, handler);
+ mCameraAgentNg.setCameraExceptionHandler(exceptionHandler);
}
}
@@ -161,7 +160,6 @@ public class CameraController implements CameraAgent.CameraOpenCallback, CameraP
public void onCameraOpened(CameraAgent.CameraProxy camera) {
Log.v(TAG, "onCameraOpened");
if (mRequestingCameraId != camera.getCameraId()) {
- // Not requesting any camera or not waiting for this one.
return;
}
mCameraProxy = camera;
@@ -283,10 +281,6 @@ public class CameraController implements CameraAgent.CameraOpenCallback, CameraP
* TODO: Make this method package private.
*/
public void closeCamera(boolean synced) {
- if (mCameraProxy == null) {
- Log.v(TAG, "No camera open, not closing");
- return;
- }
Log.v(TAG, "Closing camera");
mCameraProxy = null;
if (mUsingNewApi) {
diff --git a/src/com/android/camera/app/CameraProvider.java b/src/com/android/camera/app/CameraProvider.java
index 40c602f6c..9b98b672c 100644
--- a/src/com/android/camera/app/CameraProvider.java
+++ b/src/com/android/camera/app/CameraProvider.java
@@ -19,8 +19,8 @@ package com.android.camera.app;
import android.hardware.Camera;
import android.os.Handler;
-import com.android.ex.camera2.portability.CameraAgent.CameraExceptionCallback;
import com.android.ex.camera2.portability.CameraDeviceInfo.Characteristics;
+import com.android.ex.camera2.portability.CameraExceptionHandler;
/**
* An interface which defines the camera provider.
@@ -57,8 +57,7 @@ public interface CameraProvider {
* Sets a callback for handling camera api runtime exceptions on
* a handler.
*/
- public void setCameraDefaultExceptionCallback(CameraExceptionCallback callback,
- Handler handler);
+ public void setCameraExceptionHandler(CameraExceptionHandler exceptionHandler);
/**
* Get the {@link Characteristics} of the given camera.
diff --git a/src/com/android/camera/data/CameraDataAdapter.java b/src/com/android/camera/data/CameraDataAdapter.java
index 721b213bf..7fd2d09bc 100644
--- a/src/com/android/camera/data/CameraDataAdapter.java
+++ b/src/com/android/camera/data/CameraDataAdapter.java
@@ -23,6 +23,7 @@ import android.os.AsyncTask;
import android.view.View;
import com.android.camera.Storage;
+import com.android.camera.data.LocalData.ActionCallback;
import com.android.camera.debug.Log;
import com.android.camera.filmstrip.ImageData;
import com.android.camera.util.Callback;
@@ -125,14 +126,15 @@ public class CameraDataAdapter implements LocalDataAdapter {
}
@Override
- public View getView(Context context, View recycled, int dataID) {
+ public View getView(Context context, View recycled, int dataID,
+ ActionCallback actionCallback) {
if (dataID >= mImages.size() || dataID < 0) {
return null;
}
return mImages.get(dataID).getView(
context, recycled, mSuggestedWidth, mSuggestedHeight,
- mPlaceHolderResourceId, this, /* inProgress */ false);
+ mPlaceHolderResourceId, this, /* inProgress */ false, actionCallback);
}
@Override
@@ -178,7 +180,7 @@ public class CameraDataAdapter implements LocalDataAdapter {
int pos = findDataByContentUri(uri);
if (pos != -1) {
// a duplicate one, just do a substitute.
- Log.v(TAG, "found duplicate data");
+ Log.v(TAG, "found duplicate data: " + uri);
updateData(pos, newData);
return false;
} else {
@@ -337,6 +339,7 @@ public class CameraDataAdapter implements LocalDataAdapter {
@Override
protected List<LocalData> doInBackground(ContentResolver... contentResolvers) {
if (mMinPhotoId != LocalMediaData.QUERY_ALL_MEDIA_ID) {
+ Log.v(TAG, "updating media metadata with photos newer than id: " + mMinPhotoId);
final ContentResolver cr = contentResolvers[0];
return LocalMediaData.PhotoData.query(cr, LocalMediaData.PhotoData.CONTENT_URI,
mMinPhotoId);
@@ -346,11 +349,19 @@ public class CameraDataAdapter implements LocalDataAdapter {
@Override
protected void onPostExecute(List<LocalData> newPhotoData) {
+ if (newPhotoData == null) {
+ Log.w(TAG, "null data returned from new photos query");
+ return;
+ }
+ Log.v(TAG, "new photos query return num items: " + newPhotoData.size());
if (!newPhotoData.isEmpty()) {
LocalData newestPhoto = newPhotoData.get(0);
// We may overlap with another load task or a query task, in which case we want
// to be sure we never decrement the oldest seen id.
- mLastPhotoId = Math.max(mLastPhotoId, newestPhoto.getContentId());
+ long newLastPhotoId = newestPhoto.getContentId();
+ Log.v(TAG, "updating last photo id (old:new) " +
+ mLastPhotoId + ":" + newLastPhotoId);
+ mLastPhotoId = Math.max(mLastPhotoId, newLastPhotoId);
}
// We may add data that is already present, but if we do, it will be deduped in addData.
// addData does not dedupe session items, so we ignore them here
@@ -404,12 +415,27 @@ public class CameraDataAdapter implements LocalDataAdapter {
long lastPhotoId = LocalMediaData.QUERY_ALL_MEDIA_ID;
if (!photoData.isEmpty()) {
+ // This relies on {@link LocalMediaData.QUERY_ORDER} returning
+ // items sorted descending by ID, as such we can just pull the
+ // ID from the first item in the result to establish the last
+ // (max) photo ID.
lastPhotoId = photoData.get(0).getContentId();
}
- l.addAll(photoData);
- l.addAll(videoData);
+ if (photoData != null) {
+ Log.v(TAG, "retrieved photo metadata, number of items: " + photoData.size());
+ l.addAll(photoData);
+ }
+ if (videoData != null) {
+ Log.v(TAG, "retrieved video metadata, number of items: " + videoData.size());
+ l.addAll(videoData);
+ }
+ Log.v(TAG, "sorting video/photo metadata");
+ // Photos should be sorted within photo/video by ID, which in most
+ // cases should correlate well to the date taken/modified. This sort
+ // operation makes all photos/videos sorted by date in one list.
l.sort(new LocalData.NewestFirstComparator());
+ Log.v(TAG, "sorted video/photo metadata");
// Load enough metadata so it's already loaded when we open the filmstrip.
for (int i = 0; i < MAX_METADATA && i < l.size(); i++) {
diff --git a/src/com/android/camera/data/FixedFirstDataAdapter.java b/src/com/android/camera/data/FixedFirstDataAdapter.java
index 471fd19f9..f14a1c8a6 100644
--- a/src/com/android/camera/data/FixedFirstDataAdapter.java
+++ b/src/com/android/camera/data/FixedFirstDataAdapter.java
@@ -21,6 +21,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.view.View;
+import com.android.camera.data.LocalData.ActionCallback;
import com.android.camera.debug.Log;
import com.android.camera.filmstrip.DataAdapter;
import com.android.camera.filmstrip.ImageData;
@@ -110,12 +111,12 @@ public class FixedFirstDataAdapter extends AbstractLocalDataAdapterWrapper
}
@Override
- public View getView(Context context, View recycled, int dataID) {
+ public View getView(Context context, View recycled, int dataID, ActionCallback actionCallback) {
if (dataID == 0) {
- return mFirstData.getView(
- context, recycled, mSuggestedWidth, mSuggestedHeight, 0, null, false);
+ return mFirstData.getView(context, recycled, mSuggestedWidth, mSuggestedHeight, 0,
+ null, false, actionCallback);
}
- return mAdapter.getView(context, recycled, dataID - 1);
+ return mAdapter.getView(context, recycled, dataID - 1, actionCallback);
}
@Override
diff --git a/src/com/android/camera/data/FixedLastDataAdapter.java b/src/com/android/camera/data/FixedLastDataAdapter.java
index 35978d85b..a87a2dea4 100644
--- a/src/com/android/camera/data/FixedLastDataAdapter.java
+++ b/src/com/android/camera/data/FixedLastDataAdapter.java
@@ -21,6 +21,7 @@ import android.net.Uri;
import android.os.AsyncTask;
import android.view.View;
+import com.android.camera.data.LocalData.ActionCallback;
import com.android.camera.filmstrip.ImageData;
/**
@@ -66,7 +67,6 @@ public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper {
} else if (dataID == totalNumber) {
return mLastData;
}
-
return null;
}
@@ -112,16 +112,15 @@ public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper {
}
@Override
- public View getView(Context context, View recycled, int dataID) {
+ public View getView(Context context, View recycled, int dataID, ActionCallback actionCallback) {
int totalNumber = mAdapter.getTotalNumber();
if (dataID < totalNumber) {
- return mAdapter.getView(context, recycled, dataID);
+ return mAdapter.getView(context, recycled, dataID, actionCallback);
} else if (dataID == totalNumber) {
- return mLastData.getView(context, recycled,
- mSuggestedWidth, mSuggestedHeight, 0, null, false);
+ return mLastData.getView(context, recycled, mSuggestedWidth, mSuggestedHeight, 0, null,
+ false, actionCallback);
}
-
return null;
}
@@ -134,7 +133,6 @@ public class FixedLastDataAdapter extends AbstractLocalDataAdapterWrapper {
} else if (dataId == totalNumber) {
return mLastData.getItemViewType().ordinal();
}
-
return -1;
}
diff --git a/src/com/android/camera/data/LocalData.java b/src/com/android/camera/data/LocalData.java
index df2d9c220..d42bf9799 100644
--- a/src/com/android/camera/data/LocalData.java
+++ b/src/com/android/camera/data/LocalData.java
@@ -17,8 +17,10 @@
package com.android.camera.data;
import android.content.Context;
+import android.net.Uri;
import android.os.Bundle;
import android.view.View;
+
import com.android.camera.debug.Log;
import com.android.camera.filmstrip.ImageData;
@@ -32,6 +34,14 @@ import java.util.Comparator;
* can guarantee thread safety for LocalData.
*/
public interface LocalData extends ImageData {
+ /**
+ * An action callback to be used for actions on the local media data items.
+ */
+ public static interface ActionCallback {
+ /** Plays the video with the given URI and title. */
+ public void playVideo(Uri uri, String title);
+ }
+
static final Log.Tag TAG = new Log.Tag("LocalData");
public static final String MIME_TYPE_JPEG = "image/jpeg";
@@ -79,7 +89,8 @@ public interface LocalData extends ImageData {
* @param adapter Data adapter for this data item.
*/
View getView(Context context, View recycled, int thumbWidth, int thumbHeight,
- int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgress);
+ int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgress,
+ ActionCallback actionCallback);
/** Returns a unique identifier for the view created by this data so that the view
* can be reused.
diff --git a/src/com/android/camera/data/LocalDataUtil.java b/src/com/android/camera/data/LocalDataUtil.java
index f029f456a..00ace49ad 100644
--- a/src/com/android/camera/data/LocalDataUtil.java
+++ b/src/com/android/camera/data/LocalDataUtil.java
@@ -43,7 +43,7 @@ public class LocalDataUtil {
* @return Whether the MIME is a video type.
*/
public static boolean isMimeTypeVideo(String mimeType) {
- return mimeType.startsWith("video/");
+ return mimeType != null && mimeType.startsWith("video/");
}
/**
@@ -51,7 +51,7 @@ public class LocalDataUtil {
* @return Whether the MIME is a image type.
*/
public static boolean isMimeTypeImage(String mimeType) {
- return mimeType.startsWith("image/");
+ return mimeType != null && mimeType.startsWith("image/");
}
/**
diff --git a/src/com/android/camera/data/LocalMediaData.java b/src/com/android/camera/data/LocalMediaData.java
index cb5ba41ae..83399e695 100644
--- a/src/com/android/camera/data/LocalMediaData.java
+++ b/src/com/android/camera/data/LocalMediaData.java
@@ -16,7 +16,6 @@
package com.android.camera.data;
-import android.app.Activity;
import android.content.ContentResolver;
import android.content.Context;
import android.database.Cursor;
@@ -32,7 +31,6 @@ import android.widget.ImageView;
import com.android.camera.Storage;
import com.android.camera.debug.Log;
-import com.android.camera.util.CameraUtil;
import com.android.camera2.R;
import com.bumptech.glide.BitmapRequestBuilder;
import com.bumptech.glide.Glide;
@@ -219,7 +217,8 @@ public abstract class LocalMediaData implements LocalData {
@Override
public View getView(Context context, View recycled, int thumbWidth, int thumbHeight,
- int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgress) {
+ int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgress,
+ ActionCallback actionCallback) {
final ImageView imageView;
if (recycled != null) {
imageView = (ImageView) recycled;
@@ -334,8 +333,10 @@ public abstract class LocalMediaData implements LocalData {
static final Uri CONTENT_URI = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- private static final String QUERY_ORDER = MediaStore.Images.ImageColumns.DATE_TAKEN + " DESC, "
- + MediaStore.Images.ImageColumns._ID + " DESC";
+ // Sort all data by ID. This must be aligned with
+ // {@link CameraDataAdapter.QueryTask} which relies on the highest ID
+ // being first in any data returned.
+ private static final String QUERY_ORDER = MediaStore.Images.ImageColumns._ID + " DESC";
/**
* These values should be kept in sync with column IDs (COL_*) above.
*/
@@ -536,32 +537,50 @@ public abstract class LocalMediaData implements LocalData {
return;
}
- BitmapRequestBuilder<Uri, Bitmap> request = Glide.with(context)
- .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, mOrientation)
- .asBitmap()
- .encoder(JPEG_ENCODER)
- .placeholder(placeHolderResourceId)
- .fitCenter();
+ final int overrideWidth;
+ final int overrideHeight;
+ final BitmapRequestBuilder<Uri, Bitmap> thumbnailRequest;
if (full) {
- request.thumbnail(Glide.with(context)
- .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds,
- mOrientation)
- .asBitmap()
- .encoder(JPEG_ENCODER)
- .override(thumbWidth, thumbHeight)
- .fitCenter())
- .override(Math.min(getWidth(), MAXIMUM_TEXTURE_SIZE),
- Math.min(getHeight(), MAXIMUM_TEXTURE_SIZE));
+ // Load up to the maximum size Bitmap we can render.
+ overrideWidth = Math.min(getWidth(), MAXIMUM_TEXTURE_SIZE);
+ overrideHeight = Math.min(getHeight(), MAXIMUM_TEXTURE_SIZE);
+
+ // Load two thumbnails, first the small low quality thumb from the media store,
+ // then a medium quality thumbWidth/thumbHeight image. Using two thumbnails ensures
+ // we don't flicker to grey while we load the maximum size image.
+ thumbnailRequest = loadUri(context)
+ .override(thumbWidth, thumbHeight)
+ .fitCenter()
+ .thumbnail(loadMediaStoreThumb(context));
} else {
- request.thumbnail(Glide.with(context)
- .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds,
- mOrientation)
- .asBitmap()
- .encoder(JPEG_ENCODER)
- .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT))
- .override(thumbWidth, thumbHeight);
+ // Load a medium quality thumbWidth/thumbHeight image.
+ overrideWidth = thumbWidth;
+ overrideHeight = thumbHeight;
+
+ // Load a single small low quality thumbnail from the media store.
+ thumbnailRequest = loadMediaStoreThumb(context);
}
- request.into(imageView);
+
+ loadUri(context)
+ .placeholder(placeHolderResourceId)
+ .fitCenter()
+ .override(overrideWidth, overrideHeight)
+ .thumbnail(thumbnailRequest)
+ .into(imageView);
+ }
+
+ /** Loads a thumbnail with a size targeted to use MediaStore.Images.Thumbnails. */
+ private BitmapRequestBuilder<Uri, Bitmap> loadMediaStoreThumb(Context context) {
+ return loadUri(context)
+ .override(MEDIASTORE_THUMB_WIDTH, MEDIASTORE_THUMB_HEIGHT);
+ }
+
+ /** Loads an image using a MediaStore Uri with our default options. */
+ private BitmapRequestBuilder<Uri, Bitmap> loadUri(Context context) {
+ return Glide.with(context)
+ .loadFromMediaStore(getUri(), mMimeType, mDateModifiedInSeconds, mOrientation)
+ .asBitmap()
+ .encoder(JPEG_ENCODER);
}
@Override
@@ -824,7 +843,8 @@ public abstract class LocalMediaData implements LocalData {
@Override
public View getView(final Context context, View recycled,
int thumbWidth, int thumbHeight, int placeHolderResourceId,
- LocalDataAdapter adapter, boolean isInProgress) {
+ LocalDataAdapter adapter, boolean isInProgress,
+ final ActionCallback actionCallback) {
final VideoViewHolder viewHolder;
final View result;
@@ -847,9 +867,7 @@ public abstract class LocalMediaData implements LocalData {
viewHolder.mPlayButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- // TODO: refactor this into activities to avoid this class
- // conversion.
- CameraUtil.playVideo((Activity) context, getUri(), mTitle);
+ actionCallback.playVideo(getUri(), mTitle);
}
});
diff --git a/src/com/android/camera/data/LocalSessionData.java b/src/com/android/camera/data/LocalSessionData.java
index 8254baaa9..8a5d6c6f3 100644
--- a/src/com/android/camera/data/LocalSessionData.java
+++ b/src/com/android/camera/data/LocalSessionData.java
@@ -26,7 +26,6 @@ import android.widget.ImageView;
import com.android.camera.Storage;
import com.android.camera2.R;
import com.bumptech.glide.Glide;
-import com.bumptech.glide.load.DecodeFormat;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@@ -37,9 +36,9 @@ import java.util.concurrent.TimeUnit;
*/
public class LocalSessionData implements LocalData {
- private Uri mUri;
+ private final Uri mUri;
// Units are GMT epoch milliseconds.
- private long mDateTaken;
+ private final long mDateTaken;
protected final Bundle mMetaData;
private int mWidth;
private int mHeight;
@@ -59,7 +58,8 @@ public class LocalSessionData implements LocalData {
@Override
public View getView(Context context, View recycled, int thumbWidth, int thumbHeight,
- int placeholderResourcedId, LocalDataAdapter adapter, boolean isInProgress) {
+ int placeholderResourcedId, LocalDataAdapter adapter, boolean isInProgress,
+ ActionCallback actionCallback) {
final ImageView imageView;
if (recycled != null) {
imageView = (ImageView) recycled;
diff --git a/src/com/android/camera/data/SimpleViewData.java b/src/com/android/camera/data/SimpleViewData.java
index 74f1073c3..3b6095825 100644
--- a/src/com/android/camera/data/SimpleViewData.java
+++ b/src/com/android/camera/data/SimpleViewData.java
@@ -17,7 +17,6 @@
package com.android.camera.data;
import android.content.Context;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.view.View;
@@ -135,8 +134,9 @@ public class SimpleViewData implements LocalData {
}
@Override
- public View getView(Context context, View recycled, int width, int height, int placeHolderResourceId,
- LocalDataAdapter adapter, boolean isInProgressSession) {
+ public View getView(Context context, View recycled, int width, int height,
+ int placeHolderResourceId, LocalDataAdapter adapter, boolean isInProgressSession,
+ ActionCallback actionCallback) {
return mView;
}
diff --git a/src/com/android/camera/debug/DebugCameraProxy.java b/src/com/android/camera/debug/DebugCameraProxy.java
index cf6952cb6..05e8229e4 100644
--- a/src/com/android/camera/debug/DebugCameraProxy.java
+++ b/src/com/android/camera/debug/DebugCameraProxy.java
@@ -66,6 +66,12 @@ public class DebugCameraProxy extends CameraAgent.CameraProxy {
}
@Override
+ public CameraAgent getAgent() {
+ log("getAgent");
+ return mProxy.getAgent();
+ }
+
+ @Override
public CameraCapabilities getCapabilities() {
log("getCapabilities");
return mProxy.getCapabilities();
@@ -210,12 +216,6 @@ public class DebugCameraProxy extends CameraAgent.CameraProxy {
}
@Override
- public void setErrorCallback(Handler handler, CameraAgent.CameraErrorCallback cb) {
- log("setErrorCallback");
- mProxy.setErrorCallback(handler, cb);
- }
-
- @Override
public void setParameters(Camera.Parameters params) {
log("setParameters");
mProxy.setParameters(params);
diff --git a/src/com/android/camera/debug/Log.java b/src/com/android/camera/debug/Log.java
index b6a8e8c68..1b3c98c55 100644
--- a/src/com/android/camera/debug/Log.java
+++ b/src/com/android/camera/debug/Log.java
@@ -16,6 +16,10 @@
package com.android.camera.debug;
+import android.os.Build;
+
+import com.android.camera.util.ReleaseHelper;
+
public class Log {
/**
* All Camera logging using this class will use this tag prefix.
@@ -123,13 +127,22 @@ public class Log {
// than the desired output level. This applies to all tags.
return LogHelper.getOverrideLevel() <= level;
} else {
- // The prefix can be used as an override tag to see all camera logs
- return android.util.Log.isLoggable(CAMERA_LOGTAG_PREFIX, level)
- || android.util.Log.isLoggable(tag.toString(), level);
+ return ReleaseHelper.shouldLogVerbose() ||
+ isDebugOsBuild() || shouldLog(tag, level);
}
} catch (IllegalArgumentException ex) {
e(TAG, "Tag too long:" + tag);
return false;
}
}
+
+ private static boolean shouldLog(Tag tag, int level) {
+ // The prefix can be used as an override tag to see all camera logs
+ return android.util.Log.isLoggable(CAMERA_LOGTAG_PREFIX, level)
+ || android.util.Log.isLoggable(tag.toString(), level);
+ }
+
+ private static boolean isDebugOsBuild() {
+ return "userdebug".equals(Build.TYPE) || "eng".equals(Build.TYPE);
+ }
}
diff --git a/src/com/android/camera/filmstrip/DataAdapter.java b/src/com/android/camera/filmstrip/DataAdapter.java
index e1ca61dea..65ae48a3a 100644
--- a/src/com/android/camera/filmstrip/DataAdapter.java
+++ b/src/com/android/camera/filmstrip/DataAdapter.java
@@ -19,6 +19,8 @@ package com.android.camera.filmstrip;
import android.content.Context;
import android.view.View;
+import com.android.camera.data.LocalData.ActionCallback;
+
/**
* An interface which defines the interactions between the
* {@link ImageData} and the
@@ -85,7 +87,7 @@ public interface DataAdapter {
* @return The view representing the image data. Null if unavailable or
* the {@code dataID} is out of range.
*/
- public View getView(Context context, View recycled, int dataID);
+ public View getView(Context context, View recycled, int dataID, ActionCallback actionCallback);
/** Returns a unique identifier for the view created by this data so that the view
* can be reused.
diff --git a/src/com/android/camera/one/OneCamera.java b/src/com/android/camera/one/OneCamera.java
index 593dea4cd..743be3726 100644
--- a/src/com/android/camera/one/OneCamera.java
+++ b/src/com/android/camera/one/OneCamera.java
@@ -17,7 +17,6 @@
package com.android.camera.one;
import android.content.Context;
-import android.graphics.Bitmap;
import android.location.Location;
import android.net.Uri;
import android.view.Surface;
@@ -159,7 +158,7 @@ public interface OneCamera {
* Called when a thumbnail image is provided before the final image is
* finished.
*/
- public void onThumbnailResult(Bitmap bitmap);
+ public void onThumbnailResult(byte[] jpegData);
/**
* Called when the final picture is done taking
@@ -240,6 +239,10 @@ public interface OneCamera {
public Flash flashMode = Flash.AUTO;
/** The location of this capture. */
public Location location = null;
+ /** Zoom value. */
+ public float zoom = 1f;
+ /** Timer duration in seconds or null for no timer. */
+ public Float timerSeconds = null;
/** Set this to provide a debug folder for this capture. */
public File debugDataFolder;
diff --git a/src/com/android/camera/one/OneCameraException.java b/src/com/android/camera/one/OneCameraException.java
new file mode 100644
index 000000000..154acbe65
--- /dev/null
+++ b/src/com/android/camera/one/OneCameraException.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2014 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.one;
+
+/**
+ * Used for exceptions thrown by OneCamera API. Use this for severe,
+ * irrecoverable errors only.
+ */
+public class OneCameraException extends Exception {
+ private static final long serialVersionUID = 1L;
+
+ public OneCameraException(String message) {
+ super(message);
+ }
+}
diff --git a/src/com/android/camera/one/OneCameraManager.java b/src/com/android/camera/one/OneCameraManager.java
index ee505ae34..607e6a077 100644
--- a/src/com/android/camera/one/OneCameraManager.java
+++ b/src/com/android/camera/one/OneCameraManager.java
@@ -20,7 +20,6 @@ import android.content.Context;
import android.hardware.camera2.CameraAccessException;
import android.hardware.camera2.CameraCharacteristics;
import android.hardware.camera2.CameraManager;
-import android.os.Build;
import android.os.Handler;
import android.util.DisplayMetrics;
import android.view.WindowManager;
@@ -30,6 +29,7 @@ import com.android.camera.debug.Log;
import com.android.camera.debug.Log.Tag;
import com.android.camera.one.OneCamera.Facing;
import com.android.camera.one.OneCamera.OpenCallback;
+import com.android.camera.util.ApiHelper;
import com.android.camera.util.Size;
/**
@@ -66,24 +66,37 @@ public abstract class OneCameraManager {
/**
* Creates a camera manager that is based on Camera2 API, if available, or
* otherwise uses the portability layer API.
+ *
+ * @throws OneCameraException Thrown if an error occurred while trying to
+ * access the camera.
*/
- public static OneCameraManager get(CameraActivity activity) {
+ public static OneCameraManager get(CameraActivity activity) throws OneCameraException {
return create(activity);
}
/**
* Creates a new camera manager that is based on Camera2 API, if available,
* or otherwise uses the portability API.
+ *
+ * @throws OneCameraException Thrown if an error occurred while trying to
+ * access the camera.
*/
- private static OneCameraManager create(CameraActivity activity) {
+ private static OneCameraManager create(CameraActivity activity) throws OneCameraException {
DisplayMetrics displayMetrics = getDisplayMetrics(activity);
- CameraManager cameraManager = (CameraManager) activity
- .getSystemService(Context.CAMERA_SERVICE);
- int maxMemoryMB = activity.getServices().getMemoryManager()
- .getMaxAllowedNativeMemoryAllocation();
+ CameraManager cameraManager = null;
+
+ try {
+ cameraManager = ApiHelper.HAS_CAMERA_2_API ? (CameraManager) activity
+ .getSystemService(Context.CAMERA_SERVICE) : null;
+ } catch (IllegalStateException ex) {
+ cameraManager = null;
+ Log.e(TAG, "Could not get camera service v2", ex);
+ }
if (cameraManager != null && isCamera2Supported(cameraManager)) {
+ int maxMemoryMB = activity.getServices().getMemoryManager()
+ .getMaxAllowedNativeMemoryAllocation();
return new com.android.camera.one.v2.OneCameraManagerImpl(
- activity.getApplicationContext(), cameraManager, maxMemoryMB,
+ activity.getAndroidContext(), cameraManager, maxMemoryMB,
displayMetrics, activity.getSoundPlayer());
} else {
return new com.android.camera.one.v1.OneCameraManagerImpl();
@@ -98,13 +111,20 @@ public abstract class OneCameraManager {
* HAL (such as the Nexus 4, 7 or 10), this method returns false. It
* only returns true, if Camera2 is fully supported through newer
* HALs.
+ * @throws OneCameraException Thrown if an error occurred while trying to
+ * access the camera.
*/
- private static boolean isCamera2Supported(CameraManager cameraManager) {
- if (Build.VERSION.SDK_INT < 21) {
+ private static boolean isCamera2Supported(CameraManager cameraManager)
+ throws OneCameraException {
+ if (!ApiHelper.HAS_CAMERA_2_API) {
return false;
}
try {
- final String id = cameraManager.getCameraIdList()[0];
+ String[] cameraIds = cameraManager.getCameraIdList();
+ if (cameraIds.length == 0) {
+ throw new OneCameraException("Camera 2 API supported but no devices available.");
+ }
+ final String id = cameraIds[0];
// TODO: We should check for all the flags we need to ensure the
// device is capable of taking Camera2 API shots. For now, let's
// accept all device that are either 'partial' or 'full' devices
diff --git a/src/com/android/camera/one/Settings3A.java b/src/com/android/camera/one/Settings3A.java
index a8abf69cc..cdfaa524e 100644
--- a/src/com/android/camera/one/Settings3A.java
+++ b/src/com/android/camera/one/Settings3A.java
@@ -71,6 +71,12 @@ public class Settings3A {
private static final int FOCUS_HOLD_MILLIS = 3000;
/**
+ * The number of milliseconds to hold tap-to-expose/metering after the
+ * last payload frame is received before returning to continuous 3A.
+ */
+ private static final int GCAM_POST_SHOT_FOCUS_HOLD_MILLIS = 1000;
+
+ /**
* Width of touch metering region in [0,1] relative to shorter edge of the
* current crop region. Multiply this number by the number of pixels along
* shorter edge of the current crop region's width to get a value in pixels.
@@ -98,7 +104,7 @@ public class Settings3A {
* Was fixed at 15.0f prior to L release.
* </p>
*/
- private static final float GCAM_METERING_REGION_WEIGHT = 22.0f;
+ private static final float GCAM_METERING_REGION_WEIGHT = 45.0f;
public static float getAutoFocusRegionWidth() {
return AF_REGION_BOX;
@@ -123,4 +129,8 @@ public class Settings3A {
public static int getFocusHoldMillis() {
return FOCUS_HOLD_MILLIS;
}
+
+ public static int getGcamPostShotFocusHoldMillis() {
+ return GCAM_POST_SHOT_FOCUS_HOLD_MILLIS;
+ }
}
diff --git a/src/com/android/camera/one/v2/AutoFocusHelper.java b/src/com/android/camera/one/v2/AutoFocusHelper.java
index 8cfab6806..6416d7034 100644
--- a/src/com/android/camera/one/v2/AutoFocusHelper.java
+++ b/src/com/android/camera/one/v2/AutoFocusHelper.java
@@ -122,10 +122,9 @@ public class AutoFocusHelper {
* @param cropRegion Crop region of the image.
* @param sensorOrientation sensor orientation as defined by
* CameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION).
- *
- * */
- private static MeteringRectangle[] regionsForNormalizedCoord(float nx, float ny, float fraction,
- final Rect cropRegion, int sensorOrientation) {
+ */
+ private static MeteringRectangle[] regionsForNormalizedCoord(float nx, float ny,
+ float fraction, final Rect cropRegion, int sensorOrientation) {
// Compute half side length in pixels.
int minCropEdge = Math.min(cropRegion.width(), cropRegion.height());
int halfSideLength = (int) (0.5f * fraction * minCropEdge);
@@ -188,6 +187,22 @@ public class AutoFocusHelper {
}
/**
+ * [Gcam mode only]: Return AE region(s) for a sensor-referenced touch coordinate.
+ *
+ * <p>
+ * Normalized coordinates are referenced to portrait preview window with
+ * (0, 0) top left and (1, 1) bottom right. Rotation has no effect.
+ * </p>
+ *
+ * @return AE region(s).
+ */
+ public static MeteringRectangle[] gcamAERegionsForNormalizedCoord(float nx,
+ float ny, final Rect cropRegion, int sensorOrientation) {
+ return regionsForNormalizedCoord(nx, ny, Settings3A.getGcamMeteringRegionFraction(),
+ cropRegion, sensorOrientation);
+ }
+
+ /**
* Calculates sensor crop region for a zoom level (zoom >= 1.0).
*
* @return Crop region.
diff --git a/src/com/android/camera/one/v2/ImageCaptureManager.java b/src/com/android/camera/one/v2/ImageCaptureManager.java
index d84d95712..068707149 100644
--- a/src/com/android/camera/one/v2/ImageCaptureManager.java
+++ b/src/com/android/camera/one/v2/ImageCaptureManager.java
@@ -48,7 +48,7 @@ import java.util.concurrent.atomic.AtomicInteger;
/**
* Implements {@link android.media.ImageReader.OnImageAvailableListener} and
- * {@link android.hardware.camera2.CameraCaptureSession.CaptureCallback} to
+ * {@link android.hardware.camera2.CameraCaptureSession.CaptureListener} to
* store the results of capture requests (both {@link Image}s and
* {@link TotalCaptureResult}s in a ring-buffer from which they may be saved.
* <br>
@@ -276,7 +276,7 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
/**
* The set of constraints which must be satisfied for a newly acquired image
- * to be captured and sent to {@link #mPendingImageCaptureListener}. null if
+ * to be captured and sent to {@link #mPendingImageCaptureCallback}. null if
* there is no pending capture request.
*/
private List<ImageCaptureManager.CapturedImageConstraint> mPendingImageCaptureConstraints;
@@ -286,7 +286,7 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
* image which satisfies {@link #mPendingImageCaptureConstraints}. null if
* there is no pending capture request.
*/
- private ImageCaptureManager.ImageCaptureListener mPendingImageCaptureListener;
+ private ImageCaptureManager.ImageCaptureListener mPendingImageCaptureCallback;
/**
* Map from CaptureResult key to the frame number of the capture result
@@ -310,11 +310,11 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
* that this should probably be on a different thread than the
* one used for camera operations, such as capture requests and
* OnImageAvailable listeners, to avoid stalling the preview.
- * @param imageCaptureCallbackExecutor the executor on which to invoke image
+ * @param imageCaptureListenerExecutor the executor on which to invoke image
* capture listeners, {@link ImageCaptureListener}.
*/
ImageCaptureManager(int maxImages, Handler listenerHandler,
- Executor imageCaptureCallbackExecutor) {
+ Executor imageCaptureListenerExecutor) {
// Ensure that there are always 2 images available for the framework to
// continue processing frames.
// TODO Could we make this tighter?
@@ -322,7 +322,7 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
maxImages - 2);
mListenerHandler = listenerHandler;
- mImageCaptureListenerExecutor = imageCaptureCallbackExecutor;
+ mImageCaptureListenerExecutor = imageCaptureListenerExecutor;
}
/**
@@ -551,7 +551,7 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
*/
public void captureNextImage(final ImageCaptureListener onImageCaptured,
final List<CapturedImageConstraint> constraints) {
- mPendingImageCaptureListener = onImageCaptured;
+ mPendingImageCaptureCallback = onImageCaptured;
mPendingImageCaptureConstraints = constraints;
}
@@ -562,7 +562,7 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
* should be captured if appropriate and possible.
*/
private void tryExecutePendingCaptureRequest(long newImageTimestamp) {
- if (mPendingImageCaptureListener != null) {
+ if (mPendingImageCaptureCallback != null) {
final Pair<Long, CapturedImage> pinnedImage = mCapturedImageBuffer.tryPin(
newImageTimestamp);
if (pinnedImage != null) {
@@ -588,11 +588,11 @@ public class ImageCaptureManager extends CameraCaptureSession.CaptureCallback im
// If we get here, the image satisfies all the necessary
// constraints.
- if (tryExecuteCaptureOrRelease(pinnedImage, mPendingImageCaptureListener)) {
+ if (tryExecuteCaptureOrRelease(pinnedImage, mPendingImageCaptureCallback)) {
// If we successfully handed the image off to the callback,
// remove the pending
// capture request.
- mPendingImageCaptureListener = null;
+ mPendingImageCaptureCallback = null;
mPendingImageCaptureConstraints = null;
}
}
diff --git a/src/com/android/camera/settings/AppUpgrader.java b/src/com/android/camera/settings/AppUpgrader.java
index 2e60e70be..e94c2d100 100644
--- a/src/com/android/camera/settings/AppUpgrader.java
+++ b/src/com/android/camera/settings/AppUpgrader.java
@@ -43,6 +43,7 @@ public class AppUpgrader extends SettingsUpgrader {
private static final String OLD_CAMERA_PREFERENCES_PREFIX = "_preferences_";
private static final String OLD_MODULE_PREFERENCES_PREFIX = "_preferences_module_";
private static final String OLD_GLOBAL_PREFERENCES_FILENAME = "_preferences_camera";
+ private static final String OLD_KEY_UPGRADE_VERSION = "pref_strict_upgrade_version";
/**
* With this version everyone was forced to choose their location settings
@@ -60,8 +61,13 @@ public class AppUpgrader extends SettingsUpgrader {
/**
* With this version, the names of the files storing camera specific and
* module specific settings changed.
+ * <p>
+ * NOTE: changed this from 4 to 6 to re-run on latest Glacier upgrade.
+ * Initial upgraders to Glacier will run conversion once as of the change.
+ * When re-run for early dogfooders, values will get overwritten but will
+ * all work.
*/
- private static final int CAMERA_MODULE_SETTINGS_FILES_RENAMED_VERSION = 4;
+ private static final int CAMERA_MODULE_SETTINGS_FILES_RENAMED_VERSION = 6;
/**
* With this version, timelapse mode was removed and mode indices need to be
@@ -80,6 +86,12 @@ public class AppUpgrader extends SettingsUpgrader {
private static final int CAMERA_SETTINGS_MAX_BRIGHTNESS = 7;
/**
+ * With this version internal storage is changed to use only Strings, and
+ * a type conversion process should execute.
+ */
+ private static final int CAMERA_SETTINGS_STRINGS_UPGRADE = 5;
+
+ /**
* Increment this value whenever new AOSP UpgradeSteps need to be executed.
*/
public static final int APP_UPGRADE_VERSION = 7;
@@ -93,28 +105,35 @@ public class AppUpgrader extends SettingsUpgrader {
@Override
protected int getLastVersion(SettingsManager settingsManager) {
- // Prior appwide versions were stored in the default preferences. If
- // current
- // state indicates this is still the case, port the version and then
- // process
- // all other known app settings to the new SettingsManager string
- // scheme.
- try {
- return super.getLastVersion(settingsManager);
- } catch (ClassCastException e) {
- // We infer that a ClassCastException here means we have pre-String
- // settings that need to be upgraded, so we hack in a full upgrade
- // here.
- upgradeTypesToStrings(settingsManager);
- // Retrieve version as default now that we're sure it is converted
- return super.getLastVersion(settingsManager);
+ // Prior upgrade versions were stored in the default preferences as int
+ // and String. We create a new version location for migration to String.
+ // If we don't have a version persisted in the new location, check for
+ // the prior value from the old location. We expect the old value to be
+ // processed during {@link #upgradeTypesToStrings}.
+ SharedPreferences defaultPreferences = settingsManager.getDefaultPreferences();
+ if (defaultPreferences.contains(OLD_KEY_UPGRADE_VERSION)) {
+ Map<String, ?> allPrefs = defaultPreferences.getAll();
+ Object oldVersion = allPrefs.get(OLD_KEY_UPGRADE_VERSION);
+ defaultPreferences.edit().remove(OLD_KEY_UPGRADE_VERSION).apply();
+ if (oldVersion instanceof Integer) {
+ return (Integer) oldVersion;
+ } else if (oldVersion instanceof String) {
+ return SettingsManager.convertToInt((String) oldVersion);
+ }
}
+ return super.getLastVersion(settingsManager);
}
@Override
public void upgrade(SettingsManager settingsManager, int lastVersion, int currentVersion) {
Context context = mAppController.getAndroidContext();
+ // Do strings upgrade first before 'earlier' upgrades, since they assume
+ // valid storage of values.
+ if (lastVersion < CAMERA_SETTINGS_STRINGS_UPGRADE) {
+ upgradeTypesToStrings(settingsManager);
+ }
+
if (lastVersion < FORCE_LOCATION_CHOICE_VERSION) {
forceLocationChoice(settingsManager);
}
@@ -166,11 +185,6 @@ public class AppUpgrader extends SettingsUpgrader {
SharedPreferences oldGlobalPreferences =
settingsManager.openPreferences(OLD_GLOBAL_PREFERENCES_FILENAME);
- // Strict upgrade version: Integer -> String, from default.
- int strictUpgradeVersion = removeInteger(defaultPreferences, Keys.KEY_UPGRADE_VERSION);
- settingsManager.set(SettingsManager.SCOPE_GLOBAL, Keys.KEY_UPGRADE_VERSION,
- strictUpgradeVersion);
-
// Location: boolean -> String, from default.
if (defaultPreferences.contains(Keys.KEY_RECORD_LOCATION)) {
boolean location = removeBoolean(defaultPreferences, Keys.KEY_RECORD_LOCATION);
@@ -340,6 +354,11 @@ public class AppUpgrader extends SettingsUpgrader {
* SharedPreferences file to another SharedPreferences file, as Strings.
* Settings that are not a known supported format (int/boolean/String)
* are dropped with warning.
+ *
+ * This will normally be run only once but was used both for upgrade version
+ * 4 and 6 -- in 6 we repair issues with previous runs of the upgrader. So
+ * we make sure to remove entries from destination if the source isn't valid
+ * like a null or unsupported type.
*/
private void copyPreferences(SharedPreferences oldPrefs,
SharedPreferences newPrefs) {
@@ -348,7 +367,8 @@ public class AppUpgrader extends SettingsUpgrader {
String key = entry.getKey();
Object value = entry.getValue();
if (value == null) {
- Log.w(TAG, "skipped upgrade for null key " + key);
+ Log.w(TAG, "skipped upgrade and removing entry for null key " + key);
+ newPrefs.edit().remove(key).apply();
} else if (value instanceof Boolean) {
String boolValue = SettingsManager.convert((Boolean) value);
newPrefs.edit().putString(key, boolValue).apply();
@@ -370,8 +390,9 @@ public class AppUpgrader extends SettingsUpgrader {
} else if (value instanceof String){
newPrefs.edit().putString(key, (String) value).apply();
} else {
- Log.w(TAG,"skipped upgrade for unrecognized key type " +
- key + " : " + value.getClass());
+ Log.w(TAG,"skipped upgrade and removing entry for unrecognized "
+ + "key type " + key + " : " + value.getClass());
+ newPrefs.edit().remove(key).apply();
}
}
}
diff --git a/src/com/android/camera/settings/CameraSettingsActivity.java b/src/com/android/camera/settings/CameraSettingsActivity.java
index af80741b5..bb30b1122 100644
--- a/src/com/android/camera/settings/CameraSettingsActivity.java
+++ b/src/com/android/camera/settings/CameraSettingsActivity.java
@@ -49,6 +49,13 @@ import java.util.List;
* Provides the settings UI for the Camera app.
*/
public class CameraSettingsActivity extends FragmentActivity {
+ /**
+ * Used to denote a subsection of the preference tree to display in the
+ * Fragment. For instance, if 'Advanced' key is provided, the advanced
+ * preference section will be treated as the root for display. This is used
+ * to enable activity transitions between preference sections, and allows
+ * back/up stack to operate correctly.
+ */
public static final String PREF_SCREEN_EXTRA = "pref_screen_extra";
@Override
@@ -60,7 +67,10 @@ public class CameraSettingsActivity extends FragmentActivity {
actionBar.setTitle(R.string.mode_settings);
String prefKey = getIntent().getStringExtra(PREF_SCREEN_EXTRA);
- CameraSettingsFragment dialog = new CameraSettingsFragment(prefKey);
+ CameraSettingsFragment dialog = new CameraSettingsFragment();
+ Bundle bundle = new Bundle(1);
+ bundle.putString(PREF_SCREEN_EXTRA, prefKey);
+ dialog.setArguments(bundle);
getFragmentManager().beginTransaction().replace(android.R.id.content, dialog).commit();
}
@@ -83,7 +93,7 @@ public class CameraSettingsActivity extends FragmentActivity {
private static DecimalFormat sMegaPixelFormat = new DecimalFormat("##0.0");
private String[] mCamcorderProfileNames;
private CameraDeviceInfo mInfos;
- private final String mPrefKey;
+ private String mPrefKey;
private boolean mGetSubPrefAsRoot = true;
private boolean mPreferencesRemoved = false;
@@ -95,13 +105,13 @@ public class CameraSettingsActivity extends FragmentActivity {
private SelectedVideoQualities mVideoQualitiesBack;
private SelectedVideoQualities mVideoQualitiesFront;
- public CameraSettingsFragment(String prefKey) {
- mPrefKey = prefKey;
- }
-
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ Bundle arguments = getArguments();
+ if (arguments != null) {
+ mPrefKey = arguments.getString(PREF_SCREEN_EXTRA);
+ }
Context context = this.getActivity().getApplicationContext();
addPreferencesFromResource(R.xml.camera_preferences);
@@ -235,6 +245,14 @@ public class CameraSettingsActivity extends FragmentActivity {
* was found and removed.
*/
private boolean recursiveDelete(PreferenceGroup group, Preference preference) {
+ if (group == null) {
+ Log.d(TAG, "attempting to delete from null preference group");
+ return false;
+ }
+ if (preference == null) {
+ Log.d(TAG, "attempting to delete null preference");
+ return false;
+ }
if (group.removePreference(preference)) {
// Removal was successful.
return true;
diff --git a/src/com/android/camera/settings/Keys.java b/src/com/android/camera/settings/Keys.java
index 685b40fb4..8c1f2765a 100644
--- a/src/com/android/camera/settings/Keys.java
+++ b/src/com/android/camera/settings/Keys.java
@@ -63,7 +63,7 @@ public class Keys {
"pref_release_dialog_last_shown_version";
public static final String KEY_FLASH_SUPPORTED_BACK_CAMERA =
"pref_flash_supported_back_camera";
- public static final String KEY_UPGRADE_VERSION = "pref_strict_upgrade_version";
+ public static final String KEY_UPGRADE_VERSION = "pref_upgrade_version";
public static final String KEY_REQUEST_RETURN_HDR_PLUS = "pref_request_return_hdr_plus";
public static final String KEY_SHOULD_SHOW_REFOCUS_VIEWER_CLING =
"pref_should_show_refocus_viewer_cling";
diff --git a/src/com/android/camera/settings/SettingsManager.java b/src/com/android/camera/settings/SettingsManager.java
index 297bacbae..6d90832a4 100644
--- a/src/com/android/camera/settings/SettingsManager.java
+++ b/src/com/android/camera/settings/SettingsManager.java
@@ -320,7 +320,13 @@ public class SettingsManager {
*/
public String getString(String scope, String key, String defaultValue) {
SharedPreferences preferences = getPreferencesFromScope(scope);
- return preferences.getString(key, defaultValue);
+ try {
+ return preferences.getString(key, defaultValue);
+ } catch (ClassCastException e) {
+ Log.w(TAG, "existing preference with invalid type, removing and returning default", e);
+ preferences.edit().remove(key).apply();
+ return defaultValue;
+ }
}
/**
@@ -332,13 +338,13 @@ public class SettingsManager {
}
/**
- * Retrieve a setting's value as an Integer, manually specifiying
+ * Retrieve a setting's value as an Integer, manually specifying
* a default value.
*/
public Integer getInteger(String scope, String key, Integer defaultValue) {
String defaultValueString = Integer.toString(defaultValue);
String value = getString(scope, key, defaultValueString);
- return Integer.parseInt(value);
+ return convertToInt(value);
}
/**
@@ -356,7 +362,7 @@ public class SettingsManager {
public boolean getBoolean(String scope, String key, boolean defaultValue) {
String defaultValueString = defaultValue ? "1" : "0";
String value = getString(scope, key, defaultValueString);
- return (Integer.parseInt(value) != 0);
+ return convertToBoolean(value);
}
/**
@@ -517,6 +523,29 @@ public class SettingsManager {
}
/**
+ * Package private conversion method to turn String storage format into
+ * ints.
+ *
+ * @param value String to be converted to int
+ * @return int value of stored String
+ */
+ static int convertToInt(String value) {
+ return Integer.parseInt(value);
+ }
+
+ /**
+ * Package private conversion method to turn String storage format into
+ * booleans.
+ *
+ * @param value String to be converted to boolean
+ * @return boolean value of stored String
+ */
+ static boolean convertToBoolean(String value) {
+ return Integer.parseInt(value) != 0;
+ }
+
+
+ /**
* Package private conversion method to turn booleans into preferred
* String storage format.
*
diff --git a/src/com/android/camera/settings/SettingsUpgrader.java b/src/com/android/camera/settings/SettingsUpgrader.java
index 96797ace6..4b38669ed 100644
--- a/src/com/android/camera/settings/SettingsUpgrader.java
+++ b/src/com/android/camera/settings/SettingsUpgrader.java
@@ -88,7 +88,12 @@ public abstract class SettingsUpgrader {
* modules to upgrade their boolean settings to Strings.
*/
protected boolean removeBoolean(SharedPreferences oldPreferencesLocation, String key) {
- boolean value = oldPreferencesLocation.getBoolean(key, false);
+ boolean value = false;
+ try {
+ value = oldPreferencesLocation.getBoolean(key, value);
+ } catch (ClassCastException e) {
+ Log.e(TAG, "error reading old value, removing and returning default", e);
+ }
oldPreferencesLocation.edit().remove(key).apply();
return value;
}
@@ -102,7 +107,12 @@ public abstract class SettingsUpgrader {
* modules to upgrade their Integer settings to Strings.
*/
protected int removeInteger(SharedPreferences oldPreferencesLocation, String key) {
- int value = oldPreferencesLocation.getInt(key, 0);
+ int value = 0;
+ try {
+ value = oldPreferencesLocation.getInt(key, value);
+ } catch (ClassCastException e) {
+ Log.e(TAG, "error reading old value, removing and returning default", e);
+ }
oldPreferencesLocation.edit().remove(key).apply();
return value;
}
@@ -116,7 +126,12 @@ public abstract class SettingsUpgrader {
* modules to upgrade their boolean settings to Strings.
*/
protected String removeString(SharedPreferences oldPreferencesLocation, String key) {
- String value = oldPreferencesLocation.getString(key, null);
+ String value = null;
+ try {
+ value = oldPreferencesLocation.getString(key, value);
+ } catch (ClassCastException e) {
+ Log.e(TAG, "error reading old value, removing and returning default", e);
+ }
oldPreferencesLocation.edit().remove(key).apply();
return value;
}
diff --git a/src/com/android/camera/settings/SettingsUtil.java b/src/com/android/camera/settings/SettingsUtil.java
index d0f9b09ee..acf892116 100644
--- a/src/com/android/camera/settings/SettingsUtil.java
+++ b/src/com/android/camera/settings/SettingsUtil.java
@@ -24,6 +24,7 @@ import android.media.CamcorderProfile;
import android.util.SparseArray;
import com.android.camera.debug.Log;
+import com.android.camera.util.ApiHelper;
import com.android.camera.util.Callback;
import com.android.camera2.R;
import com.android.ex.camera2.portability.CameraDeviceInfo;
@@ -353,7 +354,8 @@ public class SettingsUtil {
*/
private static int getNextSupportedVideoQualityIndex(int cameraId, int start) {
for (int i = start + 1; i < sVideoQualities.length; ++i) {
- if (CamcorderProfile.hasProfile(cameraId, sVideoQualities[i])) {
+ if (isVideoQualitySupported(sVideoQualities[i])
+ && CamcorderProfile.hasProfile(cameraId, sVideoQualities[i])) {
// We found a new supported quality.
return i;
}
@@ -371,6 +373,19 @@ public class SettingsUtil {
}
/**
+ * @return Whether the given {@link CamcorderProfile} is supported on the
+ * current device/OS version.
+ */
+ private static boolean isVideoQualitySupported(int videoQuality) {
+ // 4k is only supported on L or higher but some devices falsely report
+ // to have support for it on K, see b/18172081.
+ if (!ApiHelper.isLOrHigher() && videoQuality == CamcorderProfile.QUALITY_2160P) {
+ return false;
+ }
+ return true;
+ }
+
+ /**
* Returns the index of the size within the given list that is closest to
* the given target pixel count.
*/
diff --git a/src/com/android/camera/ui/BottomBar.java b/src/com/android/camera/ui/BottomBar.java
index 551cc094f..d5fdbf620 100644
--- a/src/com/android/camera/ui/BottomBar.java
+++ b/src/com/android/camera/ui/BottomBar.java
@@ -18,6 +18,7 @@ package com.android.camera.ui;
import android.content.Context;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
@@ -25,6 +26,7 @@ import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.TransitionDrawable;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.TouchDelegate;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.ImageButton;
@@ -75,9 +77,7 @@ public class BottomBar extends FrameLayout {
private final float mCircleRadius;
private CaptureLayoutHelper mCaptureLayoutHelper = null;
- // for Android L, these backgrounds are RippleDrawables (ISA LayerDrawable)
- // pre-L, they're plain old LayerDrawables
- private final LayerDrawable[] mShutterButtonBackgrounds;
+ private final Drawable.ConstantState[] mShutterButtonBackgroundConstantStates;
// a reference to the shutter background's first contained drawable
// if it's an animated circle drawable (for video mode)
private AnimatedCircleDrawable mAnimatedCircleDrawable;
@@ -100,25 +100,11 @@ public class BottomBar extends FrameLayout {
TypedArray ar = context.getResources()
.obtainTypedArray(R.array.shutter_button_backgrounds);
int len = ar.length();
-
- mShutterButtonBackgrounds = new LayerDrawable[len];
+ mShutterButtonBackgroundConstantStates = new Drawable.ConstantState[len];
for (int i = 0; i < len; i++) {
int drawableId = ar.getResourceId(i, -1);
- LayerDrawable shutterBackground = mShutterButtonBackgrounds[i] =
- (LayerDrawable) context.getResources().getDrawable(drawableId).mutate();
-
- // the background for video has a circle_item drawable placeholder
- // that gets replaced by an AnimatedCircleDrawable for the cool
- // shrink-down-to-a-circle effect
- // all other modes need not do this replace
- Drawable d = shutterBackground.findDrawableByLayerId(R.id.circle_item);
- if (d != null) {
- Drawable animatedCircleDrawable =
- new AnimatedCircleDrawable((int) mCircleRadius);
- animatedCircleDrawable.setLevel(DRAWABLE_MAX_LEVEL);
- shutterBackground
- .setDrawableByLayerId(R.id.circle_item, animatedCircleDrawable);
- }
+ mShutterButtonBackgroundConstantStates[i] =
+ context.getResources().getDrawable(drawableId).getConstantState();
}
ar.recycle();
}
@@ -146,11 +132,18 @@ public class BottomBar extends FrameLayout {
private void setCancelBackgroundColor(int alpha, int color) {
LayerDrawable layerDrawable = (LayerDrawable) mCancelButton.getBackground();
- ColorDrawable colorDrawable = (ColorDrawable) layerDrawable.getDrawable(0);
- if (!ApiHelper.isLOrHigher()) {
- colorDrawable.setColor(color);
+ Drawable d = layerDrawable.getDrawable(0);
+ if (d instanceof AnimatedCircleDrawable) {
+ AnimatedCircleDrawable animatedCircleDrawable = (AnimatedCircleDrawable) d;
+ animatedCircleDrawable.setColor(color);
+ animatedCircleDrawable.setAlpha(alpha);
+ } else if (d instanceof ColorDrawable) {
+ ColorDrawable colorDrawable = (ColorDrawable) d;
+ if (!ApiHelper.isLOrHigher()) {
+ colorDrawable.setColor(color);
+ }
+ colorDrawable.setAlpha(alpha);
}
- colorDrawable.setAlpha(alpha);
}
private void setCaptureButtonUp() {
@@ -222,6 +215,32 @@ public class BottomBar extends FrameLayout {
}
});
+ extendTouchAreaToMatchParent(R.id.done_button);
+ }
+
+ private void extendTouchAreaToMatchParent(int id) {
+ final View button = findViewById(id);
+ final View parent = (View) button.getParent();
+
+ parent.post(new Runnable() {
+ @Override
+ public void run() {
+ Rect parentRect = new Rect();
+ parent.getHitRect(parentRect);
+ Rect buttonRect = new Rect();
+ button.getHitRect(buttonRect);
+
+ int widthDiff = parentRect.width() - buttonRect.width();
+ int heightDiff = parentRect.height() - buttonRect.height();
+
+ buttonRect.left -= widthDiff/2;
+ buttonRect.right += widthDiff/2;
+ buttonRect.top -= heightDiff/2;
+ buttonRect.bottom += heightDiff/2;
+
+ parent.setTouchDelegate(new TouchDelegate(buttonRect, button));
+ }
+ });
}
/**
@@ -347,9 +366,33 @@ public class BottomBar extends FrameLayout {
}
}
+ private LayerDrawable applyCircleDrawableToShutterBackground(LayerDrawable shutterBackground) {
+ // the background for video has a circle_item drawable placeholder
+ // that gets replaced by an AnimatedCircleDrawable for the cool
+ // shrink-down-to-a-circle effect
+ // all other modes need not do this replace
+ Drawable d = shutterBackground.findDrawableByLayerId(R.id.circle_item);
+ if (d != null) {
+ Drawable animatedCircleDrawable =
+ new AnimatedCircleDrawable((int) mCircleRadius);
+ animatedCircleDrawable.setLevel(DRAWABLE_MAX_LEVEL);
+ shutterBackground
+ .setDrawableByLayerId(R.id.circle_item, animatedCircleDrawable);
+ }
+
+ return shutterBackground;
+ }
+
+ private LayerDrawable newDrawableFromConstantState(Drawable.ConstantState constantState) {
+ return (LayerDrawable) constantState.newDrawable(getContext().getResources());
+ }
+
private void setupShutterBackgroundForModeIndex(int index) {
- LayerDrawable shutterBackground = mShutterButtonBackgrounds[index];
+ LayerDrawable shutterBackground = applyCircleDrawableToShutterBackground(
+ newDrawableFromConstantState(mShutterButtonBackgroundConstantStates[index]));
mShutterButton.setBackground(shutterBackground);
+ mCancelButton.setBackground(applyCircleDrawableToShutterBackground(
+ newDrawableFromConstantState(mShutterButtonBackgroundConstantStates[index])));
Drawable d = shutterBackground.getDrawable(0);
mAnimatedCircleDrawable = null;
diff --git a/src/com/android/camera/ui/ModeListView.java b/src/com/android/camera/ui/ModeListView.java
index 41bd3d672..98c0c2e6d 100644
--- a/src/com/android/camera/ui/ModeListView.java
+++ b/src/com/android/camera/ui/ModeListView.java
@@ -276,6 +276,15 @@ public class ModeListView extends FrameLayout
// Do nothing.
}
+ /**
+ * Hide the mode drawer (with animation, if supported)
+ * and switch to fully hidden state.
+ * Default is to simply call {@link #hide()}.
+ */
+ public void hideAnimated() {
+ hide();
+ }
+
/***************GestureListener implementation*****************/
@Override
public boolean onDown(MotionEvent e) {
@@ -742,6 +751,16 @@ public class ModeListView extends FrameLayout
mCurrentStateManager.setCurrentState(new FullyHiddenState());
}
+ @Override
+ public void hideAnimated() {
+ cancelAnimation();
+ animateListToWidth(0).addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mCurrentStateManager.setCurrentState(new FullyHiddenState());
+ }
+ });
+ }
}
/**
@@ -1440,6 +1459,20 @@ public class ModeListView extends FrameLayout
}
/**
+ * Hide the mode list immediately (provided the current state allows it).
+ */
+ public void hide() {
+ mCurrentStateManager.getCurrentState().hide();
+ }
+
+ /**
+ * Hide the mode list with an animation.
+ */
+ public void hideAnimated() {
+ mCurrentStateManager.getCurrentState().hideAnimated();
+ }
+
+ /**
* Resets the visible width of all the mode selectors to 0.
*/
private void resetModeSelectors() {
@@ -1895,7 +1928,7 @@ public class ModeListView extends FrameLayout
private class PeepholeAnimationEffect extends AnimationEffects {
private final static int UNSET = -1;
- private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 300;
+ private final static int PEEP_HOLE_ANIMATION_DURATION_MS = 500;
private final Paint mMaskPaint = new Paint();
private final RectF mBackgroundDrawArea = new RectF();
diff --git a/src/com/android/camera/util/ApiHelper.java b/src/com/android/camera/util/ApiHelper.java
index 713397588..7f6f59c8a 100644
--- a/src/com/android/camera/util/ApiHelper.java
+++ b/src/com/android/camera/util/ApiHelper.java
@@ -42,6 +42,7 @@ public class ApiHelper {
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN;
public static final boolean HAS_CAMERA_HDR_PLUS = isKitKatOrHigher();
+ public static final boolean HDR_PLUS_CAN_USE_ARBITRARY_ASPECT_RATIOS = isKitKatMR2OrHigher();
public static final boolean HAS_CAMERA_HDR =
Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1;
public static final boolean HAS_DISPLAY_LISTENER =
@@ -59,6 +60,12 @@ public class ApiHelper {
&& "hammerhead".equalsIgnoreCase(Build.DEVICE);
public static final boolean IS_NEXUS_6 = "motorola".equalsIgnoreCase(Build.MANUFACTURER)
&& "shamu".equalsIgnoreCase(Build.DEVICE);
+ public static final boolean IS_NEXUS_9 = "htc".equalsIgnoreCase(Build.MANUFACTURER)
+ && ("flounder".equalsIgnoreCase(Build.DEVICE)
+ || "flounder_lte".equalsIgnoreCase(Build.DEVICE));
+ public static final boolean IS_HTC = "htc".equalsIgnoreCase(Build.MANUFACTURER);
+
+ public static final boolean HAS_CAMERA_2_API = isLOrHigher();
public static int getIntFieldIfExists(Class<?> klass, String fieldName,
Class<?> obj, int defaultVal) {
@@ -76,6 +83,12 @@ public class ApiHelper {
|| "KeyLimePie".equals(Build.VERSION.CODENAME);
}
+ public static boolean isKitKatMR2OrHigher() {
+ return isLOrHigher()
+ || (isKitKatOrHigher() &&
+ ("4.4.4".equals(Build.VERSION.RELEASE) || "4.4.3".equals(Build.VERSION.RELEASE)));
+ }
+
public static boolean isLOrHigher() {
return Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP
|| "L".equals(Build.VERSION.CODENAME);
diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java
index a55aa8451..d6237a92e 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -40,7 +40,6 @@ import android.net.Uri;
import android.os.ParcelFileDescriptor;
import android.telephony.TelephonyManager;
import android.util.DisplayMetrics;
-import android.util.FloatMath;
import android.util.TypedValue;
import android.view.Display;
import android.view.OrientationEventListener;
@@ -301,6 +300,7 @@ public class CameraUtil {
// appearing, so check to ensure that the activity is not shutting down
// before attempting to attach a dialog to the window manager.
if (!activity.isFinishing()) {
+ Log.e(TAG, "Show fatal error dialog");
new AlertDialog.Builder(activity)
.setCancelable(false)
.setTitle(R.string.camera_error_title)
@@ -513,7 +513,15 @@ public class CameraUtil {
public static int getOptimalPreviewSizeIndex(Context context,
List<Size> sizes, double targetRatio) {
// Use a very small tolerance because we want an exact match.
- final double ASPECT_TOLERANCE = 0.01;
+ final double ASPECT_TOLERANCE;
+ // HTC 4:3 ratios is over .01 from true 4:3, targeted fix for those
+ // devices here, see b/18241645
+ if (ApiHelper.IS_HTC && targetRatio > 1.3433 && targetRatio < 1.35) {
+ Log.w(TAG, "4:3 ratio out of normal tolerance, increasing tolerance to 0.02");
+ ASPECT_TOLERANCE = 0.02;
+ } else {
+ ASPECT_TOLERANCE = 0.01;
+ }
if (sizes == null) {
return -1;
}
@@ -552,7 +560,7 @@ public class CameraUtil {
// Cannot find the one match the aspect ratio. This should not happen.
// Ignore the requirement.
if (optimalSizeIndex == -1) {
- Log.w(TAG, "No preview size match the aspect ratio");
+ Log.w(TAG, "No preview size match the aspect ratio. available sizes: " + sizes);
minDiff = Double.MAX_VALUE;
for (int i = 0; i < sizes.size(); i++) {
Size size = sizes.get(i);
@@ -562,12 +570,24 @@ public class CameraUtil {
}
}
}
+
return optimalSizeIndex;
}
- /** Returns the largest picture size which matches the given aspect ratio. */
+ /**
+ * Returns the largest picture size which matches the given aspect ratio,
+ * except for the special WYSIWYG case where the picture size exactly matches
+ * the target size.
+ *
+ * @param sizes a list of candidate sizes, available for use
+ * @param targetWidth the ideal width of the video snapshot
+ * @param targetHeight the ideal height of the video snapshot
+ * @return the Optimal Video Snapshot Picture Size
+ */
public static com.android.ex.camera2.portability.Size getOptimalVideoSnapshotPictureSize(
- List<com.android.ex.camera2.portability.Size> sizes, double targetRatio) {
+ List<com.android.ex.camera2.portability.Size> sizes, int targetWidth,
+ int targetHeight) {
+
// Use a very small tolerance because we want an exact match.
final double ASPECT_TOLERANCE = 0.001;
if (sizes == null) {
@@ -576,7 +596,17 @@ public class CameraUtil {
com.android.ex.camera2.portability.Size optimalSize = null;
+ // WYSIWYG Override
+ // We assume that physical display constraints have already been
+ // imposed on the variables sizes
+ for (com.android.ex.camera2.portability.Size size : sizes) {
+ if (size.height() == targetHeight && size.width() == targetWidth) {
+ return size;
+ }
+ }
+
// Try to find a size matches aspect ratio and has the largest width
+ final double targetRatio = (double) targetWidth / targetHeight;
for (com.android.ex.camera2.portability.Size size : sizes) {
double ratio = (double) size.width() / size.height();
if (Math.abs(ratio - targetRatio) > ASPECT_TOLERANCE) {
@@ -903,7 +933,7 @@ public class CameraUtil {
float sigma = len;
float sum = 0;
for (int i = 0; i <= mid; i++) {
- float ex = FloatMath.exp(-(i - mid) * (i - mid) / (mid * mid))
+ float ex = (float) Math.exp(-(i - mid) * (i - mid) / (mid * mid))
/ (2 * sigma * sigma);
int symmetricIndex = len - 1 - i;
mask[i] = ex;
diff --git a/src/com/android/camera/util/QuickActivity.java b/src/com/android/camera/util/QuickActivity.java
new file mode 100644
index 000000000..039d8fc9b
--- /dev/null
+++ b/src/com/android/camera/util/QuickActivity.java
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2014 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.util;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Handler;
+import android.provider.MediaStore;
+
+import com.android.camera.debug.Log;
+
+/**
+ * Workaround for secure-lockscreen double-onResume() bug:
+ * <p>
+ * If started from the secure-lockscreen, the activity may be quickly started,
+ * resumed, paused, stopped, and then started and resumed again. This is
+ * problematic for launch time from the secure-lockscreen because we typically open the
+ * camera in onResume() and close it in onPause(). These camera operations take
+ * a long time to complete. To workaround it, this class filters out
+ * high-frequency onResume()->onPause() sequences if the current intent
+ * indicates that we have started from the secure-lockscreen.
+ * </p>
+ * <p>
+ * Subclasses should override the appropriate on[Create|Start...]Tasks() method
+ * in place of the original.
+ * </p>
+ * <p>
+ * Sequences of onResume() followed quickly by onPause(), when the activity is
+ * started from a secure-lockscreen will result in a quick no-op.<br>
+ * </p>
+ */
+public abstract class QuickActivity extends Activity {
+ private static final Log.Tag TAG = new Log.Tag("QuickActivity");
+
+ /**
+ * The amount of time to wait before running onResumeTasks when started from
+ * the lockscreen.
+ */
+ private static final long ON_RESUME_DELAY_MILLIS = 20;
+ /** A reference to the main handler on which to run lifecycle methods. */
+ private Handler mMainHandler;
+ private boolean mPaused;
+ /**
+ * True if the last call to onResume() resulted in a delayed call to
+ * mOnResumeTasks which was then canceled due to an immediate onPause().
+ * This allows optimizing the common case in which the subsequent
+ * call to onResume() should execute onResumeTasks() immediately.
+ */
+ private boolean mCanceledResumeTasks = false;
+
+ /**
+ * A runnable for deferring tasks to be performed in onResume() if starting
+ * from the lockscreen.
+ */
+ private final Runnable mOnResumeTasks = new Runnable() {
+ @Override
+ public void run() {
+ logLifecycle("onResumeTasks", true);
+ if (mPaused) {
+ onResumeTasks();
+ mPaused = false;
+ mCanceledResumeTasks = false;
+ }
+ logLifecycle("onResumeTasks", false);
+ }
+ };
+
+ @Override
+ protected final void onNewIntent(Intent intent) {
+ logLifecycle("onNewIntent", true);
+ Log.v(TAG, "Intent Action = " + intent.getAction());
+ setIntent(intent);
+ super.onNewIntent(intent);
+ onNewIntentTasks(intent);
+ logLifecycle("onNewIntent", false);
+ }
+
+ @Override
+ protected final void onCreate(Bundle bundle) {
+ logLifecycle("onCreate", true);
+ Log.v(TAG, "Intent Action = " + getIntent().getAction());
+ super.onCreate(bundle);
+
+ mMainHandler = new Handler(getMainLooper());
+
+ onCreateTasks(bundle);
+
+ mPaused = true;
+
+ logLifecycle("onCreate", false);
+ }
+
+ @Override
+ protected final void onStart() {
+ logLifecycle("onStart", true);
+ onStartTasks();
+ super.onStart();
+ logLifecycle("onStart", false);
+ }
+
+ @Override
+ protected final void onResume() {
+ logLifecycle("onResume", true);
+ mMainHandler.removeCallbacks(mOnResumeTasks);
+ if (delayOnResumeOnStart() && !mCanceledResumeTasks) {
+ mMainHandler.postDelayed(mOnResumeTasks, ON_RESUME_DELAY_MILLIS);
+ } else {
+ if (mPaused) {
+ onResumeTasks();
+ mPaused = false;
+ mCanceledResumeTasks = false;
+ }
+ }
+ super.onResume();
+ logLifecycle("onResume", false);
+ }
+
+ @Override
+ protected final void onPause() {
+ logLifecycle("onPause", true);
+ mMainHandler.removeCallbacks(mOnResumeTasks);
+ if (!mPaused) {
+ onPauseTasks();
+ mPaused = true;
+ } else {
+ mCanceledResumeTasks = true;
+ }
+ super.onPause();
+ logLifecycle("onPause", false);
+ }
+
+ @Override
+ protected final void onStop() {
+ if (isChangingConfigurations()) {
+ Log.v(TAG, "changing configurations");
+ }
+ logLifecycle("onStop", true);
+ onStopTasks();
+ super.onStop();
+ logLifecycle("onStop", false);
+ }
+
+ @Override
+ protected final void onRestart() {
+ logLifecycle("onRestart", true);
+ super.onRestart();
+ // TODO Support onRestartTasks() and handle the workaround for that too.
+ logLifecycle("onRestart", false);
+ }
+
+ @Override
+ protected final void onDestroy() {
+ logLifecycle("onDestroy", true);
+ onDestroyTasks();
+ super.onDestroy();
+ logLifecycle("onDestroy", false);
+ }
+
+ private void logLifecycle(String methodName, boolean start) {
+ String prefix = start ? "START" : "END";
+ Log.v(TAG, prefix + " " + methodName + ": Activity = " + toString());
+ }
+
+ private boolean delayOnResumeOnStart() {
+ String action = getIntent().getAction();
+ boolean isSecureLockscreenCamera =
+ MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA_SECURE.equals(action);
+ return isSecureLockscreenCamera;
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onNewIntent}.
+ */
+ protected void onNewIntentTasks(Intent newIntent) {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onCreate}.
+ */
+ protected void onCreateTasks(Bundle savedInstanceState) {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onStart}.
+ */
+ protected void onStartTasks() {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onResume}.
+ */
+ protected void onResumeTasks() {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onPause}.
+ */
+ protected void onPauseTasks() {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onStop}.
+ */
+ protected void onStopTasks() {
+ }
+
+ /**
+ * Subclasses should override this in place of {@link Activity#onDestroy}.
+ */
+ protected void onDestroyTasks() {
+ }
+}
diff --git a/src/com/android/camera/widget/FilmstripView.java b/src/com/android/camera/widget/FilmstripView.java
index e0a1bac90..37d3ea719 100644
--- a/src/com/android/camera/widget/FilmstripView.java
+++ b/src/com/android/camera/widget/FilmstripView.java
@@ -21,6 +21,7 @@ import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
+import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Point;
@@ -42,6 +43,7 @@ import android.view.animation.DecelerateInterpolator;
import android.widget.Scroller;
import com.android.camera.CameraActivity;
+import com.android.camera.data.LocalData.ActionCallback;
import com.android.camera.debug.Log;
import com.android.camera.filmstrip.DataAdapter;
import com.android.camera.filmstrip.FilmstripController;
@@ -52,11 +54,39 @@ import com.android.camera.util.ApiHelper;
import com.android.camera.util.CameraUtil;
import com.android.camera2.R;
+import java.lang.ref.WeakReference;
import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Queue;
public class FilmstripView extends ViewGroup {
+ /**
+ * An action callback to be used for actions on the local media data items.
+ */
+ public static class ActionCallbackImpl implements ActionCallback {
+ private final WeakReference<Activity> mActivity;
+
+ /**
+ * The given activity is used to start intents. It is wrapped in a weak
+ * reference to prevent leaks.
+ */
+ public ActionCallbackImpl(Activity activity) {
+ mActivity = new WeakReference<Activity>(activity);
+ }
+
+ /**
+ * Fires an intent to play the video with the given URI and title.
+ */
+ @Override
+ public void playVideo(Uri uri, String title) {
+ Activity activity = mActivity.get();
+ if (activity != null) {
+ CameraUtil.playVideo(activity, uri, title);
+ }
+ }
+ }
+
+
private static final Log.Tag TAG = new Log.Tag("FilmstripView");
private static final int BUFFER_SIZE = 5;
@@ -87,6 +117,7 @@ public class FilmstripView extends ViewGroup {
private static final int DECELERATION_FACTOR = 4;
private CameraActivity mActivity;
+ private ActionCallback mActionCallback;
private FilmstripGestureRecognizer mGestureRecognizer;
private FilmstripGestureRecognizer.Listener mGestureListener;
private DataAdapter mDataAdapter;
@@ -585,6 +616,7 @@ public class FilmstripView extends ViewGroup {
private void init(CameraActivity cameraActivity) {
setWillNotDraw(false);
mActivity = cameraActivity;
+ mActionCallback = new ActionCallbackImpl(mActivity);
mScale = 1.0f;
mDataIdOnUserScrolling = 0;
mController = new MyController(cameraActivity);
@@ -792,6 +824,12 @@ public class FilmstripView extends ViewGroup {
}
private ViewItem buildItemFromData(int dataID) {
+ if (mActivity.isDestroyed()) {
+ // Loading item data is call from multiple AsyncTasks and the
+ // activity may be finished when buildItemFromData is called.
+ Log.d(TAG, "Activity destroyed, don't load data");
+ return null;
+ }
ImageData data = mDataAdapter.getImageData(dataID);
if (data == null) {
return null;
@@ -807,7 +845,8 @@ public class FilmstripView extends ViewGroup {
data.prepare();
View recycled = getRecycledView(dataID);
- View v = mDataAdapter.getView(mActivity, recycled, dataID);
+ View v = mDataAdapter.getView(mActivity.getAndroidContext(), recycled, dataID,
+ mActionCallback);
if (v == null) {
return null;
}
@@ -1215,18 +1254,22 @@ public class FilmstripView extends ViewGroup {
// It's in full-screen mode.
fadeAndScaleRightViewItem(itemID);
} else {
- if (curr.getVisibility() == INVISIBLE) {
- curr.setVisibility(VISIBLE);
- }
+ boolean setToVisible = (curr.getVisibility() == INVISIBLE);
+
if (itemID == mCurrentItem + 1) {
curr.setAlpha(1f - scaleFraction);
} else {
if (scaleFraction == 0f) {
curr.setAlpha(1f);
} else {
- curr.setVisibility(INVISIBLE);
+ setToVisible = false;
}
}
+
+ if (setToVisible) {
+ curr.setVisibility(VISIBLE);
+ }
+
curr.setTranslationX(
(mViewItem[mCurrentItem].getLeftPosition() - curr.getLeftPosition()) *
scaleFraction);
@@ -1364,15 +1407,20 @@ public class FilmstripView extends ViewGroup {
// The end of the filmstrip might have been changed.
// The mCenterX might be out of the bound.
ViewItem currItem = mViewItem[mCurrentItem];
- if (currItem.getId() == mDataAdapter.getTotalNumber() - 1
- && mCenterX > currItem.getCenterX()) {
- int adjustDiff = currItem.getCenterX() - mCenterX;
- mCenterX = currItem.getCenterX();
- for (int i = 0; i < BUFFER_SIZE; i++) {
- if (mViewItem[i] != null) {
- mViewItem[i].translateXScaledBy(adjustDiff);
+ if(currItem!=null) {
+ if (currItem.getId() == mDataAdapter.getTotalNumber() - 1
+ && mCenterX > currItem.getCenterX()) {
+ int adjustDiff = currItem.getCenterX() - mCenterX;
+ mCenterX = currItem.getCenterX();
+ for (int i = 0; i < BUFFER_SIZE; i++) {
+ if (mViewItem[i] != null) {
+ mViewItem[i].translateXScaledBy(adjustDiff);
+ }
}
}
+ } else {
+ // CurrItem should NOT be NULL, but if is, at least don't crash.
+ Log.w(TAG,"Caught invalid update in removal animation.");
}
} else {
// fill the removed place by right shift
@@ -1473,6 +1521,10 @@ public class FilmstripView extends ViewGroup {
getMeasuredWidth(), getMeasuredHeight());
final int offsetX = dim.x + mViewGapInPixel;
ViewItem viewItem = buildItemFromData(dataID);
+ if (viewItem == null) {
+ Log.w(TAG, "unable to build inserted item from data");
+ return;
+ }
if (insertedItemId >= mCurrentItem) {
if (insertedItemId == mCurrentItem) {
@@ -1887,7 +1939,7 @@ public class FilmstripView extends ViewGroup {
MyController(Context context) {
TimeInterpolator decelerateInterpolator = new DecelerateInterpolator(1.5f);
- mScroller = new MyScroller(mActivity,
+ mScroller = new MyScroller(mActivity.getAndroidContext(),
new Handler(mActivity.getMainLooper()),
mScrollerListener, decelerateInterpolator);
mCanStopScroll = true;
diff --git a/src/com/android/camera/widget/VideoRecordingHints.java b/src/com/android/camera/widget/VideoRecordingHints.java
index c931dfbe2..1c0b7a3b6 100644
--- a/src/com/android/camera/widget/VideoRecordingHints.java
+++ b/src/com/android/camera/widget/VideoRecordingHints.java
@@ -29,6 +29,8 @@ import android.view.View;
import com.android.camera.util.CameraUtil;
import com.android.camera2.R;
+import java.lang.ref.WeakReference;
+
/**
* This class is designed to show the video recording hint when device is held in
* portrait before video recording. The rotation device indicator will start rotating
@@ -58,6 +60,87 @@ public class VideoRecordingHints extends View {
private int mCenterY = UNSET;
private int mLastOrientation = OrientationEventListener.ORIENTATION_UNKNOWN;
+ private static class RotationAnimatorListener implements Animator.AnimatorListener {
+ private final WeakReference<VideoRecordingHints> mHints;
+ private boolean mCanceled = false;
+
+ public RotationAnimatorListener(VideoRecordingHints hint) {
+ mHints = new WeakReference<VideoRecordingHints>(hint);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ mCanceled = false;
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ VideoRecordingHints hint = mHints.get();
+ if (hint == null) {
+ return;
+ }
+
+ hint.mRotation = ((int) hint.mRotation) % 360;
+ // If animation is canceled, do not restart it.
+ if (mCanceled) {
+ return;
+ }
+ hint.post(new Runnable() {
+ @Override
+ public void run() {
+ VideoRecordingHints hint = mHints.get();
+ if (hint != null) {
+ hint.continueRotationAnimation();
+ }
+ }
+ });
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ mCanceled = true;
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ // Do nothing.
+ }
+ }
+
+ private static class AlphaAnimatorListener implements Animator.AnimatorListener {
+ private final WeakReference<VideoRecordingHints> mHints;
+ AlphaAnimatorListener(VideoRecordingHints hint) {
+ mHints = new WeakReference<VideoRecordingHints>(hint);
+ }
+
+ @Override
+ public void onAnimationStart(Animator animation) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ VideoRecordingHints hint = mHints.get();
+ if (hint == null) {
+ return;
+ }
+
+ hint.invalidate();
+ hint.setAlpha(1f);
+ hint.mRotation = 0;
+ }
+
+ @Override
+ public void onAnimationCancel(Animator animation) {
+ // Do nothing.
+ }
+
+ @Override
+ public void onAnimationRepeat(Animator animation) {
+ // Do nothing.
+ }
+ }
+
public VideoRecordingHints(Context context, AttributeSet attrs) {
super(context, attrs);
mRotateArrows = getResources().getDrawable(R.drawable.rotate_arrows);
@@ -80,64 +163,11 @@ public class VideoRecordingHints extends View {
}
});
- mRotationAnimation.addListener(new Animator.AnimatorListener() {
- private boolean mCanceled = false;
- @Override
- public void onAnimationStart(Animator animation) {
- mCanceled = false;
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- mRotation = ((int) mRotation) % 360;
- // If animation is canceled, do not restart it.
- if (mCanceled) {
- return;
- }
- post(new Runnable() {
- @Override
- public void run() {
- continueRotationAnimation();
- }
- });
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- mCanceled = true;
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- // Do nothing.
- }
- });
+ mRotationAnimation.addListener(new RotationAnimatorListener(this));
mAlphaAnimator = ObjectAnimator.ofFloat(this, "alpha", 1f, 0f);
mAlphaAnimator.setDuration(FADE_OUT_DURATION_MS);
- mAlphaAnimator.addListener(new Animator.AnimatorListener() {
- @Override
- public void onAnimationStart(Animator animation) {
- // Do nothing.
- }
-
- @Override
- public void onAnimationEnd(Animator animation) {
- invalidate();
- setAlpha(1f);
- mRotation = 0;
- }
-
- @Override
- public void onAnimationCancel(Animator animation) {
- // Do nothing.
- }
-
- @Override
- public void onAnimationRepeat(Animator animation) {
- // Do nothing.
- }
- });
+ mAlphaAnimator.addListener(new AlphaAnimatorListener(this));
mIsDefaultToPortrait = CameraUtil.isDefaultToPortrait(context);
}