OSDN Git Service

Add error handling for repeating preview workaround
authorPuneet Lall <puneetl@google.com>
Tue, 3 Mar 2015 21:39:03 +0000 (13:39 -0800)
committerPuneet Lall <puneetl@google.com>
Thu, 5 Mar 2015 18:37:29 +0000 (10:37 -0800)
Adds error handling to show a dialog if the workaround for the repeating
preview (bug: 19061883) fails.

Bug: 19565931
Change-Id: Iabc3b4fdbc6977b0e1828243e80138385a3ef522

22 files changed:
src/com/android/camera/CameraActivity.java
src/com/android/camera/CaptureModule.java
src/com/android/camera/FatalErrorHandler.java [new file with mode: 0644]
src/com/android/camera/FatalErrorHandlerImpl.java [new file with mode: 0644]
src/com/android/camera/app/AppController.java
src/com/android/camera/captureintent/resource/ResourceConstructed.java
src/com/android/camera/captureintent/resource/ResourceConstructedImpl.java
src/com/android/camera/captureintent/state/StateBackground.java
src/com/android/camera/captureintent/state/StateOpeningCamera.java
src/com/android/camera/one/OneCameraManager.java
src/com/android/camera/one/v1/OneCameraManagerImpl.java
src/com/android/camera/one/v2/OneCameraFactory.java
src/com/android/camera/one/v2/OneCameraManagerImpl.java
src/com/android/camera/one/v2/SimpleOneCameraFactory.java
src/com/android/camera/one/v2/ZslOneCameraFactory.java
src/com/android/camera/one/v2/errorhandling/FailureHandler.java [new file with mode: 0644]
src/com/android/camera/one/v2/errorhandling/FastCameraReset.java [new file with mode: 0644]
src/com/android/camera/one/v2/errorhandling/FatalErrorDialogFailureHandler.java [new file with mode: 0644]
src/com/android/camera/one/v2/errorhandling/RecoverySuccessCallback.java [new file with mode: 0644]
src/com/android/camera/one/v2/errorhandling/RepeatFailureDetector.java
src/com/android/camera/one/v2/errorhandling/RepeatFailureHandlerComponent.java [new file with mode: 0644]
src/com/android/camera/util/CameraUtil.java

index 08768b2..f37f4b8 100644 (file)
@@ -2405,6 +2405,11 @@ public class CameraActivity extends QuickActivity
         return CameraServicesImpl.instance();
     }
 
+    @Override
+    public FatalErrorHandler getFatalErrorHandler() {
+        return new FatalErrorHandlerImpl(this);
+    }
+
     public List<String> getSupportedModeNames() {
         List<Integer> indices = mModuleManager.getSupportedModeIndexList();
         List<String> supported = new ArrayList<String>();
index c2d9ba0..5d86806 100644 (file)
@@ -1318,7 +1318,7 @@ public class CaptureModule extends CameraModule implements
                                     }
                                 });
                         }
-                });
+                }, mAppController.getFatalErrorHandler());
         guard.stop("mCameraManager.open()");
     }
 
