Show dialog when camera device is not connected.

  bug:10726516

Change-Id: I3d3433d0b2eced54027b19910473fd55135d0e1c
diff --git a/src/com/android/camera/AndroidCameraManagerImpl.java b/src/com/android/camera/AndroidCameraManagerImpl.java
index 00fe905..bed7ba7 100644
--- a/src/com/android/camera/AndroidCameraManagerImpl.java
+++ b/src/com/android/camera/AndroidCameraManagerImpl.java
@@ -202,6 +202,10 @@
                             if (mParamsToSet == null) {
                                 mParamsToSet = mCamera.getParameters();
                             }
+                        } else {
+                            if (msg.obj != null) {
+                                ((CameraOpenErrorCallback) msg.obj).onDeviceOpenFailure(msg.arg1);
+                            }
                         }
                         return;
 
@@ -336,8 +340,11 @@
     }
 
     @Override
-    public CameraManager.CameraProxy cameraOpen(int cameraId) {
-        mCameraHandler.obtainMessage(OPEN_CAMERA, cameraId, 0).sendToTarget();
+    public CameraManager.CameraProxy cameraOpen(
+        Handler handler, int cameraId, CameraOpenErrorCallback callback) {
+        mCameraHandler.obtainMessage(OPEN_CAMERA, cameraId, 0,
+                CameraOpenErrorCallbackForward.getNewInstance(
+                        handler, callback)).sendToTarget();
         mCameraHandler.waitDone();
         if (mCamera != null) {
             return new AndroidCameraProxyImpl();
@@ -347,8 +354,10 @@
     }
 
     /**
-     * A class which implements {@link CameraManager.CameraProxy} and 
+     * A class which implements {@link CameraManager.CameraProxy} and
      * camera handler thread.
+     * TODO: Save the handler for the callback here to avoid passing the same
+     * handler multiple times.
      */
     public class AndroidCameraProxyImpl implements CameraManager.CameraProxy {
 
@@ -370,12 +379,18 @@
         }
 
         @Override
-        public void reconnect() throws IOException {
+        public boolean reconnect(Handler handler, CameraOpenErrorCallback cb) {
             mCameraHandler.sendEmptyMessage(RECONNECT);
             mCameraHandler.waitDone();
+            CameraOpenErrorCallback cbforward =
+                    CameraOpenErrorCallbackForward.getNewInstance(handler, cb);
             if (mReconnectIOException != null) {
-                throw mReconnectIOException;
+                if (cbforward != null) {
+                    cbforward.onReconnectionFailure(AndroidCameraManagerImpl.this);
+                }
+                return false;
             }
+            return true;
         }
 
         @Override
@@ -776,4 +791,65 @@
             });
         }
     }
+
+    /**
+     * A callback helps to invoke the original callback on another
+     * {@link android.os.Handler}.
+     */
+    private static class CameraOpenErrorCallbackForward implements CameraOpenErrorCallback {
+        private final Handler mHandler;
+        private final CameraOpenErrorCallback mCallback;
+
+        /**
+         * Returns a new instance of {@link FaceDetectionCallbackForward}.
+         *
+         * @param handler The handler in which the callback will be invoked in.
+         * @param cb The callback to be invoked.
+         * @return The instance of the {@link FaceDetectionCallbackForward}, or
+         *         null if any parameter is null.
+         */
+        public static CameraOpenErrorCallbackForward getNewInstance(
+                Handler handler, CameraOpenErrorCallback cb) {
+            if (handler == null || cb == null) {
+                return null;
+            }
+            return new CameraOpenErrorCallbackForward(handler, cb);
+        }
+
+        private CameraOpenErrorCallbackForward(
+                Handler h, CameraOpenErrorCallback cb) {
+            mHandler = h;
+            mCallback = cb;
+        }
+
+        @Override
+        public void onCameraDisabled(final int cameraId) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onCameraDisabled(cameraId);
+                }
+            });
+        }
+
+        @Override
+        public void onDeviceOpenFailure(final int cameraId) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onDeviceOpenFailure(cameraId);
+                }
+            });
+        }
+
+        @Override
+        public void onReconnectionFailure(final CameraManager mgr) {
+            mHandler.post(new Runnable() {
+                @Override
+                public void run() {
+                    mCallback.onReconnectionFailure(mgr);
+                }
+            });
+        }
+    }
 }
diff --git a/src/com/android/camera/CameraActivity.java b/src/com/android/camera/CameraActivity.java
index fe65c02..3638aac 100644
--- a/src/com/android/camera/CameraActivity.java
+++ b/src/com/android/camera/CameraActivity.java
@@ -19,10 +19,12 @@
 import android.animation.Animator;
 import android.app.ActionBar;
 import android.app.Activity;
