diff options
author | Steve Kondik <steve@cyngn.com> | 2015-03-24 07:23:19 -0700 |
---|---|---|
committer | Steve Kondik <steve@cyngn.com> | 2015-03-24 07:23:19 -0700 |
commit | 6eaafbef54de3a9b70ec338512b537cd0f7b5d13 (patch) | |
tree | 69646095b714fc45fb7c4d8aeeae6dcaf8aa3d49 /src | |
parent | 708b6b29c2cde2510b6cee475bbb91f0ac06c74f (diff) | |
parent | 6cb15e279f0fcecfab919a51b6a542bcef5bd2f1 (diff) | |
download | android_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')
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); } |