diff --git a/src/com/android/camera/FatalErrorHandler.java b/src/com/android/camera/FatalErrorHandler.java
new file mode 100644 (file)
index 0000000..305202a
--- /dev/null
@@ -0,0 +1,125 @@
+/*
+ * Copyright (C) 2015 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 android.hardware.camera2.CameraDevice;
+
+import com.android.camera2.R;
+
+/**
+ * Handles fatal application errors.
+ * <p>
+ * Usage:
+ * 
+ * <pre>
+ * if (unrecoverableErrorDetected) {
+ *     fatalErrorHandler.handleFatalError(Reason.CANNOT_CONNECT_TO_CAMERA);
+ * }
+ * </pre>
+ */
+public interface FatalErrorHandler {
+    public static enum Reason {
+        CANNOT_CONNECT_TO_CAMERA(
+                R.string.cannot_connect_camera,
+                R.string.feedback_description_camera_access,
+                true),
+        CAMERA_HAL_FAILED(
+                R.string.cannot_connect_camera,
+                R.string.feedback_description_camera_access,
+                true),
+        CAMERA_DISABLED_BY_SECURITY_POLICY(
+                R.string.camera_disabled,
+                R.string.feedback_description_camera_access,
+                true),
+        MEDIA_STORAGE_FAILURE(
+                R.string.media_storage_failure,
+                R.string.feedback_description_save_photo,
+                false);
+
+        private final int mDialogMsgId;
+        private final int mFeedbackMsgId;
+        private final boolean mFinishActivity;
+
+        /**
+         * @param dialogMsgId The resource ID of string to display in the fatal
+         *            error dialog.
+         * @param feedbackMsgId The resource ID of default string to display in
+         *            the feedback dialog, if the user chooses to submit
+         *            feedback from the dialog.
+         * @param finishActivity Whether the activity should be finished as a
+         *            result of this error.
+         */
+        Reason(int dialogMsgId, int feedbackMsgId, boolean finishActivity) {
+            mDialogMsgId = dialogMsgId;
+            mFeedbackMsgId = feedbackMsgId;
+            mFinishActivity = finishActivity;
+        }
+
+        /**
+         * @return The resource ID of the string to display in the fatal error
+         *         dialog.
+         */
+        public int getFeedbackMsgId() {
+            return mFeedbackMsgId;
+        }
+
+        /**
+         * @return The resource ID of the default string to display in the
+         *         feedback dialog, if the user chooses to submit feedback from
+         *         the dialog.
+         */
+        public int getDialogMsgId() {
+            return mDialogMsgId;
+        }
+
+        /**
+         * @return Whether the activity should be finished as a result of this
+         *         error.
+         */
+        public boolean doesFinishActivity() {
+            return mFinishActivity;
+        }
+
+        /**
+         * Creates a new Reason based on an error code for
+         * {@link CameraDevice.StateCallback#onError}.
+         *
+         * @param error The error code. One of
+         *            CameraDevice.StateCallback.ERROR_*
+         * @return The appropriate Reason.
+         */
+        public static Reason fromCamera2CameraDeviceStateCallbackError(int error) {
+            // TODO Use a more descriptive reason to distinguish between
+            // different types of errors.
+            switch (error) {
+                case CameraDevice.StateCallback.ERROR_CAMERA_DEVICE:
+                case CameraDevice.StateCallback.ERROR_CAMERA_DISABLED:
+                case CameraDevice.StateCallback.ERROR_CAMERA_IN_USE:
+                case CameraDevice.StateCallback.ERROR_CAMERA_SERVICE:
+                case CameraDevice.StateCallback.ERROR_MAX_CAMERAS_IN_USE:
+                default:
+                    return CANNOT_CONNECT_TO_CAMERA;
+            }
+        }
+    }
+
+    /**
+     * Handles a fatal error, e.g. by displaying the appropriate dialog and
+     * exiting the activity.
+     */
+    public void handleFatalError(Reason reason);
+}
diff --git a/src/com/android/camera/FatalErrorHandlerImpl.java b/src/com/android/camera/FatalErrorHandlerImpl.java
new file mode 100644 (file)
index 0000000..40b064e
--- /dev/null
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2015 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 android.app.Activity;
+
+import com.android.camera.debug.Log;
+import com.android.camera.util.CameraUtil;
+
+public final class FatalErrorHandlerImpl implements FatalErrorHandler {
+    private static final Log.Tag TAG = new Log.Tag("FatalErrorHandler");
+
+    private final Activity mActivity;
+
+    public FatalErrorHandlerImpl(Activity activity) {
+        mActivity = activity;
+    }
+
+    @Override
+    public void handleFatalError(Reason reason) {
+        // Log a stack trace to be sure we can track the source.
+        Log.e(TAG, "Handling Fatal Error:", new Exception());
+
+        boolean finishActivity = reason.doesFinishActivity();
+        CameraUtil.showError(mActivity, reason.getDialogMsgId(), reason.getFeedbackMsgId(),
+                finishActivity);
+    }
+}
index 6c0bd22..47ac90e 100644 (file)
@@ -29,6 +29,7 @@ import android.view.View;
 import android.widget.FrameLayout;
 
 import com.android.camera.ButtonManager;
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.SoundPlayer;
 import com.android.camera.module.ModuleController;
 import com.android.camera.one.OneCameraManager;
@@ -380,6 +381,11 @@ public interface AppController {
     public CameraServices getServices();
 
     /**
+     * @return The error handler to invoke for fatal (app-killing) errors.
+     */
+    public FatalErrorHandler getFatalErrorHandler();
+
+    /**
      * Returns the {@link com.android.camera.app.CameraAppUI}.
      *
      * @return {@code null} if not available yet.
index 9eaa61d..5e9a977 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.camera.captureintent.resource;
 
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.app.AppController;
 import com.android.camera.app.LocationManager;
 import com.android.camera.app.OrientationManager;
@@ -117,4 +118,11 @@ public interface ResourceConstructed extends SafeCloseable {
      */
     @Deprecated
     public AppController getAppController();
+
+    /**
+     * Obtains the fatal error handler.
+     *
+     * @return An {@link FatalErrorHandler} object.
+     */
+    public FatalErrorHandler getFatalErrorHandler();
 }
index a6c0267..16991c8 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.camera.captureintent.resource;
 
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.app.AppController;
 import com.android.camera.app.LocationManager;
 import com.android.camera.app.OrientationManager;