+import android.app.AlertDialog;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
 import android.content.ContentResolver;
 import android.content.Context;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
@@ -75,6 +77,8 @@
 import com.android.camera.util.PhotoSphereHelper.PanoramaViewHelper;
 import com.android.camera2.R;
 
+import static com.android.camera.CameraManager.CameraOpenErrorCallback;
+
 public class CameraActivity extends Activity
         implements ModuleSwitcher.ModuleSwitchListener {
 
@@ -203,6 +207,27 @@
         }
     };
 
+    private CameraOpenErrorCallback mCameraOpenErrorCallback =
+            new CameraOpenErrorCallback() {
+                @Override
+                public void onCameraDisabled(int cameraId) {
+                    CameraUtil.showErrorAndFinish(CameraActivity.this,
+                            R.string.camera_disabled);
+                }
+
+                @Override
+                public void onDeviceOpenFailure(int cameraId) {
+                    CameraUtil.showErrorAndFinish(CameraActivity.this,
+                            R.string.cannot_connect_camera);
+                }
+
+                @Override
+                public void onReconnectionFailure(CameraManager mgr) {
+                    CameraUtil.showErrorAndFinish(CameraActivity.this,
+                            R.string.cannot_connect_camera);
+                }
+            };
+
     // close activity when screen turns off
     private BroadcastReceiver mScreenOffReceiver = new BroadcastReceiver() {
         @Override
@@ -1254,4 +1279,8 @@
         return (mCurrentModule instanceof VideoModule) ?
                 ((VideoModule) mCurrentModule).isRecording() : false;
     }
+
+    public CameraOpenErrorCallback getCameraOpenErrorCallback() {
+        return mCameraOpenErrorCallback;
+    }
 }
diff --git a/src/com/android/camera/CameraButtonIntentReceiver.java b/src/com/android/camera/CameraButtonIntentReceiver.java
index a65942d..253105a 100644
--- a/src/com/android/camera/CameraButtonIntentReceiver.java
+++ b/src/com/android/camera/CameraButtonIntentReceiver.java
@@ -38,7 +38,9 @@
         CameraHolder holder = CameraHolder.instance();
         ComboPreferences pref = new ComboPreferences(context);
         int cameraId = CameraSettings.readPreferredCameraId(pref);
-        if (holder.tryOpen(cameraId) == null) return;
+        if (holder.tryOpen(null, cameraId, null) == null) {
+            return;
+        }
 
         // We are going to launch the camera, so hold the camera for later use
         holder.keep();
diff --git a/src/com/android/camera/CameraHolder.java b/src/com/android/camera/CameraHolder.java
index 0ffc736..32eae82 100644
--- a/src/com/android/camera/CameraHolder.java
+++ b/src/com/android/camera/CameraHolder.java
@@ -188,8 +188,9 @@
         return mInfo;
     }
 
