Show error dialog when the camera hardware is busy (Eg: hijack by other
activity). We also change the behavior of the "Camera button", if the
camera is being used by others. We just ignore the request and won't start
the camera.

Change-Id: Icf3b00113aba61c9aa3f43ba07cf6944085d5d52
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 6d97c4d..ace55c0 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -17,6 +17,12 @@
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- General strings -->
 
+    <!-- title for the dialog showing the error of camera hardware -->
+    <string name="camera_error_title">Camera Error</string>
+
+    <!-- message for the dialog showing the error of camera hardware -->
+    <string name="cannot_connect_camera">Cannot connect to camera.</string>
+
     <!-- label for the icon meaning 'show me all the images' -->
     <string name="all_images">All pictures</string>
 
diff --git a/src/com/android/camera/Camera.java b/src/com/android/camera/Camera.java
index fb2e76d..5ee9128 100644
--- a/src/com/android/camera/Camera.java
+++ b/src/com/android/camera/Camera.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.Matrix;
@@ -143,6 +144,7 @@
     private ToneGenerator mFocusToneGenerator;
     private ZoomButtonsController mZoomButtons;
     private Switcher mSwitcher;
+    private boolean mStartPreviewFail = false;
 
     // mPostCaptureAlert, mLastPictureButton, mThumbController
     // are non-null only if isImageCaptureIntent() is true.
@@ -734,7 +736,12 @@
          */
         Thread startPreviewThread = new Thread(new Runnable() {
             public void run() {
-                startPreview();
+                try {
+                    mStartPreviewFail = false;
+                    startPreview();
+                } catch (CameraHardwareException e) {
+                    mStartPreviewFail = true;
+                }
             }
         });
         startPreviewThread.start();
@@ -766,6 +773,7 @@
         // Make sure preview is started.
         try {
             startPreviewThread.join();
+            if (mStartPreviewFail) showCameraErrorAndFinish();
         } catch (InterruptedException ex) {
             // ignore
         }
@@ -1009,8 +1017,13 @@
         mImageCapture = new ImageCapture();
 
         // Start the preview if it is not started.
-        if (!mPreviewing) {
-            startPreview();
+        if (!mPreviewing && !mStartPreviewFail) {
+            try {
+                startPreview();
+            } catch (CameraHardwareException e) {
+                showCameraErrorAndFinish();
+                return;
+            }
         }
 
         if (mSurfaceHolder != null) {
@@ -1254,12 +1267,17 @@
             return;
         }
 
+        // The mCameraDevice will be null if it is fail to connect to the 
+        // camera hardware. In this case we will show a dialog and then 
+        // finish the activity, so it's OK to ignore it.
+        if (mCameraDevice == null) return;
+
         mSurfaceHolder = holder;
         mViewFinderWidth = w;
         mViewFinderHeight = h;
 
         // Sometimes surfaceChanged is called after onPause. Ignore it.
-        if (mPausing) return;
+        if (mPausing || isFinishing()) return;
 
         // Set preview display if the surface is being created. Preview was
         // already started.
@@ -1293,11 +1311,10 @@
         }
     }
 
-    private boolean ensureCameraDevice() {
+    private void ensureCameraDevice() throws CameraHardwareException {
         if (mCameraDevice == null) {
             mCameraDevice = CameraHolder.instance().open();
         }
-        return mCameraDevice != null;
     }
 
     private void updateLastImage() {
@@ -1318,10 +1335,22 @@
         list.close();
     }
 
+    private void showCameraErrorAndFinish() {
+        Resources ress = getResources();
+        Util.showFatalErrorAndFinish(Camera.this,
+                ress.getString(R.string.camera_error_title),
+                ress.getString(R.string.cannot_connect_camera));
+    }
+
     private void restartPreview() {
         // make sure the surfaceview fills the whole screen when previewing
         mSurfaceView.setAspectRatio(VideoPreview.DONT_CARE);
-        startPreview();
+        try {
+            startPreview();
+        } catch (CameraHardwareException e) {
+            showCameraErrorAndFinish();
+            return;
+        }
 
         // Calculate this in advance of each shot so we don't add to shutter
         // latency. It's true that someone else could write to the SD card in
@@ -1339,12 +1368,10 @@
         }
     }
 