@@ -43,6 +44,7 @@ public final class ResourceConstructedImpl implements ResourceConstructed {
     private final ResolutionSetting mResolutionSetting;
     private final HandlerThread mCameraThread;
     private final Handler mCameraHandler;
+    private final FatalErrorHandler mFatalErrorHandler;
 
     // TODO: Hope one day we could get rid of AppController.
     private final AppController mAppController;
@@ -60,10 +62,12 @@ public final class ResourceConstructedImpl implements ResourceConstructed {
             OrientationManager orientationManager,
             CameraFacingSetting cameraFacingSetting,
             ResolutionSetting resolutionSetting,
-            AppController appController) {
+            AppController appController,
+            FatalErrorHandler fatalErrorHandler) {
         return new RefCountBase<ResourceConstructed>(new ResourceConstructedImpl(
                 intent, moduleUI, mainThread, context, cameraManager, locationManager,
-                orientationManager, cameraFacingSetting, resolutionSetting, appController));
+                orientationManager, cameraFacingSetting, resolutionSetting,
+                appController, fatalErrorHandler));
     }
 
     private ResourceConstructedImpl(
@@ -76,7 +80,8 @@ public final class ResourceConstructedImpl implements ResourceConstructed {
             OrientationManager orientationManager,
             CameraFacingSetting cameraFacingSetting,
             ResolutionSetting resolutionSetting,
-            AppController appController) {
+            AppController appController,
+            FatalErrorHandler fatalErrorHandler) {
         mIntent = intent;
         mModuleUI = moduleUI;
         mMainThread = mainThread;
@@ -86,6 +91,7 @@ public final class ResourceConstructedImpl implements ResourceConstructed {
         mOrientationManager = orientationManager;
         mCameraFacingSetting = cameraFacingSetting;
         mResolutionSetting = resolutionSetting;
+        mFatalErrorHandler = fatalErrorHandler;
         mAppController = appController;
 
         mCameraThread = new HandlerThread("ImageCaptureIntentModule.CameraHandler");
@@ -152,4 +158,9 @@ public final class ResourceConstructedImpl implements ResourceConstructed {
     public AppController getAppController() {
         return mAppController;
     }
+
+    @Override
+    public FatalErrorHandler getFatalErrorHandler() {
+        return mFatalErrorHandler;
+    }
 }
index e4db02c..c1e7a8b 100644 (file)
@@ -84,7 +84,8 @@ public final class StateBackground extends StateImpl {
         super(stateMachine);
         mResourceConstructed = ResourceConstructedImpl.create(
                 intent, moduleUI, mainThread, context, cameraManager, locationManager,
-                orientationManager, cameraFacingSetting, resolutionSetting, appController);
+                orientationManager, cameraFacingSetting, resolutionSetting, appController,
+                appController.getFatalErrorHandler());
         registerEventHandlers();
     }
 
index 161ad1d..bb8a322 100644 (file)
@@ -16,6 +16,7 @@
 
 package com.android.camera.captureintent.state;
 
+import com.android.camera.FatalErrorHandlerImpl;
 import com.google.common.base.Optional;
 
 import com.android.camera.SoundPlayer;
@@ -196,7 +197,8 @@ public final class StateOpeningCamera extends StateImpl {
                 imageRotationCalculator,
                 new BurstFacadeFactory.BurstFacadeStub(),
                 new SoundPlayer(mResourceConstructed.get().getContext()),
-                mCameraOpenCallback);
+                mCameraOpenCallback,
+                mResourceConstructed.get().getFatalErrorHandler());
         return Optional.absent();
     }
 
index d017ee4..719926e 100644 (file)
@@ -20,6 +20,7 @@ import android.content.Context;
 import android.os.Handler;
 import android.util.DisplayMetrics;
 
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.SoundPlayer;
 import com.android.camera.async.MainThread;
 import com.android.camera.burst.BurstFacade;
@@ -28,8 +29,6 @@ import com.android.camera.one.OneCamera.Facing;
 import com.android.camera.one.OneCamera.OpenCallback;
 import com.android.camera.one.config.OneCameraFeatureConfig;
 import com.android.camera.one.v2.photo.ImageRotationCalculator;
