Send history feature now exports full CSV dump
[zxing.git] / android / src / com / google / zxing / client / android / CameraManager.java
index fd4a366..cc9b83a 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,21 @@ 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 int TEN_DESIRED_ZOOM = 27;
+  private static final int DESIRED_SHARPNESS = 30;
+
+  private static final Pattern COMMA_PATTERN = Pattern.compile(",");
+
   private static CameraManager cameraManager;
+
   private Camera camera;
   private final Context context;
   private Point screenResolution;
@@ -61,7 +70,7 @@ final class CameraManager {
   private boolean previewing;
   private int previewFormat;
   private String previewFormatString;
-  private boolean useOneShotPreviewCallback;
+  private final boolean useOneShotPreviewCallback;
 
   /**
    * Preview frames are delivered here, which we pass on to the registered handler. Make sure to
@@ -124,12 +133,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 +146,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 +157,7 @@ final class CameraManager {
       }
 
       setCameraParameters();
+      FlashlightManager.enableFlashlight();
     }
   }
 
@@ -157,6 +166,7 @@ final class CameraManager {
    */
   public void closeDriver() {
     if (camera != null) {
+      FlashlightManager.disableFlashlight();
       camera.release();
       camera = null;
     }
@@ -279,24 +289,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,26 +322,17 @@ 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);
 
-    // 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");
+    setFlash(parameters);
+    setZoom(parameters);
+    //setSharpness(parameters);
 
     camera.setParameters(parameters);
   }
@@ -340,4 +346,145 @@ 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: " + previewSize);
+        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: " + previewSize);
+        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;
+  }
+
+  private void setFlash(Camera.Parameters parameters) {
+    // 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");
+  }
+
+  private void setZoom(Camera.Parameters parameters) {
+
+    String zoomSupportedString = parameters.get("zoom-supported");
+    if (zoomSupportedString != null && !Boolean.parseBoolean(zoomSupportedString)) {
+      return;
+    }
+
+    int tenDesiredZoom = TEN_DESIRED_ZOOM;
+
+    String maxZoomString = parameters.get("max-zoom");
+    if (maxZoomString != null) {
+      try {
+        int tenMaxZoom = (int) (10.0 * Double.parseDouble(maxZoomString));
+        if (tenDesiredZoom > tenMaxZoom) {
+          tenDesiredZoom = tenMaxZoom;
+        }
+      } catch (NumberFormatException nfe) {
+        Log.w(TAG, "Bad max-zoom: " + maxZoomString);
+      }
+    }
+
+    String takingPictureZoomMaxString = parameters.get("taking-picture-zoom-max");
+    if (takingPictureZoomMaxString != null) {
+      try {
+        int tenMaxZoom = Integer.parseInt(takingPictureZoomMaxString);
+        if (tenDesiredZoom > tenMaxZoom) {
+          tenDesiredZoom = tenMaxZoom;
+        }
+      } catch (NumberFormatException nfe) {
+        Log.w(TAG, "Bad taking-picture-zoom-max: " + takingPictureZoomMaxString);
+      }
+    }
+
+
+    // Set zoom. This helps encourage the user to pull back.
+    // Some devices like the Behold have a zoom parameter
+    if (maxZoomString != null) {
+      parameters.set("zoom", String.valueOf(tenDesiredZoom / 10.0));
+    }
+
+    // Most devices, like the Hero, appear to expose this zoom parameter.
+    // It takes on values like "27" which appears to mean 2.7x zoom
+    if (takingPictureZoomMaxString != null) {    
+      parameters.set("taking-picture-zoom", tenDesiredZoom);
+    }
+  }
+
+  /*
+  private void setSharpness(Camera.Parameters parameters) {
+
+    int desiredSharpness = DESIRED_SHARPNESS;
+
+    String maxSharpnessString = parameters.get("sharpness-max");
+    if (maxSharpnessString != null) {
+      try {
+        int maxSharpness = Integer.parseInt(maxSharpnessString);
+        if (desiredSharpness > maxSharpness) {
+          desiredSharpness = maxSharpness;
+        }
+      } catch (NumberFormatException nfe) {
+        Log.w(TAG, "Bad sharpness-max: " + maxSharpnessString);
+      }
+    }
+
+    parameters.set("sharpness", desiredSharpness);
+  }
+   */
 }