-    private void startPreview() {
-        if (mPausing) return;
+    private void startPreview() throws CameraHardwareException {
+        if (mPausing || isFinishing()) return;
 
-        if (!ensureCameraDevice()) return;
-
-        if (isFinishing()) return;
+        ensureCameraDevice();
 
         // If we're previewing already, stop the preview first (this will blank
         // the screen).
diff --git a/src/com/android/camera/CameraButtonIntentReceiver.java b/src/com/android/camera/CameraButtonIntentReceiver.java
index 3eae965..5926709 100644
--- a/src/com/android/camera/CameraButtonIntentReceiver.java
+++ b/src/com/android/camera/CameraButtonIntentReceiver.java
@@ -26,6 +26,16 @@
 
     @Override
     public void onReceive(Context context, Intent intent) {
+        // Try to get the camera hardware
+        try {
+            CameraHolder holder = CameraHolder.instance();
+            android.hardware.Camera device = holder.open();
+            holder.keep();
+            holder.release();
+        } catch (CameraHardwareException e) {
+            // ignore the event if camera hardware cannot be connected
+            return;
+        }
         Intent i = new Intent(Intent.ACTION_MAIN);
         i.setClass(context, Camera.class);
         i.addCategory("android.intent.category.LAUNCHER");
diff --git a/src/com/android/camera/CameraHardwareException.java b/src/com/android/camera/CameraHardwareException.java
new file mode 100644
index 0000000..f746f48
--- /dev/null
+++ b/src/com/android/camera/CameraHardwareException.java
@@ -0,0 +1,5 @@
+package com.android.camera;
+
+public class CameraHardwareException extends Exception {
+
+}
diff --git a/src/com/android/camera/CameraHolder.java b/src/com/android/camera/CameraHolder.java
index 89f02e7..98f2797 100644
--- a/src/com/android/camera/CameraHolder.java
+++ b/src/com/android/camera/CameraHolder.java
@@ -16,6 +16,8 @@
 
 package com.android.camera;
 
+import static com.android.camera.Util.Assert;
+
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.Looper;
@@ -24,8 +26,6 @@
 
 import java.io.IOException;
 
-import static com.android.camera.Util.Assert;
-
 //
 // CameraHolder is used to hold an android.hardware.Camera instance.
 //
@@ -41,7 +41,7 @@
     private static final String TAG = "CameraHolder";
     private android.hardware.Camera mCameraDevice;
     private long mKeepBeforeTime = 0;  // Keep the Camera before this time.
-    private Handler mHandler;
+    private final Handler mHandler;
     private int mUsers = 0;  // number of open() - number of release()
 
     // Use a singleton.
@@ -75,10 +75,16 @@
         mHandler = new MyHandler(ht.getLooper());
     }
 
-    public synchronized android.hardware.Camera open() {
+    public synchronized android.hardware.Camera open()
+            throws CameraHardwareException {
         Assert(mUsers == 0);
         if (mCameraDevice == null) {
-            mCameraDevice = android.hardware.Camera.open();
+            try {
+                mCameraDevice = android.hardware.Camera.open();
+            } catch (RuntimeException e) {
+                Log.e(TAG, "fail to connect Camera", e);
+                throw new CameraHardwareException();
+            }
         } else {
             try {
                 mCameraDevice.reconnect();
diff --git a/src/com/android/camera/CameraSettings.java b/src/com/android/camera/CameraSettings.java
index 7ff3310..78e0724 100644
--- a/src/com/android/camera/CameraSettings.java
+++ b/src/com/android/camera/CameraSettings.java
@@ -18,6 +18,7 @@
 
 import android.content.SharedPreferences;
 import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
+import android.content.res.Resources;
 import android.hardware.Camera.Parameters;
 import android.os.Bundle;
 import android.preference.ListPreference;
@@ -80,7 +81,16 @@
                 registerOnSharedPreferenceChangeListener(this);
 
         // Get parameters.
-        android.hardware.Camera device = CameraHolder.instance().open();
+        android.hardware.Camera device;
+        try {
+            device = CameraHolder.instance().open();
+        } catch (CameraHardwareException e) {
+            Resources ress = getResources();
+            Util.showFatalErrorAndFinish(this,
+                    ress.getString(R.string.camera_error_title),
+                    ress.getString(R.string.cannot_connect_camera));
+            return;
+        }
         mParameters = device.getParameters();
         CameraHolder.instance().release();
 
diff --git a/src/com/android/camera/Util.java b/src/com/android/camera/Util.java
index 2742247..b1ad8d2 100644
--- a/src/com/android/camera/Util.java
+++ b/src/com/android/camera/Util.java
@@ -16,10 +16,11 @@
 
 package com.android.camera;
 
-import com.android.camera.gallery.IImage;
-
+import android.app.Activity;
+import android.app.AlertDialog;
 import android.app.ProgressDialog;
 import android.content.ContentResolver;
+import android.content.DialogInterface;
 import android.content.Intent;
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -34,6 +35,8 @@
 import android.view.View;
 import android.view.View.OnClickListener;
 
+import com.android.camera.gallery.IImage;
+
 import java.io.ByteArrayOutputStream;
 import java.io.Closeable;
 import java.io.FileDescriptor;
@@ -512,4 +515,21 @@
         options.inNativeAlloc = true;
         return options;
     }
+
+    public static void showFatalErrorAndFinish(
+            final Activity activity, String title, String message) {
+        DialogInterface.OnClickListener buttonListener =
+                new DialogInterface.OnClickListener() {
+            public void onClick(DialogInterface dialog, int which) {
+                activity.finish();
+            }
+        };
+        new AlertDialog.Builder(activity)
+                .setCancelable(false)
+                .setIcon(android.R.drawable.ic_dialog_alert)
+                .setTitle(title)
+                .setMessage(message)
+                .setNeutralButton(R.string.details_ok, buttonListener)
+                .show();
+    }
 }
diff --git a/src/com/android/camera/VideoCamera.java b/src/com/android/camera/VideoCamera.java
index d596b6f..c5befa6 100644
--- a/src/com/android/camera/VideoCamera.java
+++ b/src/com/android/camera/VideoCamera.java
@@ -25,6 +25,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.SharedPreferences;
+import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.Drawable;
 import android.media.MediaRecorder;
@@ -115,6 +116,7 @@
     // are non-null only if mIsVideoCaptureIntent is true.
     private ImageView mLastPictureButton;
     private ThumbnailController mThumbController;
+    private boolean mStartPreviewFail = false;
 
     private int mStorageStatus = STORAGE_STATUS_OK;
 
@@ -181,7 +183,9 @@
         }
     }
 
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+    private BroadcastReceiver mReceiver = null;
+
+    private class MyBroadcastReceiver extends BroadcastReceiver {
         @Override
         public void onReceive(Context context, Intent intent) {
             String action = intent.getAction();
@@ -202,12 +206,19 @@
                 updateAndShowStorageHint(true);
             }
         }
-    };
+    }
 
     private static String createName(long dateTaken) {
         return DateFormat.format("yyyy-MM-dd kk.mm.ss", dateTaken).toString();
     }
 