-    public synchronized CameraProxy open(int cameraId)
-            throws CameraHardwareException {
+    public synchronized CameraProxy open(
+            Handler handler, int cameraId,
+            CameraManager.CameraOpenErrorCallback cb) {
         if (DEBUG_OPEN_RELEASE) {
             collectState(cameraId, mCameraDevice);
             if (mCameraOpened) {
@@ -204,28 +205,28 @@
             mCameraId = -1;
         }
         if (mCameraDevice == null) {
-            try {
-                Log.v(TAG, "open camera " + cameraId);
-                if (mMockCameraInfo == null) {
-                    mCameraDevice = CameraManagerFactory
-                            .getAndroidCameraManager().cameraOpen(cameraId);
-                } else {
-                    if (mMockCamera == null)
-                        throw new RuntimeException();
+            Log.v(TAG, "open camera " + cameraId);
+            if (mMockCameraInfo == null) {
+                mCameraDevice = CameraManagerFactory
+                        .getAndroidCameraManager().cameraOpen(handler, cameraId, cb);
+            } else {
+                if (mMockCamera != null) {
                     mCameraDevice = mMockCamera[cameraId];
+                } else {
+                    Log.e(TAG, "MockCameraInfo found, but no MockCamera provided.");
+                    mCameraDevice = null;
                 }
-                mCameraId = cameraId;
-            } catch (RuntimeException e) {
-                Log.e(TAG, "fail to connect Camera", e);
-                throw new CameraHardwareException(e);
             }
+            if (mCameraDevice == null) {
+                Log.e(TAG, "fail to connect Camera:" + mCameraId + ", aborting.");
+                return null;
+            }
+            mCameraId = cameraId;
             mParameters = mCameraDevice.getParameters();
         } else {
-            try {
-                mCameraDevice.reconnect();
-            } catch (IOException e) {
-                Log.e(TAG, "reconnect failed.");
-                throw new CameraHardwareException(e);
+            if (!mCameraDevice.reconnect(handler, cb)) {
+                Log.e(TAG, "fail to reconnect Camera:" + mCameraId + ", aborting.");
+                return null;
             }
             mCameraDevice.setParameters(mParameters);
         }
@@ -239,17 +240,9 @@
      * Tries to open the hardware camera. If the camera is being used or
      * unavailable then return {@code null}.
      */
-    public synchronized CameraProxy tryOpen(int cameraId) {
-        try {
-            return !mCameraOpened ? open(cameraId) : null;
-        } catch (CameraHardwareException e) {
-            // In eng build, we throw the exception so that test tool
-            // can detect it and report it
-            if ("eng".equals(Build.TYPE)) {
-                throw new RuntimeException(e);
-            }
-            return null;
-        }
+    public synchronized CameraProxy tryOpen(
+            Handler handler, int cameraId, CameraManager.CameraOpenErrorCallback cb) {
+            return (!mCameraOpened ? open(handler, cameraId, cb) : null);
     }
 
     public synchronized void release() {
diff --git a/src/com/android/camera/CameraManager.java b/src/com/android/camera/CameraManager.java
index 4a8057d..07b8150 100644
--- a/src/com/android/camera/CameraManager.java
+++ b/src/com/android/camera/CameraManager.java
@@ -101,12 +101,49 @@
     }
 
     /**
+     * An interface to be called for any exception caught when opening the
+     * camera device. This error callback is different from the one defined
+     * in the framework, {@link android.hardware.Camera.ErrorCallback}, which
+     * is used after the camera is opened.
+     */
+    public interface CameraOpenErrorCallback {
+        /**
+         * Callback when {@link com.android.camera.CameraDisabledException} is
+         * caught.
+         *
+         * @param cameraId The disabled camera.
+         */
+        public void onCameraDisabled(int cameraId);
+
+        /**
+         * Callback when {@link com.android.camera.CameraHardwareException} is
+         * caught.
+         *
+         * @param cameraId The camera with the hardware failure.
+         */
+        public void onDeviceOpenFailure(int cameraId);
+
+        /**
+         * Callback when {@link java.io.IOException} is caught during
+         * {@link android.hardware.Camera#reconnect()}.
+         *
+         * @param mgr The {@link com.android.camera.CameraManager}
+         *            with the reconnect failure.
+         */
+        public void onReconnectionFailure(CameraManager mgr);
+    }
+
+    /**
      * Opens the camera of the specified ID synchronously.
      *
-     * @param cameraId      The camera ID to open.
+     * @param handler The {@link android.os.Handler} in which the callback
+     *                was handled.
+     * @param callback The callback when any error happens.
+     * @param cameraId The camera ID to open.
      * @return   An instance of {@link CameraProxy} on success. null on failure.
      */
-    public CameraProxy cameraOpen(int cameraId);
+    public CameraProxy cameraOpen(
+            Handler handler, int cameraId, CameraOpenErrorCallback callback);
 
     /**
      * An interface that takes camera operation requests and post messages to the
@@ -132,10 +169,14 @@
 
         /**
          * Reconnects to the camera device.
-         *
          * @see android.hardware.Camera#reconnect()
+         *
+         * @param handler The {@link android.os.Handler} in which the callback
+         *                was handled.
+         * @param cb The callback when any error happens.
+         * @return {@code false} on errors.
          */
-        public void reconnect() throws IOException;
+        public boolean reconnect(Handler handler, CameraOpenErrorCallback cb);
 
         /**
          * Unlocks the camera device.
@@ -180,7 +221,7 @@
         /**
          * Sets the callback for preview data.
          *
-         * @param handler    handler in which the callback was handled.
+         * @param handler    The {@link android.os.Handler} in which the callback was handled.
          * @param cb         The callback to be invoked when the preview data is available.
          * @see  android.hardware.Camera#setPreviewCallback(android.hardware.Camera.PreviewCallback)
          */
diff --git a/src/com/android/camera/PhotoModule.java b/src/com/android/camera/PhotoModule.java
index d00da19..e3bdc25 100644
--- a/src/com/android/camera/PhotoModule.java
+++ b/src/com/android/camera/PhotoModule.java
@@ -462,16 +462,14 @@
         // Restart the camera and initialize the UI. From onCreate.
         mPreferences.setLocalId(mActivity, mCameraId);
         CameraSettings.upgradeLocalPreferences(mPreferences.getLocal());
-        try {
-            mCameraDevice = CameraUtil.openCamera(mActivity, mCameraId);
-            mParameters = mCameraDevice.getParameters();
-        } catch (CameraHardwareException e) {
-            CameraUtil.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
-            return;
-        } catch (CameraDisabledException e) {
-            CameraUtil.showErrorAndFinish(mActivity, R.string.camera_disabled);
+        mCameraDevice = CameraUtil.openCamera(
+                mActivity, mCameraId, mHandler,
+                mActivity.getCameraOpenErrorCallback());
+        if (mCameraDevice == null) {
+            Log.e(TAG, "Failed to open camera:" + mCameraId + ", aborting.");
             return;
         }
+        mParameters = mCameraDevice.getParameters();
         initializeCapabilities();
         CameraInfo info = CameraHolder.instance().getCameraInfo()[mCameraId];
         mMirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT);
@@ -1166,26 +1164,32 @@
         mPaused = false;
     }
 
-    private void prepareCamera() {
-        try {
-            // We need to check whether the activity is paused before long
-            // operations to ensure that onPause() can be done ASAP.
-            mCameraDevice = CameraUtil.openCamera(mActivity, mCameraId);
-            mParameters = mCameraDevice.getParameters();
-
-            initializeCapabilities();
-            if (mFocusManager == null) initializeFocusManager();
-            setCameraParameters(UPDATE_PARAM_ALL);
-            mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
-            mCameraPreviewParamsReady = true;
-            startPreview();
-            mOnResumeTime = SystemClock.uptimeMillis();
-            checkDisplayRotation();
-        } catch (CameraHardwareException e) {
-            mHandler.sendEmptyMessage(OPEN_CAMERA_FAIL);
-        } catch (CameraDisabledException e) {
-            mHandler.sendEmptyMessage(CAMERA_DISABLED);
+    /**
+     * Opens the camera device.
+     *
+     * @return Whether the camera was opened successfully.
+     */
+    private boolean prepareCamera() {
+        // We need to check whether the activity is paused before long
+        // operations to ensure that onPause() can be done ASAP.
+        mCameraDevice = CameraUtil.openCamera(
+                mActivity, mCameraId, mHandler,
+                mActivity.getCameraOpenErrorCallback());
+        if (mCameraDevice == null) {
+            Log.e(TAG, "Failed to open camera:" + mCameraId);
+            return false;
         }
+        mParameters = mCameraDevice.getParameters();
+
+        initializeCapabilities();
+        if (mFocusManager == null) initializeFocusManager();
+        setCameraParameters(UPDATE_PARAM_ALL);
+        mHandler.sendEmptyMessage(CAMERA_OPEN_DONE);
+        mCameraPreviewParamsReady = true;
+        startPreview();
+        mOnResumeTime = SystemClock.uptimeMillis();
+        checkDisplayRotation();
+        return true;
     }
 
 
@@ -1196,7 +1200,10 @@
         mJpegPictureCallbackTime = 0;
         mZoomValue = 0;
         resetExposureCompensation();
-        prepareCamera();
+        if (!prepareCamera()) {
+            // Camera failure.
+            return;
+        }
 
         // If first time initialization is not finished, put it in the
         // message queue.
@@ -1720,7 +1727,7 @@
     private void updateAutoFocusMoveCallback() {
         if (mParameters.getFocusMode().equals(CameraUtil.FOCUS_MODE_CONTINUOUS_PICTURE)) {
             mCameraDevice.setAutoFocusMoveCallback(mHandler,
-                    (CameraManager.CameraAFMoveCallback) mAutoFocusMoveCallback);
+                    (CameraAFMoveCallback) mAutoFocusMoveCallback);
         } else {
             mCameraDevice.setAutoFocusMoveCallback(null, null);
         }
diff --git a/src/com/android/camera/VideoModule.java b/src/com/android/camera/VideoModule.java
index 56882e1..bf5ef89 100644
--- a/src/com/android/camera/VideoModule.java
+++ b/src/com/android/camera/VideoModule.java
@@ -208,16 +208,16 @@
     }
 
     private void openCamera() {
-        try {
-            if (mCameraDevice == null) {
-                mCameraDevice = CameraUtil.openCamera(mActivity, mCameraId);
-            }
-            mParameters = mCameraDevice.getParameters();
-        } catch (CameraHardwareException e) {
-            mOpenCameraFail = true;
-        } catch (CameraDisabledException e) {
-            mCameraDisabled = true;
+        if (mCameraDevice == null) {
+            mCameraDevice = CameraUtil.openCamera(
+                    mActivity, mCameraId, mHandler,
+                    mActivity.getCameraOpenErrorCallback());
         }
+        if (mCameraDevice == null) {
+            // Error.
+            return;
+        }
+        mParameters = mCameraDevice.getParameters();
     }
 
     // This Handler is used to post message back onto the main thread of the
diff --git a/src/com/android/camera/WideAnglePanoramaModule.java b/src/com/android/camera/WideAnglePanoramaModule.java
index abdb248..59c4350 100644
--- a/src/com/android/camera/WideAnglePanoramaModule.java
+++ b/src/com/android/camera/WideAnglePanoramaModule.java
@@ -316,11 +316,19 @@
         }
     }
 
-    private void setupCamera() throws CameraHardwareException, CameraDisabledException {
-        openCamera();
+    /**
+     * Opens camera and sets the parameters.
+     *
+     * @return Whether the camera was opened successfully.
+     */
+    private boolean setupCamera() {
+        if (!openCamera()) {
+            return false;
+        }
         Parameters parameters = mCameraDevice.getParameters();
         setupCaptureParams(parameters);
         configureCamera(parameters);
+        return true;
     }
 
     private void releaseCamera() {
@@ -331,16 +339,27 @@
         }
     }
 
-    private void openCamera() throws CameraHardwareException, CameraDisabledException {
+    /**
+     * Opens the camera device. The back camera has priority over the front
+     * one.
+     *
+     * @return Whether the camera was opened successfully.
+     */
+    private boolean openCamera() {
         int cameraId = CameraHolder.instance().getBackCameraId();
         // If there is no back camera, use the first camera. Camera id starts
         // from 0. Currently if a camera is not back facing, it is front facing.
         // This is also forward compatible if we have a new facing other than
         // back or front in the future.
         if (cameraId == -1) cameraId = 0;
-        mCameraDevice = CameraUtil.openCamera(mActivity, cameraId);
+        mCameraDevice = CameraUtil.openCamera(mActivity, cameraId,
+                mMainHandler, mActivity.getCameraOpenErrorCallback());
+        if (mCameraDevice == null) {
+            return false;
+        }
         mCameraOrientation = CameraUtil.getCameraOrientation(cameraId);
         if (cameraId == CameraHolder.instance().getFrontCameraId()) mUsingFrontCamera = true;
+        return true;
     }
 
     private boolean findBestPreviewSize(List<Size> supportedSizes, boolean need4To3,
@@ -820,13 +839,8 @@
 
         mCaptureState = CAPTURE_STATE_VIEWFINDER;
 
-        try {
-            setupCamera();
-        } catch (CameraHardwareException e) {
-            CameraUtil.showErrorAndFinish(mActivity, R.string.cannot_connect_camera);
-            return;
-        } catch (CameraDisabledException e) {
-            CameraUtil.showErrorAndFinish(mActivity, R.string.camera_disabled);
+        if (!setupCamera()) {
+            Log.e(TAG, "Failed to open camera, aborting");
             return;
         }
 
diff --git a/src/com/android/camera/util/CameraUtil.java b/src/com/android/camera/util/CameraUtil.java
index ca0109a..adaaaa7 100644
--- a/src/com/android/camera/util/CameraUtil.java
+++ b/src/com/android/camera/util/CameraUtil.java
@@ -48,6 +48,7 @@
 import android.location.Location;
 import android.net.Uri;
 import android.os.Build;
+import android.os.Handler;
 import android.os.ParcelFileDescriptor;
 import android.telephony.TelephonyManager;
 import android.util.DisplayMetrics;
@@ -337,21 +338,20 @@
     }
 
     public static CameraManager.CameraProxy openCamera(
-            Activity activity, int cameraId)
-            throws CameraHardwareException, CameraDisabledException {
-        throwIfCameraDisabled(activity);
-
+            Activity activity, final int cameraId,
+            Handler handler, final CameraManager.CameraOpenErrorCallback cb) {
         try {
-            return CameraHolder.instance().open(cameraId);
-        } catch (CameraHardwareException e) {
-            // In eng build, we throw the exception so that test tool
-            // can detect it and report it
-            if ("eng".equals(Build.TYPE)) {
-                throw new RuntimeException("openCamera failed", e);
-            } else {
-                throw e;
-            }
+            throwIfCameraDisabled(activity);
+            return CameraHolder.instance().open(handler, cameraId, cb);
+        } catch (CameraDisabledException ex) {
+            handler.post(new Runnable() {
+                @Override
+                public void run() {
+                    cb.onCameraDisabled(cameraId);
+                }
+            });
         }
+        return null;
     }
 
     public static void showErrorAndFinish(final Activity activity, int msgId) {