Updated ZXing Test with all of the CameraManager fixes from Barcode Scanner, as well...
authordswitkin <dswitkin@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Fri, 6 Nov 2009 15:55:00 +0000 (15:55 +0000)
committerdswitkin <dswitkin@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Fri, 6 Nov 2009 15:55:00 +0000 (15:55 +0000)
git-svn-id: http://zxing.googlecode.com/svn/trunk@1102 59b500cc-1b3d-0410-9834-0bbf25fbcc57

androidtest/AndroidManifest.xml
androidtest/src/com/google/zxing/client/androidtest/CameraManager.java
androidtest/src/com/google/zxing/client/androidtest/CameraTestActivity.java
androidtest/src/com/google/zxing/client/androidtest/SaveThread.java

index 686816c..1a5175a 100755 (executable)
  -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.google.zxing.client.androidtest"
-          android:versionName="1.11"
-          android:versionCode="3">
+          android:versionName="1.12"
+          android:versionCode="4">
+  <!-- We require Cupcake (Android 1.5) or later. -->
   <uses-sdk android:minSdkVersion="3"/>
+  <!-- Donut-specific flags which allow us to run on large and high dpi screens. -->
+  <supports-screens
+      android:largeScreens="true"
+      android:normalScreens="true"
+      android:smallScreens="true"
+      android:anyDensity="true"/>
   <application android:label="@string/app_name"
                android:icon="@drawable/icon"
                android:debuggable="true">
index f70b091..0df0f7a 100755 (executable)
@@ -20,8 +20,10 @@ import android.content.Context;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.Camera;
+import android.os.Build;
 import android.os.Handler;
 import android.os.Message;
+import android.util.Log;
 import android.view.Display;
 import android.view.SurfaceHolder;
 import android.view.WindowManager;
@@ -32,45 +34,119 @@ import java.io.IOException;
  * This object wraps the Camera service object and expects to be the only one talking to it. The
  * implementation encapsulates the steps needed to take preview-sized images, which are used for
  * both preview and decoding.
+ *
+ * @author dswitkin@google.com (Daniel Switkin)
  */
 final class CameraManager {
-
   private static final String TAG = "CameraManager";
+  private static final int MIN_FRAME_WIDTH = 240;
+  private static final int MIN_FRAME_HEIGHT = 240;
+  private static final int MAX_FRAME_WIDTH = 480;
+  private static final int MAX_FRAME_HEIGHT = 360;
+
+  private static CameraManager cameraManager;
+  private Camera camera;
+  private final Context context;
+  private Point screenResolution;
+  private Point cameraResolution;
+  private Rect framingRect;
+  private Handler previewHandler;
+  private int previewMessage;
+  private Handler autoFocusHandler;
+  private int autoFocusMessage;
+  private boolean initialized;
+  private boolean previewing;
+  private int previewFormat;
+  private String previewFormatString;
+  private boolean useOneShotPreviewCallback;
+
+  /**
+   * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
+   * clear the handler so it will only receive one message.
+   */
+  private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
+    public void onPreviewFrame(byte[] data, Camera camera) {
+      if (!useOneShotPreviewCallback) {
+        camera.setPreviewCallback(null);
+      }
+      if (previewHandler != null) {
+        Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
+            cameraResolution.y, data);
+        message.sendToTarget();
+        previewHandler = null;
+      }
+    }
+  };
+
+  /**
+   * Autofocus callbacks arrive here, and are dispatched to the Handler which requested them.
+   */
+  private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
+    public void onAutoFocus(boolean success, Camera camera) {
+      if (autoFocusHandler != null) {
+        Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success);
+        // Barcode Scanner needs to insert a delay here because it does continuous focus,
+        // but this test app does not, so send the message immediately.
+        message.sendToTarget();
+        autoFocusHandler = null;
+      }
+    }
+  };
 