+    private void showCameraBusyAndFinish() {
+        Resources ress = getResources();
+        Util.showFatalErrorAndFinish(VideoCamera.this,
+                ress.getString(R.string.camera_error_title),
+                ress.getString(R.string.cannot_connect_camera));
+    }
+
     /** Called with the activity is first created. */
     @Override
     public void onCreate(Bundle icicle) {
@@ -222,7 +233,12 @@
          */
         Thread startPreviewThread = new Thread(new Runnable() {
             public void run() {
-                startPreview();
+                try {
+                    mStartPreviewFail = false;
+                    startPreview();
+                } catch (CameraHardwareException e) {
+                    mStartPreviewFail = true;
+                }
             }
         });
         startPreviewThread.start();
@@ -280,6 +296,7 @@
         // Make sure preview is started.
         try {
             startPreviewThread.join();
+            if (mStartPreviewFail) showCameraBusyAndFinish();
         } catch (InterruptedException ex) {
             // ignore
         }
@@ -480,10 +497,14 @@
         mPausing = false;
 
         readVideoSizePreference();
-        if (!mPreviewing) {
-            startPreview();
+        if (!mPreviewing && !mStartPreviewFail) {
+            try {
+                startPreview();
+            } catch (CameraHardwareException e) {
+                showCameraBusyAndFinish();
+                return;
+            }
         }
-
         setScreenTimeoutLong();
 
         // install an intent filter to receive SD card related events.
@@ -494,6 +515,7 @@
         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_STARTED);
         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
         intentFilter.addDataScheme("file");
+        mReceiver = new MyBroadcastReceiver();
         registerReceiver(mReceiver, intentFilter);
         mStorageStatus = getStorageStatus(true);
 
@@ -525,7 +547,7 @@
         }
     }
 
-    private void startPreview() {
+    private void startPreview() throws CameraHardwareException {
         Log.v(TAG, "startPreview");
         if (mPreviewing) {
             // After recording a video, preview is not stopped. So just return.
@@ -597,7 +619,10 @@
         }
         closeCamera();
 
-        unregisterReceiver(mReceiver);
+        if (mReceiver != null) {
+            unregisterReceiver(mReceiver);
+            mReceiver = null;
+        }
         setScreenTimeoutSystemDefault();
 
         if (!mIsVideoCaptureIntent) {
@@ -678,6 +703,11 @@
             return;
         }
 
+        // The mCameraDevice will be null if it is fail to connect to the 
+        // camera hardware. In this case we will show a dialog and then 
+        // finish the activity, so it's OK to ignore it.
+        if (mCameraDevice == null) return;
+
         if (mMediaRecorderRecording) {
             stopVideoRecording();
         }
@@ -791,7 +821,8 @@
         if (mRecorderInitialized) return true;
 
         // We will call initializeRecorder() again when the alert is hidden.
-        if (isAlertVisible()) {
+        // If the mCameraDevice is null, then this activity is going to finish
+        if (isAlertVisible() || mCameraDevice == null) {
             return false;
         }