Remove some pre-Eclair code, update camera parameters handling to use 2.x APIs

git-svn-id: https://zxing.googlecode.com/svn/trunk@2038 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2011-11-16 10:39:50 +00:00
parent be03860ec9
commit 9e9e5671d9
4 changed files with 85 additions and 368 deletions

View file

@ -17,14 +17,17 @@
package com.google.zxing.client.android.camera;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.Point;
import android.hardware.Camera;
import android.os.Build;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.Display;
import android.view.WindowManager;
import java.util.regex.Pattern;
import com.google.zxing.client.android.PreferencesActivity;
import java.util.Collection;
/**
* A class which deals with reading, parsing, and setting the camera parameters which are used to
@ -32,15 +35,12 @@ import java.util.regex.Pattern;
*/
final class CameraConfigurationManager {
private static final String TAG = CameraConfigurationManager.class.getSimpleName();
private static final int TEN_DESIRED_ZOOM = 27;
private static final Pattern COMMA_PATTERN = Pattern.compile(",");
private static final String TAG = "CameraConfiguration";
private static final int MIN_PREVIEW_PIXELS = 320 * 240; // small screen
private final Context context;
private Point screenResolution;
private Point cameraResolution;
private int previewFormat;
private String previewFormatString;
CameraConfigurationManager(Context context) {
this.context = context;
@ -51,29 +51,33 @@ final class CameraConfigurationManager {
*/
void initFromCameraParameters(Camera camera) {
Camera.Parameters parameters = camera.getParameters();
previewFormat = parameters.getPreviewFormat();
previewFormatString = parameters.get("preview-format");
Log.d(TAG, "Default preview format: " + previewFormat + '/' + previewFormatString);
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
screenResolution = new Point(display.getWidth(), display.getHeight());
Log.d(TAG, "Screen resolution: " + screenResolution);
cameraResolution = getCameraResolution(parameters, screenResolution);
Log.d(TAG, "Camera resolution: " + cameraResolution);
Log.i(TAG, "Screen resolution: " + screenResolution);
cameraResolution = findBestPreviewSizeValue(parameters, screenResolution, false);
Log.i(TAG, "Camera resolution: " + cameraResolution);
}
/**
* 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.
*/
void setDesiredCameraParameters(Camera camera) {
Camera.Parameters parameters = camera.getParameters();
Log.d(TAG, "Setting preview size: " + cameraResolution);
if (parameters == null) {
Log.w(TAG, "Device error: no camera parameters are available. Proceeding without configuration.");
return;
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
initializeTorch(parameters, prefs);
String focusMode = findSettableValue(parameters.getSupportedFocusModes(),
Camera.Parameters.FOCUS_MODE_AUTO,
Camera.Parameters.FOCUS_MODE_MACRO);
if (focusMode != null) {
parameters.setFocusMode(focusMode);
}
parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
setFlash(parameters);
setZoom(parameters);
camera.setParameters(parameters);
}
@ -85,166 +89,81 @@ final class CameraConfigurationManager {
return screenResolution;
}
int getPreviewFormat() {
return previewFormat;
void setTorch(Camera camera, boolean newSetting) {
Camera.Parameters parameters = camera.getParameters();
doSetTorch(parameters, newSetting);
camera.setParameters(parameters);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
boolean currentSetting = prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false);
if (currentSetting != newSetting) {
SharedPreferences.Editor editor = prefs.edit();
editor.putBoolean(PreferencesActivity.KEY_FRONT_LIGHT, newSetting);
editor.commit();
}
}
String getPreviewFormatString() {
return previewFormatString;
private static void initializeTorch(Camera.Parameters parameters, SharedPreferences prefs) {
boolean currentSetting = prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false);
doSetTorch(parameters, currentSetting);
}
private static Point getCameraResolution(Camera.Parameters parameters, Point screenResolution) {
String previewSizeValueString = parameters.get("preview-size-values");
// This string is wrong but was seen on the Sony Xperia.
if (previewSizeValueString == null) {
previewSizeValueString = parameters.get("preview-size-value");
private static void doSetTorch(Camera.Parameters parameters, boolean newSetting) {
String flashMode;
if (newSetting) {
flashMode = findSettableValue(parameters.getSupportedFlashModes(),
Camera.Parameters.FLASH_MODE_TORCH,
Camera.Parameters.FLASH_MODE_ON);
} else {
flashMode = findSettableValue(parameters.getSupportedFlashModes(),
Camera.Parameters.FLASH_MODE_OFF);
}
Point cameraResolution = null;
if (previewSizeValueString != null) {
Log.d(TAG, "preview-size-values parameter: " + previewSizeValueString);
cameraResolution = findBestPreviewSizeValue(previewSizeValueString, screenResolution);
if (flashMode != null) {
parameters.setFlashMode(flashMode);
}
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(CharSequence previewSizeValueString,
Point screenResolution) {
int bestX = 0;
int bestY = 0;
private static Point findBestPreviewSizeValue(Camera.Parameters parameters,
Point screenResolution,
boolean portrait) {
Point bestSize = null;
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);
for (Camera.Size supportedPreviewSize : parameters.getSupportedPreviewSizes()) {
if (supportedPreviewSize.height * supportedPreviewSize.width < MIN_PREVIEW_PIXELS) {
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);
int supportedWidth = portrait ? supportedPreviewSize.height : supportedPreviewSize.width;
int supportedHeight = portrait ? supportedPreviewSize.width : supportedPreviewSize.height;
int newDiff = Math.abs(screenResolution.x * supportedHeight - supportedWidth * screenResolution.y);
if (newDiff == 0) {
bestX = newX;
bestY = newY;
bestSize = new Point(supportedWidth, supportedHeight);
break;
} else if (newDiff < diff) {
bestX = newX;
bestY = newY;
}
if (newDiff < diff) {
bestSize = new Point(supportedWidth, supportedHeight);
diff = newDiff;
}
}
if (bestX > 0 && bestY > 0) {
return new Point(bestX, bestY);
if (bestSize == null) {
Camera.Size defaultSize = parameters.getPreviewSize();
bestSize = new Point(defaultSize.width, defaultSize.height);
}
return null;
return bestSize;
}
private static int findBestMotZoomValue(CharSequence stringValues, int tenDesiredZoom) {
int tenBestValue = 0;
for (String stringValue : COMMA_PATTERN.split(stringValues)) {
stringValue = stringValue.trim();
double value;
try {
value = Double.parseDouble(stringValue);
} catch (NumberFormatException nfe) {
return tenDesiredZoom;
}
int tenValue = (int) (10.0 * value);
if (Math.abs(tenDesiredZoom - value) < Math.abs(tenDesiredZoom - tenBestValue)) {
tenBestValue = tenValue;
}
}
return tenBestValue;
}
private void setFlash(Camera.Parameters parameters) {
// This is a hack to turn the flash off on the Samsung Galaxy and the Behold II
// as advised by Samsung, neither of which respected the official parameter.
if (Build.MODEL.contains("Behold II") && CameraManager.SDK_INT == 3) { // 3 = Cupcake
parameters.set("flash-value", 1);
} else {
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;
private static String findSettableValue(Collection<String> supportedValues,
String... desiredValues) {
Log.i(TAG, "Supported values: " + supportedValues);
String result = null;
if (supportedValues != null) {
for (String desiredValue : desiredValues) {
if (supportedValues.contains(desiredValue)) {
result = desiredValue;
break;
}
} 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);
}
}
String motZoomValuesString = parameters.get("mot-zoom-values");
if (motZoomValuesString != null) {
tenDesiredZoom = findBestMotZoomValue(motZoomValuesString, tenDesiredZoom);
}
String motZoomStepString = parameters.get("mot-zoom-step");
if (motZoomStepString != null) {
try {
double motZoomStep = Double.parseDouble(motZoomStepString.trim());
int tenZoomStep = (int) (10.0 * motZoomStep);
if (tenZoomStep > 1) {
tenDesiredZoom -= tenDesiredZoom % tenZoomStep;
}
} catch (NumberFormatException nfe) {
// continue
}
}
// Sets the zoom which helps encourage the user to pull back.
// Some devices like the Behold have a zoom parameter.
if (maxZoomString != null || motZoomValuesString != 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);
}
Log.i(TAG, "Settable value: " + result);
return result;
}
}

View file

@ -18,11 +18,9 @@ package com.google.zxing.client.android.camera;
import android.content.Context;
import android.content.SharedPreferences;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Build;
import android.os.Handler;
import android.preference.PreferenceManager;
import android.util.Log;
@ -48,18 +46,6 @@ public final class CameraManager {
private static final int MAX_FRAME_WIDTH = 600;
private static final int MAX_FRAME_HEIGHT = 400;
static final int SDK_INT; // Later we can use Build.VERSION.SDK_INT
static {
int sdkInt;
try {
sdkInt = Integer.parseInt(Build.VERSION.SDK);
} catch (NumberFormatException nfe) {
// Just to be safe
sdkInt = 10000;
}
SDK_INT = sdkInt;
}
private final Context context;
private final CameraConfigurationManager configManager;
private Camera camera;
@ -68,7 +54,6 @@ public final class CameraManager {
private boolean initialized;
private boolean previewing;
private boolean reverseImage;
private final boolean useOneShotPreviewCallback;
private int requestedFramingRectWidth;
private int requestedFramingRectHeight;
/**
@ -76,22 +61,13 @@ public final class CameraManager {
* clear the handler so it will only receive one message.
*/
private final PreviewCallback previewCallback;
/** Autofocus callbacks arrive here, and are dispatched to the Handler which requested them. */
private final AutoFocusCallback autoFocusCallback;
public CameraManager(Context context) {
this.context = context;
this.configManager = new CameraConfigurationManager(context);
// 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.
useOneShotPreviewCallback = Integer.parseInt(Build.VERSION.SDK) > 3; // 3 = Cupcake
previewCallback = new PreviewCallback(configManager, useOneShotPreviewCallback);
previewCallback = new PreviewCallback(configManager);
autoFocusCallback = new AutoFocusCallback();
}
@ -125,9 +101,6 @@ public final class CameraManager {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
reverseImage = prefs.getBoolean(PreferencesActivity.KEY_REVERSE_IMAGE, false);
if (prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false)) {
FlashlightManager.enableFlashlight();
}
}
/**
@ -135,10 +108,8 @@ public final class CameraManager {
*/
public void closeDriver() {
if (camera != null) {
FlashlightManager.disableFlashlight();
camera.release();
camera = null;
// Make sure to clear these each time we close the camera, so that any scanning rect
// requested by intent is forgotten.
framingRect = null;
@ -162,9 +133,6 @@ public final class CameraManager {
*/
public void stopPreview() {
if (camera != null && previewing) {
if (!useOneShotPreviewCallback) {
camera.setPreviewCallback(null);
}
camera.stopPreview();
previewCallback.setHandler(null, 0);
autoFocusCallback.setHandler(null, 0);
@ -184,11 +152,7 @@ public final class CameraManager {
Camera theCamera = camera;
if (theCamera != null && previewing) {
previewCallback.setHandler(handler, message);
if (useOneShotPreviewCallback) {
theCamera.setOneShotPreviewCallback(previewCallback);
} else {
theCamera.setPreviewCallback(previewCallback);
}
theCamera.setOneShotPreviewCallback(previewCallback);
}
}
@ -201,7 +165,6 @@ public final class CameraManager {
public void requestAutoFocus(Handler handler, int message) {
if (camera != null && previewing) {
autoFocusCallback.setHandler(handler, message);
//Log.d(TAG, "Requesting auto-focus callback");
camera.autoFocus(autoFocusCallback);
}
}
@ -302,28 +265,9 @@ public final class CameraManager {
if (rect == null) {
return null;
}
int previewFormat = configManager.getPreviewFormat();
String previewFormatString = configManager.getPreviewFormatString();
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(), reverseImage);
default:
// 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(), reverseImage);
}
}
throw new IllegalArgumentException("Unsupported picture format: " +
previewFormat + '/' + previewFormatString);
// Go ahead and assume it's YUV rather than die.
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height(), reverseImage);
}
}

View file

@ -1,141 +0,0 @@
/*
* Copyright (C) 2010 ZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.zxing.client.android.camera;
import android.os.IBinder;
import android.util.Log;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
/**
* This class is used to activate the weak light on some camera phones (not flash)
* in order to illuminate surfaces for scanning. There is no official way to do this,
* but, classes which allow access to this function still exist on some devices.
* This therefore proceeds through a great deal of reflection.
*
* See http://almondmendoza.com/2009/01/05/changing-the-screen-brightness-programatically/ and
* http://code.google.com/p/droidled/source/browse/trunk/src/com/droidled/demo/DroidLED.java .
* Thanks to Ryan Alford for pointing out the availability of this class.
*/
final class FlashlightManager {
private static final String TAG = FlashlightManager.class.getSimpleName();
private static final Object iHardwareService;
private static final Method setFlashEnabledMethod;
static {
iHardwareService = getHardwareService();
Log.v(TAG, "This device does " + (iHardwareService == null ? "not" : "") + " support control of a flashlight");
setFlashEnabledMethod = getSetFlashEnabledMethod(iHardwareService);
}
private FlashlightManager() {
}
private static Object getHardwareService() {
Class<?> serviceManagerClass = maybeForName("android.os.ServiceManager");
if (serviceManagerClass == null) {
return null;
}
Method getServiceMethod = maybeGetMethod(serviceManagerClass, "getService", String.class);
if (getServiceMethod == null) {
return null;
}
Object hardwareService = invoke(getServiceMethod, null, "hardware");
if (hardwareService == null) {
return null;
}
Class<?> iHardwareServiceStubClass = maybeForName("android.os.IHardwareService$Stub");
if (iHardwareServiceStubClass == null) {
return null;
}
Method asInterfaceMethod = maybeGetMethod(iHardwareServiceStubClass, "asInterface",
IBinder.class);
if (asInterfaceMethod == null) {
return null;
}
return invoke(asInterfaceMethod, null, hardwareService);
}
private static Method getSetFlashEnabledMethod(Object iHardwareService) {
if (iHardwareService == null) {
return null;
}
Class<?> proxyClass = iHardwareService.getClass();
return maybeGetMethod(proxyClass, "setFlashlightEnabled", boolean.class);
}
private static Class<?> maybeForName(String name) {
try {
return Class.forName(name);
} catch (ClassNotFoundException cnfe) {
// OK
return null;
} catch (RuntimeException re) {
Log.w(TAG, "Unexpected error while finding class " + name, re);
return null;
}
}
private static Method maybeGetMethod(Class<?> clazz, String name, Class<?>... argClasses) {
try {
return clazz.getMethod(name, argClasses);
} catch (NoSuchMethodException nsme) {
// OK
return null;
} catch (RuntimeException re) {
Log.w(TAG, "Unexpected error while finding method " + name, re);
return null;
}
}
private static Object invoke(Method method, Object instance, Object... args) {
try {
return method.invoke(instance, args);
} catch (IllegalAccessException e) {
Log.w(TAG, "Unexpected error while invoking " + method, e);
return null;
} catch (InvocationTargetException e) {
Log.w(TAG, "Unexpected error while invoking " + method, e.getCause());
return null;
} catch (RuntimeException re) {
Log.w(TAG, "Unexpected error while invoking " + method, re);
return null;
}
}
static void enableFlashlight() {
setFlashlight(true);
}
static void disableFlashlight() {
setFlashlight(false);
}
private static void setFlashlight(boolean active) {
if (iHardwareService != null) {
invoke(setFlashEnabledMethod, iHardwareService, active);
}
}
}

View file

@ -27,13 +27,11 @@ final class PreviewCallback implements Camera.PreviewCallback {
private static final String TAG = PreviewCallback.class.getSimpleName();
private final CameraConfigurationManager configManager;
private final boolean useOneShotPreviewCallback;
private Handler previewHandler;
private int previewMessage;
PreviewCallback(CameraConfigurationManager configManager, boolean useOneShotPreviewCallback) {
PreviewCallback(CameraConfigurationManager configManager) {
this.configManager = configManager;
this.useOneShotPreviewCallback = useOneShotPreviewCallback;
}
void setHandler(Handler previewHandler, int previewMessage) {
@ -44,9 +42,6 @@ final class PreviewCallback implements Camera.PreviewCallback {
@Override
public void onPreviewFrame(byte[] data, Camera camera) {
Point cameraResolution = configManager.getCameraResolution();
if (!useOneShotPreviewCallback) {
camera.setPreviewCallback(null);
}
Handler thePreviewHandler = previewHandler;
if (thePreviewHandler != null) {
Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x,