Per Daniel -- don't check phone's preview size as we want to use screen size if possible
[zxing.git] / android / src / com / google / zxing / client / android / CameraManager.java
index fd4a366..ee0c0fa 100755 (executable)
@@ -32,6 +32,7 @@ import android.view.SurfaceHolder;
 import android.view.WindowManager;
 
 import java.io.IOException;
+import java.util.regex.Pattern;
 
 /**
  * This object wraps the Camera service object and expects to be the only one talking to it. The
@@ -41,13 +42,18 @@ import java.io.IOException;
  * @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 final Pattern COMMA_PATTERN = Pattern.compile(",");
+
   private static CameraManager cameraManager;
+
   private Camera camera;
   private final Context context;
   private Point screenResolution;
@@ -124,12 +130,8 @@ final class CameraManager {
     // 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.
-    if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.CUPCAKE) {
-      useOneShotPreviewCallback = false;
-    } else {
-      useOneShotPreviewCallback = true;
-    }
+    // to run out of memory. We can't use SDK_INT because it was introduced in the Donut SDK.
+    useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > Build.VERSION_CODES.CUPCAKE;
   }
 
   /**
@@ -141,6 +143,9 @@ final class CameraManager {
   public void openDriver(SurfaceHolder holder) throws IOException {
     if (camera == null) {
       camera = Camera.open();
+      if (camera == null) {
+        throw new IOException();
+      }
       camera.setPreviewDisplay(holder);
 
       if (!initialized) {
@@ -149,6 +154,7 @@ final class CameraManager {
       }
 
       setCameraParameters();
+      FlashlightManager.enableFlashlight();
     }
   }
 
@@ -157,6 +163,7 @@ final class CameraManager {
    */
   public void closeDriver() {
     if (camera != null) {
+      FlashlightManager.disableFlashlight();
       camera.release();
       camera = null;
     }
@@ -279,24 +286,29 @@ final class CameraManager {
    * @param data A preview frame.
    * @param width The width of the image.
    * @param height The height of the image.
-   * @return A BaseLuminanceSource subclass.
+   * @return A PlanarYUVLuminanceSource instance.
    */
-  public BaseLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
+  public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
     Rect rect = getFramingRect();
     switch (previewFormat) {
+      // This is the standard Android format which all devices are REQUIRED to support.
+      // In theory, it's the only one we should ever care about.
       case PixelFormat.YCbCr_420_SP:
+      // This format has never been seen in the wild, but is compatible as we only care
+      // about the Y channel, so allow it.
       case PixelFormat.YCbCr_422_SP:
         return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
             rect.width(), rect.height());
       default:
-        // There's no PixelFormat constant for this buffer format yet.
-        if (previewFormatString.equals("yuv422i-yuyv")) {
-          return new InterleavedYUV422LuminanceSource(data, width, height, rect.left, rect.top,
-              rect.width(), rect.height());
+        // The Samsung Moment incorrectly uses this variant instead of the 'sp' version.
+        // Fortunately, it too has all the Y data up front, so we can read it.
+        if ("yuv420p".equals(previewFormatString)) {
+          return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
+            rect.width(), rect.height());
         }
-        break;
     }
-    return null;
+    throw new IllegalArgumentException("Unsupported picture format: " +
+        previewFormat + '/' + previewFormatString);
   }
 
   /**
@@ -307,18 +319,11 @@ final class CameraManager {
    */
   private void setCameraParameters() {
     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);
-
-    // 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, "Default preview format: " + previewFormat + '/' + previewFormatString);
+
+    cameraResolution = getCameraResolution(parameters);
     Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y);
     parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
 
@@ -328,6 +333,13 @@ final class CameraManager {
     // This is the standard setting to turn the flash off that all devices should honor.
     parameters.set("flash-mode", "off");
 
+    // Set zoom to 2x if available. This helps encourage the user to pull back.
+    // Some devices like the Behold have a zoom parameter
+    parameters.set("zoom", "2.0");
+    // Most devices, like the Hero, appear to expose this zoom parameter.
+    // (I think) This means 2.0x
+    parameters.set("taking-picture-zoom", "20");
+
     camera.setParameters(parameters);
   }
 
@@ -340,4 +352,71 @@ final class CameraManager {
     return screenResolution;
   }
 
+  private Point getCameraResolution(Camera.Parameters parameters) {
+
+    String previewSizeValueString = parameters.get("preview-size-values");
+    // saw this on Xperia
+    if (previewSizeValueString == null) {
+      previewSizeValueString = parameters.get("preview-size-value");
+    }
+
+    Point cameraResolution = null;
+    
+    if (previewSizeValueString != null) {
+      Log.v(TAG, "preview-size parameter: " + previewSizeValueString);
+      cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution);
+    }
+
+    if (cameraResolution == null) {
+      // Ensure that the camera resolution is a multiple of 8, as the screen may not be.
+      cameraResolution = new Point(
+          (screenResolution.x >> 3) << 3,
+          (screenResolution.y >> 3) << 3);
+    }
+
+    return cameraResolution;
+  }
+
+  private static Point findBestPreviewSizeValue(String previewSizeValueString, Point screenResolution) {
+    int bestX = 0;
+    int bestY = 0;
+    int diff = Integer.MAX_VALUE;
+    for (String previewSize : COMMA_PATTERN.split(previewSizeValueString)) {
+
+      previewSize = previewSize.trim();
+      int dimPosition = previewSize.indexOf('x');
+      if (dimPosition < 0) {
+        Log.w(TAG, "Bad preview-size");
+        continue;
+      }
+
+      int newX;
+      int newY;
+      try {
+        newX = Integer.parseInt(previewSize.substring(0, dimPosition));
+        newY = Integer.parseInt(previewSize.substring(dimPosition + 1));
+      } catch (NumberFormatException nfe) {
+        Log.w(TAG, "Bad preview-size");
+        continue;
+      }
+
+      int newDiff = Math.abs(newX - screenResolution.x) + Math.abs(newY - screenResolution.y);
+      if (newDiff == 0) {
+        bestX = newX;
+        bestY = newY;
+        break;
+      } else if (newDiff < diff) {
+        bestX = newX;
+        bestY = newY;
+        diff = newDiff;
+      }
+
+    }
+
+    if (bestX > 0 && bestY > 0) {
+      return new Point(bestX, bestY);
+    }
+    return null;
+  }
+
 }