-import com.android.camera.settings.SettingsManager;
-import com.android.camera.util.Size;
 import com.google.common.base.Optional;
 
 /**
@@ -45,8 +44,9 @@ public abstract class OneCameraManager {
      * <p>
      * Exactly one call will always be made to a single method in the provided
      * {@link OpenCallback}.
-     *
-     * @param captureSetting the related settings to configure the camera for capture.
+     * 
+     * @param captureSetting the related settings to configure the camera for
+     *            capture.
      * @param handler the handler on which callback methods are invoked.
      * @param mainThread Main thread executor
      * @param imageRotationCalculator Image rotation calculator required for
@@ -55,6 +55,8 @@ public abstract class OneCameraManager {
      * @param soundPlayer the sound player.
      * @param openCallback this listener is called when the camera was opened or
      *            when it failed to open.
+     * @param fatalErrorHandler the fatal error handler to use for indicating
+     *            fatal errors
      */
     public abstract void open(
             OneCameraCaptureSetting captureSetting,
@@ -63,7 +65,8 @@ public abstract class OneCameraManager {
             ImageRotationCalculator imageRotationCalculator,
             BurstFacade burstController,
             SoundPlayer soundPlayer,
-            OpenCallback openCallback);
+            OpenCallback openCallback,
+            FatalErrorHandler fatalErrorHandler);
 
     // TODO: Move this to OneCameraCharacteristics class.
     /**
@@ -108,7 +111,7 @@ public abstract class OneCameraManager {
             Context context,
             DisplayMetrics displayMetrics) throws OneCameraException {
         Optional<OneCameraManager> manager = com.android.camera.one.v2.OneCameraManagerImpl.create(
-                        featureConfig, context, displayMetrics);
+                featureConfig, context, displayMetrics);
         if (!manager.isPresent()) {
             manager = com.android.camera.one.v1.OneCameraManagerImpl.create();
         }
index b7dd9f3..fd9f037 100644 (file)
@@ -19,7 +19,7 @@ package com.android.camera.one.v1;
 import android.hardware.Camera;
 import android.os.Handler;
 
-import com.android.camera.CameraActivity;
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.SoundPlayer;
 import com.android.camera.async.MainThread;
 import com.android.camera.burst.BurstFacade;
@@ -31,8 +31,6 @@ import com.android.camera.one.OneCameraCaptureSetting;
 import com.android.camera.one.OneCameraCharacteristics;
 import com.android.camera.one.OneCameraManager;
 import com.android.camera.one.v2.photo.ImageRotationCalculator;
-import com.android.camera.settings.SettingsManager;
-import com.android.camera.util.Size;
 import com.google.common.base.Optional;
 /**
  * The {@link OneCameraManager} implementation on top of the Camera API 1.
@@ -102,7 +100,7 @@ public class OneCameraManagerImpl extends OneCameraManager {
             ImageRotationCalculator imageRotationCalculator,
             BurstFacade burstController,
             SoundPlayer soundPlayer,
-            OpenCallback openCallback) {
+            OpenCallback openCallback, FatalErrorHandler fatalErrorHandler) {
         throw new RuntimeException("Not implemented yet.");
     }
 
index b9eaf40..bbc357f 100644 (file)
 
 package com.android.camera.one.v2;
 
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.async.MainThread;
 import com.android.camera.async.Observable;
 import com.android.camera.burst.BurstFacade;
 import com.android.camera.one.OneCamera;
 import com.android.camera.one.OneCameraCharacteristics;
-import com.android.camera.one.config.OneCameraFeatureConfig;
 import com.android.camera.one.config.OneCameraFeatureConfig.CaptureSupportLevel;
 import com.android.camera.one.v2.camera2proxy.CameraDeviceProxy;
 import com.android.camera.one.v2.imagesaver.ImageSaver;
@@ -37,5 +37,5 @@ public interface OneCameraFactory {
             Observable<OneCamera.PhotoCaptureParameters.Flash> flashSetting,
             Observable<Integer> exposureSetting,
             Observable<Boolean> hdrSceneSetting,
-            BurstFacade burstController);
+            BurstFacade burstController, FatalErrorHandler fatalErrorHandler);
 }
index 4cab1b0..02ab8bc 100644 (file)
@@ -26,6 +26,7 @@ import android.os.Build;
 import android.os.Handler;
 import android.util.DisplayMetrics;
 
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.SoundPlayer;
 import com.android.camera.async.MainThread;
 import com.android.camera.burst.BurstFacade;
@@ -99,7 +100,8 @@ public class OneCameraManagerImpl extends OneCameraManager {
             final ImageRotationCalculator imageRotationCalculator,
             final BurstFacade burstController,
             final SoundPlayer soundPlayer,
-            final OpenCallback openCallback) {
+            final OpenCallback openCallback,
+            final FatalErrorHandler fatalErrorHandler) {
         try {
             final String cameraId = getCameraId(captureSetting.getCameraFacing());
             Log.i(TAG, "Opening Camera ID " + cameraId);
@@ -158,7 +160,7 @@ public class OneCameraManagerImpl extends OneCameraManager {
                                     mainThread,
                                     imageRotationCalculator,
                                     burstController,
-                                    soundPlayer);
+                                    soundPlayer, fatalErrorHandler);
                             openCallback.onCameraOpened(oneCamera);
                         } catch (CameraAccessException e) {
                             Log.d(TAG, "Could not get camera characteristics");
index 7793ff8..0592cd1 100644 (file)
@@ -23,7 +23,7 @@ import android.hardware.camera2.CaptureRequest;
 import android.os.Build;
 import android.view.Surface;
 
-import com.android.camera.CaptureModule;
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.async.HandlerFactory;
 import com.android.camera.async.Lifetime;
 import com.android.camera.async.MainThread;
@@ -59,7 +59,6 @@ import com.android.camera.one.v2.sharedimagereader.ManagedImageReader;
 import com.android.camera.one.v2.sharedimagereader.SharedImageReaderFactory;
 import com.android.camera.util.Provider;
 import com.android.camera.util.Size;
-import com.google.common.base.Function;
 import com.google.common.base.Supplier;
 
 import java.util.ArrayList;
@@ -100,7 +99,7 @@ public class SimpleOneCameraFactory implements OneCameraFactory {
             final Observable<OneCamera.PhotoCaptureParameters.Flash> flashSetting,
             final Observable<Integer> exposureSetting,
             final Observable<Boolean> hdrSceneSetting,
-            final BurstFacade burstFacade) {
+            final BurstFacade burstFacade, FatalErrorHandler fatalErrorHandler) {
         final Lifetime lifetime = new Lifetime();
 
         final ImageReaderProxy imageReader = new CloseWhenDoneImageReader(new LoggingImageReader(
index 7af04f2..62059ef 100644 (file)
@@ -21,6 +21,7 @@ import android.hardware.camera2.CaptureRequest;
 import android.util.Range;
 import android.view.Surface;
 
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.async.HandlerFactory;
 import com.android.camera.async.Lifetime;
 import com.android.camera.async.MainThread;
@@ -48,8 +49,9 @@ import com.android.camera.one.v2.common.SimpleCaptureStream;
 import com.android.camera.one.v2.core.FrameServer;
 import com.android.camera.one.v2.core.FrameServerFactory;
 import com.android.camera.one.v2.core.RequestTemplate;
+import com.android.camera.one.v2.core.ResponseListener;
 import com.android.camera.one.v2.core.ResponseListeners;
-import com.android.camera.one.v2.errorhandling.RepeatFailureDetector;
+import com.android.camera.one.v2.errorhandling.RepeatFailureHandlerComponent;
 import com.android.camera.one.v2.imagesaver.ImageSaver;
 import com.android.camera.one.v2.initialization.CameraStarter;
 import com.android.camera.one.v2.initialization.InitializedOneCameraFactory;
@@ -59,7 +61,6 @@ import com.android.camera.stats.UsageStatistics;
 import com.android.camera.util.ApiHelper;
 import com.android.camera.util.Provider;
 import com.android.camera.util.Size;
-import com.google.common.base.Function;
 import com.google.common.base.Supplier;
 
 import java.util.ArrayList;
@@ -106,11 +107,13 @@ public class ZslOneCameraFactory implements OneCameraFactory {
             final OneCameraCharacteristics characteristics,
             CaptureSupportLevel featureConfig,
             final MainThread mainThread,
-            Size pictureSize, final ImageSaver.Builder imageSaverBuilder,
+            Size pictureSize,
+            final ImageSaver.Builder imageSaverBuilder,
             final Observable<OneCamera.PhotoCaptureParameters.Flash> flashSetting,
             final Observable<Integer> exposureSetting,
             final Observable<Boolean> hdrSceneSetting,
-            final BurstFacade burstFacade) {
+            final BurstFacade burstFacade,
+            final FatalErrorHandler fatalErrorHandler) {
         final Lifetime lifetime = new Lifetime();
 
         final ImageReaderProxy imageReader = new CloseWhenDoneImageReader(
@@ -201,23 +204,25 @@ public class ZslOneCameraFactory implements OneCameraFactory {
                         sharedImageReaderFactory.provideMetadataPool(), flashSetting);
                 BurstTaker burstTaker = new BurstTakerImpl(cameraCommandExecutor, frameServer,
                         rootBuilder, sharedImageReaderFactory.provideSharedImageReader(),
-                        burstFacade.getInputSurface(),basicCameraFactory.providePreviewStarter(),
-                        // ImageReader#acquireLatestImage() requires two images as the margin so
-                        // specify that as the maximum number of images that can be used by burst.
+                        burstFacade.getInputSurface(), basicCameraFactory.providePreviewStarter(),
+                        // ImageReader#acquireLatestImage() requires two images
+                        // as the margin so
+                        // specify that as the maximum number of images that can
+                        // be used by burst.
                         mMaxImageCount - 2);
 
                 burstFacade.setBurstTaker(burstTaker);
 
                 if (isBackCamera && ApiHelper.IS_NEXUS_5) {
                     // Workaround for bug: 19061883
-                    RepeatFailureDetector failureDetector = new RepeatFailureDetector(
-                            Loggers.tagFactory(), UsageStatistics.instance(),
-                            cameraCaptureSession, cameraCommandExecutor, basicCameraFactory
-                            .providePreviewStarter(), 10);
+                    ResponseListener failureDetector = RepeatFailureHandlerComponent.create(
+                            Loggers.tagFactory(),
+                            fatalErrorHandler, cameraCaptureSession, cameraCommandExecutor,
+                            basicCameraFactory.providePreviewStarter(),
+                            UsageStatistics.instance(), 10).provideResponseListener();
                     rootBuilder.addResponseListener(failureDetector);
                 }
 
-
                 final Observable<Integer> availableImageCount = sharedImageReaderFactory
                         .provideAvailableImageCount();
                 final Observable<Boolean> frameServerAvailability = frameServerComponent
diff --git a/src/com/android/camera/one/v2/errorhandling/FailureHandler.java b/src/com/android/camera/one/v2/errorhandling/FailureHandler.java
new file mode 100644 (file)
index 0000000..0067cff
--- /dev/null
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2015 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.v2.errorhandling;
+
+import android.hardware.camera2.CameraCaptureSession;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+/**
+ * Represents a strategy for dealing with repeat failure (multiple consecutive
+ * camera frames resulting in
+ * {@link CameraCaptureSession.CaptureCallback#onCaptureFailed}).
+ */
+@ParametersAreNonnullByDefault
+interface FailureHandler extends Runnable {
+}
diff --git a/src/com/android/camera/one/v2/errorhandling/FastCameraReset.java b/src/com/android/camera/one/v2/errorhandling/FastCameraReset.java
new file mode 100644 (file)
index 0000000..35d1518
--- /dev/null
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2015 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.v2.errorhandling;
+
+import android.hardware.camera2.CameraAccessException;
+
+import com.android.camera.debug.Log;
+import com.android.camera.debug.Logger;
+import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionClosedException;
+import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
+import com.android.camera.one.v2.commands.CameraCommandExecutor;
+import com.android.camera.stats.UsageStatistics;
+import com.google.common.logging.eventprotos;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+/**
+ * Resets camera usage by calling abortCaptures(), flushing (interrupting) any
+ * currently-executing camera commands, and restarting the preview.
+ * <p>
+ * Workaround for Bug: 19061883
+ */
+@ParametersAreNonnullByDefault
+final class FastCameraReset implements FailureHandler {
+    private final Logger mLog;
+    private final CameraCaptureSessionProxy mCaptureSession;
+    private final CameraCommandExecutor mCommandExecutor;
+    private final Runnable mPreviewStarter;
+    private final UsageStatistics mUsageStats;
+
+    FastCameraReset(Logger.Factory logFactory, CameraCaptureSessionProxy captureSession,
+            CameraCommandExecutor commandExecutor, Runnable previewStarter,
+            UsageStatistics usageStats) {
+        mLog = logFactory.create(new Log.Tag("FastCameraReset"));
+        mCaptureSession = captureSession;
+        mCommandExecutor = commandExecutor;
+        mPreviewStarter = previewStarter;
+        mUsageStats = usageStats;
+    }
+
+    @Override
+    public void run() {
+        // TODO: Replace UNKNOWN_REASON with enum for this error.
+        mUsageStats.cameraFailure(eventprotos.CameraFailure.FailureReason.UNKNOWN_REASON,
+                "api2_repeated_failure", UsageStatistics.NONE, UsageStatistics.NONE);
+
+        mLog.w("beginning reset()");
+        try {
+            mLog.w("abortCaptures()");
+            mCaptureSession.abortCaptures();
+        } catch (CameraAccessException e) {
+            e.printStackTrace();
+        } catch (CameraCaptureSessionClosedException e) {
+            e.printStackTrace();
+        }
+        mLog.w("flushing existing camera commands");
+        mCommandExecutor.flush();
+        mLog.w("restarting the preview");
+        mPreviewStarter.run();
+        mLog.w("finished reset()");
+    }
+}
diff --git a/src/com/android/camera/one/v2/errorhandling/FatalErrorDialogFailureHandler.java b/src/com/android/camera/one/v2/errorhandling/FatalErrorDialogFailureHandler.java
new file mode 100644 (file)
index 0000000..605381c
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2015 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.v2.errorhandling;
+
+import com.android.camera.FatalErrorHandler;
+import com.android.camera.stats.UsageStatistics;
+import com.google.common.logging.eventprotos;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+/**
+ * Handles repeat failure by displaying the fatal error dialog (which also
+ * finishes the activity).
+ */
+@ParametersAreNonnullByDefault
+final class FatalErrorDialogFailureHandler implements FailureHandler {
+    private final FatalErrorHandler mFatalErrorHandler;
+    private final UsageStatistics mUsageStats;
+
+    FatalErrorDialogFailureHandler(FatalErrorHandler fatalErrorHandler,
+            UsageStatistics usageStats) {
+        mFatalErrorHandler = fatalErrorHandler;
+        mUsageStats = usageStats;
+    }
+
+    @Override
+    public void run() {
+        mUsageStats.cameraFailure(eventprotos.CameraFailure.FailureReason.UNKNOWN_REASON,
+                "api2_repeated_failure_2", UsageStatistics.NONE, UsageStatistics.NONE);
+        // TODO Add another {@link FatalErrorHandler.Reason} for this situation
+        mFatalErrorHandler.handleFatalError(FatalErrorHandler.Reason.CANNOT_CONNECT_TO_CAMERA);
+    }
+}
diff --git a/src/com/android/camera/one/v2/errorhandling/RecoverySuccessCallback.java b/src/com/android/camera/one/v2/errorhandling/RecoverySuccessCallback.java
new file mode 100644 (file)
index 0000000..fee2fd8
--- /dev/null
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2015 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.v2.errorhandling;
+
+import com.android.camera.stats.UsageStatistics;
+import com.android.camera.util.Callback;
+import com.google.common.logging.eventprotos;
+
+import javax.annotation.Nonnull;
+import javax.annotation.ParametersAreNonnullByDefault;
+
+@ParametersAreNonnullByDefault
+final class RecoverySuccessCallback implements Callback<String> {
+    private final UsageStatistics mUsageStats;
+
+    RecoverySuccessCallback(UsageStatistics usageStats) {
+        mUsageStats = usageStats;
+    }
+
+    @Override
+    public void onCallback(@Nonnull String successfulRecoveryStrategyName) {
+        // Log Success
+        mUsageStats.cameraFailure(eventprotos.CameraFailure.FailureReason.UNKNOWN_REASON,
+                "api2_repeated_failure_recovery_success", UsageStatistics.NONE, UsageStatistics
+                        .NONE);
+    }
+}
index f3fdedf..7daeaa6 100644 (file)
 
 package com.android.camera.one.v2.errorhandling;
 