-  private static CameraManager mCameraManager;
-  private Camera mCamera;
-  private final Context mContext;
-  private Point mScreenResolution;
-  private Rect mFramingRect;
-  private Handler mPreviewHandler;
-  private int mPreviewMessage;
-  private Handler mAutoFocusHandler;
-  private int mAutoFocusMessage;
-  private boolean mPreviewing;
-
-  public static synchronized void init(Context context) {
-    if (mCameraManager == null) {
-      mCameraManager = new CameraManager(context);
-      mCameraManager.getScreenResolution();
+  /**
+   * Initializes this static object with the Context of the calling Activity.
+   *
+   * @param context The Activity which wants to use the camera.
+   */
+  public static void init(Context context) {
+    if (cameraManager == null) {
+      cameraManager = new CameraManager(context);
     }
   }
 
+  /**
+   * Gets the CameraManager singleton instance.
+   *
+   * @return A reference to the CameraManager singleton.
+   */
   public static CameraManager get() {
-    return mCameraManager;
+    return cameraManager;
   }
 
   private CameraManager(Context context) {
-    mContext = context;
-    mCamera = null;
-    mPreviewing = false;
+    this.context = context;
+    camera = null;
+    initialized = false;
+    previewing = false;
+
+    // Camera.setOneShotPreviewCallback() has a race condition in Cupcake, so we use the older
+    // Camera.setPreviewCallback() on 1.5 and earlier. For Donut and later, we need to use
+    // the more efficient one shot callback, as the older one can swamp the system and cause it
+    // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK.
+    if (Integer.parseInt(Build.VERSION.SDK) <= Build.VERSION_CODES.CUPCAKE) {
+      useOneShotPreviewCallback = false;
+    } else {
+      useOneShotPreviewCallback = true;
+    }
   }
 
-  // Throws IOException added to accommodate Android 1.5.
+  /**
+   * Opens the camera driver and initializes the hardware parameters.
+   *
+   * @param holder The surface object which the camera will draw preview frames into.
+   * @throws IOException Indicates the camera driver failed to open.
+   */
   public String openDriver(SurfaceHolder holder, boolean getParameters) throws IOException {
     String result = null;
-    if (mCamera == null) {
-      mCamera = Camera.open();
-      mCamera.setPreviewDisplay(holder);
+    if (camera == null) {
+      camera = Camera.open();
+      camera.setPreviewDisplay(holder);
+
+      if (!initialized) {
+        initialized = true;
+        getScreenResolution();
+      }
+
       if (getParameters) {
         result = collectCameraParameters();
       }
@@ -79,114 +155,141 @@ final class CameraManager {
     return result;
   }
 
+  /**
+   * Closes the camera driver if still in use.
+   */
   public void closeDriver() {
-    if (mCamera != null) {
-      mCamera.release();
-      mCamera = null;
+    if (camera != null) {
+      camera.release();
+      camera = null;
     }
   }
 
+  /**
+   * Asks the camera hardware to begin drawing preview frames to the screen.
+   */
   public void startPreview() {
-    if (mCamera != null && !mPreviewing) {
-      mCamera.startPreview();
-      mPreviewing = true;
+    if (camera != null && !previewing) {
+      camera.startPreview();
+      previewing = true;
     }
   }
 
+  /**
+   * Tells the camera to stop drawing preview frames.
+   */
   public void stopPreview() {
-    if (mCamera != null && mPreviewing) {
-      mCamera.setPreviewCallback(null);
-      mCamera.stopPreview();
-      mPreviewHandler = null;
-      mAutoFocusHandler = null;
-      mPreviewing = false;
+    if (camera != null && previewing) {
+      if (!useOneShotPreviewCallback) {
+        camera.setPreviewCallback(null);
+      }
+      camera.stopPreview();
+      previewHandler = null;
+      autoFocusHandler = null;
+      previewing = false;
     }
   }
 
   /**
-   * A single preview frame will be returned to the handler supplied. The data will arrive as
-   * byte[] in the message.obj field, with width and height encoded as message.arg1 and
-   * message.arg2, respectively.
+   * A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
+   * in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
+   * respectively.
    *
    * @param handler The handler to send the message to.
    * @param message The what field of the message to be sent.
    */
   public void requestPreviewFrame(Handler handler, int message) {
-    if (mCamera != null && mPreviewing) {
-      mPreviewHandler = handler;
-      mPreviewMessage = message;
-      mCamera.setPreviewCallback(previewCallback);
+    if (camera != null && previewing) {
+      previewHandler = handler;
+      previewMessage = message;
+      if (useOneShotPreviewCallback) {
+        camera.setOneShotPreviewCallback(previewCallback);
+      } else {
+        camera.setPreviewCallback(previewCallback);
+      }
     }
   }
 
+  /**
+   * Asks the camera hardware to perform an autofocus.
+   *
+   * @param handler The Handler to notify when the autofocus completes.
+   * @param message The message to deliver.
+   */
   public void requestAutoFocus(Handler handler, int message) {
-    if (mCamera != null && mPreviewing) {
-      mAutoFocusHandler = handler;
-      mAutoFocusMessage = message;
-      mCamera.autoFocus(autoFocusCallback);
+    if (camera != null && previewing) {
+      autoFocusHandler = handler;
+      autoFocusMessage = message;
+      camera.autoFocus(autoFocusCallback);
     }
   }
 
   /**
    * Calculates the framing rect which the UI should draw to show the user where to place the
-   * barcode. The actual captured image should be a bit larger than indicated because they might
-   * frame the shot too tightly. This target helps with alignment as well as forces the user to
-   * hold the device far enough away to ensure the image will be in focus.
+   * barcode. This target helps with alignment as well as forces the user to hold the device
+   * far enough away to ensure the image will be in focus.
    *
    * @return The rectangle to draw on screen in window coordinates.
    */
   public Rect getFramingRect() {
-    if (mFramingRect == null) {
-      int width = mScreenResolution.x * 3 / 4;
-      int height = mScreenResolution.y * 3 / 4;
-      int leftOffset = (mScreenResolution.x - width) / 2;
-      int topOffset = (mScreenResolution.y - height) / 2;
-      mFramingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
-    }
-    return mFramingRect;
-  }
-
-  /**
-   * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
-   * clear the handler so it will only receive one message.
-   */
-  private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
-    public void onPreviewFrame(byte[] data, Camera camera) {
-      if (mPreviewHandler != null) {
-        mCamera.setPreviewCallback(null);
-        Message message = mPreviewHandler.obtainMessage(mPreviewMessage,
-            mScreenResolution.x, mScreenResolution.y, data);
-        message.sendToTarget();
-        mPreviewHandler = null;
+    if (framingRect == null) {
+      if (camera == null) {
+        return null;
       }
-    }
-  };
-
-  private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
-    public void onAutoFocus(boolean success, Camera camera) {
-      if (mAutoFocusHandler != null) {
-        Message message = mAutoFocusHandler.obtainMessage(mAutoFocusMessage, success);
-        // The Barcodes app needs to insert a delay here because it does continuous focus,
-        // but this test app does not, so send the message immediately.
-        message.sendToTarget();
-        mAutoFocusHandler = null;
+      int width = cameraResolution.x * 3 / 4;
+      if (width < MIN_FRAME_WIDTH) {
+        width = MIN_FRAME_WIDTH;
+      } else if (width > MAX_FRAME_WIDTH) {
+        width = MAX_FRAME_WIDTH;
+      }
+      int height = cameraResolution.y * 3 / 4;
+      if (height < MIN_FRAME_HEIGHT) {
+        height = MIN_FRAME_HEIGHT;
+      } else if (height > MAX_FRAME_HEIGHT) {
+        height = MAX_FRAME_HEIGHT;
       }
+      int leftOffset = (cameraResolution.x - width) / 2;
+      int topOffset = (cameraResolution.y - height) / 2;
+      framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
+      Log.v(TAG, "Calculated framing rect: " + framingRect);
     }
-  };
+    return framingRect;
+  }
 
   /**
-   * Sets the camera up to take preview images which are used for both preview and decoding. We're
-   * counting on the default YUV420 semi-planar data. If that changes in the future, we'll need to
-   * specify it explicitly with setPreviewFormat().
+   * Sets the camera up to take preview images which are used for both preview and decoding.
+   * We detect the preview format here so that buildLuminanceSource() can build an appropriate
+   * LuminanceSource subclass. In the future we may want to force YUV420SP as it's the smallest,
+   * and the planar Y can be used for barcode scanning without a copy in some cases.
    */
   private void setCameraParameters() {
-    Camera.Parameters parameters = mCamera.getParameters();
-    parameters.setPreviewSize(mScreenResolution.x, mScreenResolution.y);
-    mCamera.setParameters(parameters);
+    Camera.Parameters parameters = camera.getParameters();
+    Camera.Size size = parameters.getPreviewSize();
+    Log.v(TAG, "Default preview size: " + size.width + ", " + size.height);
+    previewFormat = parameters.getPreviewFormat();
+    previewFormatString = parameters.get("preview-format");
+    Log.v(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString);
+
+    // Ensure that the camera resolution is a multiple of 8, as the screen may not be.
+    // TODO: A better solution would be to request the supported preview resolutions
+    // and pick the best match, but this parameter is not standardized in Cupcake.
+    cameraResolution = new Point();
+    cameraResolution.x = (screenResolution.x >> 3) << 3;
+    cameraResolution.y = (screenResolution.y >> 3) << 3;
+    Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y);
+    parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
+
+    // FIXME: This is a hack to turn the flash off on the Samsung Galaxy.
+    parameters.set("flash-value", 2);
+
+    // This is the standard setting to turn the flash off that all devices should honor.
+    parameters.set("flash-mode", "off");
+
+    camera.setParameters(parameters);
   }
 
   private String collectCameraParameters() {
-    Camera.Parameters parameters = mCamera.getParameters();
+    Camera.Parameters parameters = camera.getParameters();
     String[] params = parameters.flatten().split(";");
     StringBuffer result = new StringBuffer();
     result.append("Default camera parameters:");
@@ -199,12 +302,11 @@ final class CameraManager {
   }
 
   private Point getScreenResolution() {
-    if (mScreenResolution == null) {
-      WindowManager wm = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
-      Display display = wm.getDefaultDisplay();
-      mScreenResolution = new Point(display.getWidth(), display.getHeight());
+    if (screenResolution == null) {
+      WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
+      Display display = manager.getDefaultDisplay();
+      screenResolution = new Point(display.getWidth(), display.getHeight());
     }
-    return mScreenResolution;
+    return screenResolution;
   }
-
 }
index 9f9323d..0549705 100755 (executable)
@@ -69,7 +69,7 @@ public final class CameraTestActivity extends Activity implements SurfaceHolder.
   protected void onResume() {
     super.onResume();
     if (mSaveThread == null && !mGetCameraParameters) {
-      mSaveThread = new SaveThread(this, CameraManager.get().getFramingRect());
+      mSaveThread = new SaveThread(this);
       mSaveThread.start();
     }
   }
index 166e495..c90d577 100755 (executable)
@@ -37,11 +37,9 @@ final class SaveThread extends Thread {
   public Handler mHandler;
 
   private final CameraTestActivity mActivity;
-  private final Rect mFramingRect;
 
-  SaveThread(CameraTestActivity activity, Rect framingRect) {
+  SaveThread(CameraTestActivity activity) {
     mActivity = activity;
-    mFramingRect = framingRect;
   }
 
   @Override
@@ -65,14 +63,15 @@ final class SaveThread extends Thread {
 
   // Save the center rectangle of the Y channel as a greyscale PNG to the SD card.
   private void save(byte[] data, int width, int height) {
-    int framingWidth = mFramingRect.width();
-    int framingHeight = mFramingRect.height();
+    final Rect framingRect = CameraManager.get().getFramingRect();
+    int framingWidth = framingRect.width();
+    int framingHeight = framingRect.height();
     if (framingWidth > width || framingHeight > height) {
       throw new IllegalArgumentException();
     }
 
-    int leftOffset = mFramingRect.left;
-    int topOffset = mFramingRect.top;
+    int leftOffset = framingRect.left;
+    int topOffset = framingRect.top;
     int[] colors = new int[framingWidth * framingHeight];
 
     for (int y = 0; y < framingHeight; y++) {