-import android.hardware.camera2.CameraAccessException;
-import android.hardware.camera2.CameraCaptureSession;
 import android.hardware.camera2.CaptureFailure;
 import android.hardware.camera2.TotalCaptureResult;
 
 import com.android.camera.debug.Log;
 import com.android.camera.debug.Logger;
-import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionClosedException;
-import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
-import com.android.camera.one.v2.commands.CameraCommandExecutor;
-import com.android.camera.one.v2.core.ResponseListener;
-import com.android.camera.stats.UsageStatistics;
-import com.google.common.logging.eventprotos;
+import com.android.camera.util.Callback;
 
-import java.net.UnknownServiceException;
+import java.util.List;
 
 import javax.annotation.ParametersAreNonnullByDefault;
 
 /**
- * Listens for repeated capture failure and resets the camera by calling
- * {@link CameraCaptureSession#abortCaptures()}, flushing/interrupting any
- * currently-running camera interactions, and restarting the preview.
+ * Listens for repeated capture failure and invokes recovery strategies,
+ * in-order as the repeated failures continue.
  * <p>
  * Workaround for bug: 19061883
  */
 @ParametersAreNonnullByDefault
-public final class RepeatFailureDetector extends ResponseListener {
+final class RepeatFailureDetector extends com.android.camera.one.v2.core.ResponseListener {
     private final Logger mLog;
-    private final UsageStatistics mUsageStats;
-    private final CameraCaptureSessionProxy mCaptureSession;
-    private final CameraCommandExecutor mCommandExecutor;
-    private final Runnable mPreviewStarter;
     private final int mConsecutiveFailureThreshold;
+    private final List<FailureHandler> mRecoveryStrategies;
+    private final Callback<String> mRecoverySuccessCallback;
+    /**
+     * Indicates the number of consecutive times repeat failure has been
+     * detected.
+     * <p>
+     * 0 indicates normal operation
+     * <p>
+     * Positive values also indicate the index of the recovery strategy which
+     * has been used.
+     */
+    private int mFailureLevel;
+    /**
+     * The frame number of the failure which resulted in a recovery strategy
+     * being invoked. This is used to determine if a frame success corresponds
+     * to a frame from before or after the recovery strategy was run.
+     * <p>
+     * This
+     */
+    private long mFailureFrameNumber;
+    /**
+     * The number of consecutive
+     */
     private int mConsecutiveErrorCount;
-    private boolean mExecutedReset;
 
     /**
      * @param logFactory Used for logging.
-     * @param usageStats Used for logging.
-     * @param captureSession The camera capture session to abort captures upon
-     *            repeated failure.
-     * @param commandExecutor The command executor to flush upon repeated
-     *            failure.
-     * @param previewStarter Used for restarting the preview.
      * @param consecutiveFailureThreshold The number of consecutive failures to
-     *            classify as a repeat-failure.
+     *            consider a "repeat failure".
+     * @param recoveryStrategies A list of strategies to try to recover from or
+     *            handle (in other ways) a repeat failure. Strategies are
+     *            invoked in-order each time the number of consecutive failures
+     *            reaches over the threshold. That is, the Nth strategy is
+     *            invoked after N * consecutiveFailureThreshold consecutive
+     *            failures are detected.
+     * @param recoverySuccessCallback Invoked upon success of a recovery
+     *            strategy, with the string name of the recovery strategy which
+     *            worked.
      */
     public RepeatFailureDetector(Logger.Factory logFactory,
-            UsageStatistics usageStats, CameraCaptureSessionProxy captureSession,
-            CameraCommandExecutor commandExecutor, Runnable previewStarter,
-            int consecutiveFailureThreshold) {
-        mConsecutiveFailureThreshold = consecutiveFailureThreshold;
+            int consecutiveFailureThreshold, List<FailureHandler> recoveryStrategies,
+            Callback<String> recoverySuccessCallback) {
         mLog = logFactory.create(new Log.Tag("RepeatFailureDtctr"));
-        mUsageStats = usageStats;
-        mCaptureSession = captureSession;
-        mCommandExecutor = commandExecutor;
-        mPreviewStarter = previewStarter;
 
-        mConsecutiveErrorCount = 0;
-        mExecutedReset = false;
-    }
+        mConsecutiveFailureThreshold = consecutiveFailureThreshold;
+        mRecoveryStrategies = recoveryStrategies;
+        mRecoverySuccessCallback = recoverySuccessCallback;
 
-    @Override
-    public void onCompleted(TotalCaptureResult result) {
+        mFailureLevel = 0;
         mConsecutiveErrorCount = 0;
+        mFailureFrameNumber = -1;
     }
 
     @Override
-    public void onSequenceAborted(int sequenceId) {
-        mExecutedReset = false;
+    public void onCompleted(TotalCaptureResult result) {
         mConsecutiveErrorCount = 0;
-    }
-
-    @Override
-    public void onSequenceCompleted(int sequenceId, long frameNumber) {
-
+        if (mFailureLevel > 0) {
+            if (result.getFrameNumber() > mFailureFrameNumber) {
+                // Success! Recovery worked, and a frame was completed
+                // successfully.
+                mRecoverySuccessCallback.onCallback(mRecoveryStrategies.get(mFailureLevel)
+                        .toString());
+                mFailureLevel = 0;
+                mFailureFrameNumber = -1;
+            }
+        }
     }
 
     @Override
     public void onFailed(CaptureFailure failure) {
         if (failure.getReason() == CaptureFailure.REASON_ERROR) {
             mConsecutiveErrorCount++;
-            mLog.e("onCaptureFailed() REASON_ERROR:  Consecutive error count = " +
-                    mConsecutiveErrorCount);
-            if (mConsecutiveErrorCount >= mConsecutiveFailureThreshold && !
-                    mExecutedReset) {
-                mLog.e("onCaptureFailed() REASON_ERROR:  Running repeat error callback");
-                // TODO: Replace UNKNOWN_REASON with enum for this error.
-                mUsageStats.cameraFailure(eventprotos.CameraFailure.FailureReason.UNKNOWN_REASON,
-                        "api2_repeated_failure", UsageStatistics.NONE, UsageStatistics.NONE);
-                mExecutedReset = true;
-                reset();
+            mLog.e(String.format("onCaptureFailed() REASON_ERROR:  Consecutive error count = %d x" +
+                    " %d", mConsecutiveErrorCount, mFailureLevel));
+            if (mConsecutiveErrorCount >= mConsecutiveFailureThreshold) {
+                mConsecutiveErrorCount = 0;
+                mFailureFrameNumber = failure.getFrameNumber();
+                if (mFailureLevel < mRecoveryStrategies.size()) {
+                    mLog.e(String.format("onCaptureFailed() REASON_ERROR:  Repeat failure " +
+                            "detected (x%d).  Attempting recovery strategy:  %s",
+                            mConsecutiveErrorCount, mRecoveryStrategies.get(mFailureLevel)
+                                    .toString()));
+                    mRecoveryStrategies.get(mFailureLevel).run();
+                }
+                mFailureLevel++;
             }
         }
     }
-
-    private void reset() {
-        mLog.w("beginning reset()");
-        try {
-            mLog.w("abortCaptures()");
-            mCaptureSession.abortCaptures();
-        } catch (CameraAccessException e) {
-            e.printStackTrace();
-        } catch (CameraCaptureSessionClosedException e) {
-            e.printStackTrace();
-        }
-        mLog.w("flushing existing camera commands");
-        mCommandExecutor.flush();
-        mLog.w("restarting the preview");
-        mPreviewStarter.run();
-        mLog.w("finished reset()");
-    }
 }
diff --git a/src/com/android/camera/one/v2/errorhandling/RepeatFailureHandlerComponent.java b/src/com/android/camera/one/v2/errorhandling/RepeatFailureHandlerComponent.java
new file mode 100644 (file)
index 0000000..a3c325a
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2015 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.v2.errorhandling;
+
+import com.android.camera.FatalErrorHandler;
+import com.android.camera.debug.Logger;
+import com.android.camera.one.v2.camera2proxy.CameraCaptureSessionProxy;
+import com.android.camera.one.v2.commands.CameraCommandExecutor;
+import com.android.camera.one.v2.core.ResponseListener;
+import com.android.camera.stats.UsageStatistics;
+
+import java.util.Arrays;
+import java.util.List;
+
+import javax.annotation.ParametersAreNonnullByDefault;
+
+/**
+ * Creates a ResponseListener which will handle repeat failures.
+ * <p>
+ * This is to workaround bug: 19061883
+ */
+@ParametersAreNonnullByDefault
+public final class RepeatFailureHandlerComponent {
+    private final RepeatFailureDetector mRepeatFailureHandler;
+
+    private RepeatFailureHandlerComponent(RepeatFailureDetector repeatFailureHandler) {
+        mRepeatFailureHandler = repeatFailureHandler;
+    }
+
+    public ResponseListener provideResponseListener() {
+        return mRepeatFailureHandler;
+    }
+
+    public static RepeatFailureHandlerComponent create(Logger.Factory logFactory,
+            FatalErrorHandler fatalErrorHandler, CameraCaptureSessionProxy captureSession,
+            CameraCommandExecutor commandExecutor, Runnable previewStarter,
+            UsageStatistics usageStats, int consecutiveFailureThreshold) {
+        FastCameraReset fastCameraReset = new FastCameraReset(logFactory, captureSession,
+                commandExecutor, previewStarter, usageStats);
+        FatalErrorDialogFailureHandler fatalErrorDialog = new FatalErrorDialogFailureHandler
+                (fatalErrorHandler, usageStats);
+
+        RecoverySuccessCallback recoverySuccessCallback = new RecoverySuccessCallback(usageStats);
+
+        List<FailureHandler> recoveryStrategies = Arrays.asList(fastCameraReset, fatalErrorDialog);
+        RepeatFailureDetector failureDetector = new RepeatFailureDetector(logFactory,
+                consecutiveFailureThreshold,
+                recoveryStrategies,
+                recoverySuccessCallback);
+        return new RepeatFailureHandlerComponent(failureDetector);
+    }
+}
index 123878f..23a5d7b 100644 (file)
@@ -49,6 +49,7 @@ import android.widget.Toast;
 
 import com.android.camera.CameraActivity;
 import com.android.camera.CameraDisabledException;
+import com.android.camera.FatalErrorHandler;
 import com.android.camera.debug.Log;
 import com.android.camera2.R;
 import com.android.ex.camera2.portability.CameraCapabilities;
@@ -283,7 +284,9 @@ public class CameraUtil {
     /**
      * Shows custom error dialog. Designed specifically
      * for the scenario where the camera cannot be attached.
+     * @deprecated Use {@link FatalErrorHandler} instead.
      */
+    @Deprecated
     public static void showError(final Activity activity, final int dialogMsgId, final int feedbackMsgId,
                                  final boolean finishActivity) {
         final DialogInterface.OnClickListener buttonListener =