From: drizea Date: Tue, 7 Aug 2012 17:29:04 +0000 (+0300) Subject: barcode scan functionality added (zxing library) X-Git-Url: https://old-git.evergreen-ils.org/?a=commitdiff_plain;h=bbfebfc034f6b26581401a51953ab5a911de433f;p=working%2FEvergreen.git barcode scan functionality added (zxing library) --- diff --git a/Open-ILS/src/Android/.classpath b/Open-ILS/src/Android/.classpath index 1b0ce02fe4..3c525784df 100644 --- a/Open-ILS/src/Android/.classpath +++ b/Open-ILS/src/Android/.classpath @@ -8,5 +8,6 @@ + diff --git a/Open-ILS/src/Android/AndroidManifest.xml b/Open-ILS/src/Android/AndroidManifest.xml index 7685a0dd3b..1c653d317b 100644 --- a/Open-ILS/src/Android/AndroidManifest.xml +++ b/Open-ILS/src/Android/AndroidManifest.xml @@ -7,6 +7,14 @@ + + + + + + + + + + + + + + + diff --git a/Open-ILS/src/Android/libs/zxing_barcode.jar b/Open-ILS/src/Android/libs/zxing_barcode.jar new file mode 100644 index 0000000000..6c2732337f Binary files /dev/null and b/Open-ILS/src/Android/libs/zxing_barcode.jar differ diff --git a/Open-ILS/src/Android/res/drawable/header_rounded_corners.xml b/Open-ILS/src/Android/res/drawable/header_rounded_corners.xml index 98b40ddb25..b78206b207 100644 --- a/Open-ILS/src/Android/res/drawable/header_rounded_corners.xml +++ b/Open-ILS/src/Android/res/drawable/header_rounded_corners.xml @@ -8,7 +8,9 @@ + android:startColor="@color/header_gradient_start" + + /> + android:startColor="#40ffffff" + android:useLevel="false" + /> + + + + + + + + + + + + diff --git a/Open-ILS/src/Android/res/layout/copy_information_more.xml b/Open-ILS/src/Android/res/layout/copy_information_more.xml index df1baee7c4..a945767911 100644 --- a/Open-ILS/src/Android/res/layout/copy_information_more.xml +++ b/Open-ILS/src/Android/res/layout/copy_information_more.xml @@ -5,10 +5,19 @@ android:orientation="vertical" android:layout_width="fill_parent" android:layout_height="fill_parent" + android:padding="4dip" > + + #000 + + + + + #ff000000 + #ffffffff + #ffcccccc + #ff404040 + #c0ffff00 + #ffffffff + #ffc0c0c0 + #c000ff00 + #ffffffff + #b0000000 + #ff808080 + #ffffffff + #fffff0e0 + #ffffffff + #ff000000 + #ff4b4b4b + #ff000000 + #50000000 + #ffffffff + #00000000 + #ff000000 + #ffff0000 + #60000000 + #a0ff0000 diff --git a/Open-ILS/src/Android/res/values/ids.xml b/Open-ILS/src/Android/res/values/ids.xml index e0a4745b22..89a6d61f8b 100644 --- a/Open-ILS/src/Android/res/values/ids.xml +++ b/Open-ILS/src/Android/res/values/ids.xml @@ -20,4 +20,14 @@ + + + + + + + + + + diff --git a/Open-ILS/src/Android/res/values/strings.xml b/Open-ILS/src/Android/res/values/strings.xml index 1fe47d98cf..b1adcc26b9 100644 --- a/Open-ILS/src/Android/res/values/strings.xml +++ b/Open-ILS/src/Android/res/values/strings.xml @@ -34,6 +34,8 @@ Cancel Connect + + What are you looking for? Search @@ -45,7 +47,7 @@ Library hours Preferences - + Availability of the book : Hello World, EvergreenAppActivity! EvergreenApp @@ -104,4 +106,9 @@ create bookbag name add + + + + Sorry, the Android camera encountered a problem. You may need to restart the device. + OK \ No newline at end of file diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/CaptureActivity.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/CaptureActivity.java new file mode 100755 index 0000000000..3ecade7d8b --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/CaptureActivity.java @@ -0,0 +1,234 @@ +package org.evergreen.android.barcodescan; + +import java.io.IOException; +import java.util.Vector; + +import org.evergreen.android.R; +import org.evergreen.android.barcodescan.camera.CameraManager; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.util.Log; +import android.view.Menu; +import android.view.SurfaceHolder; +import android.view.SurfaceView; +import android.view.Window; +import android.view.WindowManager; +import android.widget.Toast; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; + +public class CaptureActivity extends Activity implements SurfaceHolder.Callback { + + private static final String TAG = CaptureActivity.class.getSimpleName(); + + private ViewfinderView viewfinderView; + + private CaptureActivityHandler handler; + + private CameraManager cameraManager; + private boolean hasSurface; + private Vector decodeFormats; + // private HistoryManager historyManager; + private Result lastResult; + private String characterSet; + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Remove title bar + this.requestWindowFeature(Window.FEATURE_NO_TITLE); + + // Remove notification bar + this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN, + WindowManager.LayoutParams.FLAG_FULLSCREEN); + + setContentView(R.layout.barcode_scan); + + Log.d("BARCODE","Start application 1"); + Window window = getWindow(); + window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + // init camera manager + cameraManager = new CameraManager(getApplication()); + hasSurface = false; + handler = null; + + viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view); + + viewfinderView.setCameraManager(cameraManager); + //database = new DBHelper(this); + + Log.d("BARCODE","Start application 2"); + Toast.makeText(this, "Hello", Toast.LENGTH_LONG); + } + + + @Override + public void onResume() { + super.onResume(); + + SurfaceView surfaceView = (SurfaceView) findViewById(R.id.camera_view); + SurfaceHolder surfaceHolder = surfaceView.getHolder(); + if (hasSurface) { + // The activity was paused but not stopped, so the surface still + // exists. Therefore + // surfaceCreated() won't be called, so init the camera here. + initCamera(surfaceHolder); + } else { + // Install the callback and wait for surfaceCreated() to init the + // camera. + surfaceHolder.addCallback(this); + surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS); + } + + } + + @Override + protected void onPause() { + super.onPause(); + if (handler != null) { + handler.quitSynchronously(); + handler = null; + } + cameraManager.closeDriver(); + } + + public void surfaceCreated(SurfaceHolder holder) { + if (!hasSurface) { + hasSurface = true; + initCamera(holder); + } + } + + public void surfaceDestroyed(SurfaceHolder holder) { + hasSurface = false; + } + + public void surfaceChanged(SurfaceHolder holder, int format, int width, + int height) { + + } + + private void initCamera(SurfaceHolder surfaceHolder) { + try { + + + cameraManager.openDriver(surfaceHolder); + // Creating the handler starts the preview, which can also throw a + // RuntimeException. + if (handler == null) { + //decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS); + handler = new CaptureActivityHandler(this, decodeFormats, + characterSet, cameraManager); + } + } catch (IOException ioe) { + Log.w(TAG, ioe); + displayFrameworkBugMessageAndExit("IOException"); + } catch (RuntimeException e) { + // Barcode Scanner has seen crashes in the wild of this variety: + // java.?lang.?RuntimeException: Fail to connect to camera service + Log.w(TAG, "Unexpected error initializating camera", e); + displayFrameworkBugMessageAndExit("RuntimeException"); + } + } + + public Handler getHandler() { + return handler; + } + + ViewfinderView getViewfinderView() { + return viewfinderView; + } + + + public void handleDecode(Result points) { + /* + float[] newPoints = new float[8]; + int i = 0; + for (ResultPoint point : points.getPoints()) { + newPoints[i] = point.getX(); + i++; + newPoints[i] = point.getY(); + i++; + } + resultPoints.setResultPoints(newPoints); + resultPoints.setDestination(destinationPoint.refx,destinationPoint.refy); + + resultPoints.invalidate(); + + //handleDecodeInternally(rawResult, resultHandler, barcode); + if (handler != null) { + handler.sendEmptyMessage(R.id.restart_preview); + } + */ + /* + if (handler != null) { + handler.sendEmptyMessage(R.id.restart_preview); + } + */ + + } + + private void displayFrameworkBugMessageAndExit(String info) { + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(getString(R.string.app_name)); + builder.setMessage("[" + info + "] " + + getString(R.string.msg_camera_framework_bug)); + builder.setPositiveButton(R.string.button_ok, new FinishListener(this)); + builder.setOnCancelListener(new FinishListener(this)); + builder.show(); + } + + public void drawViewfinder() { + // Draw image result + + viewfinderView.drawViewfinder(); + } + + public void removePoints() { + + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + // TODO Auto-generated method stub + super.onCreateOptionsMenu(menu); + + return true; + } + + /** + * A valid barcode has been found, so give an indication of success and show the results. + * + * @param rawResult The contents of the barcode. + * @param barcode A greyscale bitmap of the camera data which was decoded. + */ + public void handleDecode(Result rawResult, Bitmap barcode) { + + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage("Code bar Message : " + rawResult.getText()) + .setCancelable(false) + .setPositiveButton("Yes", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + //restart preview to decode more barcodes + if(handler != null){ + handler.sendEmptyMessage(R.id.restart_preview); + } + } + }); + + AlertDialog alert = builder.create(); + alert.show(); + //Toast.makeText(this, rawResult.getText(), Toast.LENGTH_LONG).show(); + Log.d("BARCODE","Value"+ rawResult.getText()); + + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/CaptureActivityHandler.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/CaptureActivityHandler.java new file mode 100755 index 0000000000..963afb1fa5 --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/CaptureActivityHandler.java @@ -0,0 +1,131 @@ + +package org.evergreen.android.barcodescan; + +import java.util.Vector; + +import org.evergreen.android.R; +import org.evergreen.android.barcodescan.camera.CameraManager; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.Result; + +/** + * This class handles all the messaging which comprises the state machine for capture. +@ + */ +public final class CaptureActivityHandler extends Handler { + + private static final String TAG = CaptureActivityHandler.class.getSimpleName(); + + private final CaptureActivity activity; + private final DecodeThread decodeThread; + private final CameraManager cameraManager; + private State state; + + private enum State { + PREVIEW, + SUCCESS, + DONE + } + + CaptureActivityHandler(CaptureActivity activity, Vector decodeFormats, + String characterSet,CameraManager cameraManager) { + this.activity = activity; + decodeThread = new DecodeThread(activity, decodeFormats, characterSet, + new ViewfinderResultPointCallback(activity.getViewfinderView()), cameraManager); + decodeThread.start(); + state = State.SUCCESS; + + // Start ourselves capturing previews and decoding. + this.cameraManager = cameraManager; + cameraManager.startPreview(); + restartPreviewAndDecode(); + if (state == State.PREVIEW) { + this.cameraManager.requestAutoFocus(this, R.id.auto_focus); + } + + } + @Override + public void handleMessage(Message message) { + switch (message.what) { + case R.id.auto_focus: + //Log.d(TAG, "Got auto-focus message"); + // When one auto focus pass finishes, start another. This is the closest thing to + + + break; + case R.id.decode_succeeded: + Log.d(TAG, "Got decode succeeded message"); + state = State.SUCCESS; + + + Bundle bundle = message.getData(); + Bitmap barcode = bundle == null ? null : + (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP); + + activity.handleDecode((Result) message.obj, barcode); + activity.handleDecode((Result) message.obj); + + break; + case R.id.restart_preview: + restartPreviewAndDecode(); + + break; + + case R.id.decode_failed: + // We're decoding as fast as possible, so when one decode fails, start another. + state = State.PREVIEW; + cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); + activity.removePoints(); + + break; + case R.id.return_scan_result: + Log.d(TAG, "Got return scan result message"); + activity.setResult(Activity.RESULT_OK, (Intent) message.obj); + activity.finish(); + break; + case R.id.launch_product_query: + Log.d(TAG, "Got product query message"); + String url = (String) message.obj; + Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); + activity.startActivity(intent); + break; + } + } + + public void quitSynchronously() { + state = State.DONE; + cameraManager.stopPreview(); + Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit); + quit.sendToTarget(); + try { + decodeThread.join(); + } catch (InterruptedException e) { + // continue + } + + // Be absolutely sure we don't send any queued up messages + removeMessages(R.id.decode_succeeded); + removeMessages(R.id.decode_failed); + } + + private void restartPreviewAndDecode() { + if (state == State.SUCCESS) { + state = State.PREVIEW; + cameraManager.requestPreviewFrame(decodeThread.getHandler(), R.id.decode); + cameraManager.requestAutoFocus(this, R.id.auto_focus); + activity.drawViewfinder(); + } + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeFormatManager.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeFormatManager.java new file mode 100755 index 0000000000..f89f283032 --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeFormatManager.java @@ -0,0 +1,104 @@ +/* + * 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 org.evergreen.android.barcodescan; + +import java.util.Arrays; +import java.util.List; +import java.util.Vector; +import java.util.regex.Pattern; + +import android.content.Intent; +import android.net.Uri; +import com.google.zxing.BarcodeFormat; + +final class DecodeFormatManager { + + private static final Pattern COMMA_PATTERN = Pattern.compile(","); + + static final Vector PRODUCT_FORMATS; + static final Vector ONE_D_FORMATS; + static final Vector QR_CODE_FORMATS; + static final Vector DATA_MATRIX_FORMATS; + static { + PRODUCT_FORMATS = new Vector(5); + PRODUCT_FORMATS.add(BarcodeFormat.UPC_A); + PRODUCT_FORMATS.add(BarcodeFormat.UPC_E); + PRODUCT_FORMATS.add(BarcodeFormat.EAN_13); + PRODUCT_FORMATS.add(BarcodeFormat.EAN_8); + PRODUCT_FORMATS.add(BarcodeFormat.RSS_14); + ONE_D_FORMATS = new Vector(PRODUCT_FORMATS.size() + 4); + ONE_D_FORMATS.addAll(PRODUCT_FORMATS); + ONE_D_FORMATS.add(BarcodeFormat.CODE_39); + ONE_D_FORMATS.add(BarcodeFormat.CODE_93); + ONE_D_FORMATS.add(BarcodeFormat.CODE_128); + ONE_D_FORMATS.add(BarcodeFormat.ITF); + QR_CODE_FORMATS = new Vector(1); + QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE); + DATA_MATRIX_FORMATS = new Vector(1); + DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX); + } + + private DecodeFormatManager() {} + + static Vector parseDecodeFormats(Intent intent) { + List scanFormats = null; + String scanFormatsString = intent.getStringExtra(Intents.Scan.FORMATS); + if (scanFormatsString != null) { + scanFormats = Arrays.asList(COMMA_PATTERN.split(scanFormatsString)); + } + return parseDecodeFormats(scanFormats, intent.getStringExtra(Intents.Scan.MODE)); + } + + static Vector parseDecodeFormats(Uri inputUri) { + List formats = inputUri.getQueryParameters(Intents.Scan.FORMATS); + if (formats != null && formats.size() == 1 && formats.get(0) != null){ + formats = Arrays.asList(COMMA_PATTERN.split(formats.get(0))); + } + return parseDecodeFormats(formats, inputUri.getQueryParameter(Intents.Scan.MODE)); + } + + private static Vector parseDecodeFormats(Iterable scanFormats, + String decodeMode) { + if (scanFormats != null) { + Vector formats = new Vector(); + try { + for (String format : scanFormats) { + formats.add(BarcodeFormat.valueOf(format)); + } + return formats; + } catch (IllegalArgumentException iae) { + // ignore it then + } + } + if (decodeMode != null) { + if (Intents.Scan.PRODUCT_MODE.equals(decodeMode)) { + return PRODUCT_FORMATS; + } + if (Intents.Scan.QR_CODE_MODE.equals(decodeMode)) { + return QR_CODE_FORMATS; + } + if (Intents.Scan.DATA_MATRIX_MODE.equals(decodeMode)) { + return DATA_MATRIX_FORMATS; + } + if (Intents.Scan.ONE_D_MODE.equals(decodeMode)) { + return ONE_D_FORMATS; + } + } + return null; + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeHandler.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeHandler.java new file mode 100755 index 0000000000..8063700037 --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeHandler.java @@ -0,0 +1,122 @@ +/* + * 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 org.evergreen.android.barcodescan; + +import java.util.Hashtable; +import org.evergreen.android.R; +import org.evergreen.android.barcodescan.camera.CameraManager; + +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import android.os.Message; +import android.util.Log; + +import com.google.zxing.BinaryBitmap; +import com.google.zxing.DecodeHintType; +import com.google.zxing.MultiFormatReader; +import com.google.zxing.ReaderException; +import com.google.zxing.Result; +import com.google.zxing.common.HybridBinarizer; + +final class DecodeHandler extends Handler { + + private static final String TAG = DecodeHandler.class.getSimpleName(); + + //Activity used for communication with her's handle + private final CaptureActivity activity; + private CameraManager cameraManager; + //Reader used to decode + private final MultiFormatReader multiFormatReader; + + //running state of this decode thread + private boolean running = true; + + DecodeHandler(CaptureActivity activity, Hashtable hints, CameraManager cameraManager) { + multiFormatReader = new MultiFormatReader(); + multiFormatReader.setHints(hints); + this.activity = activity; + this.cameraManager = cameraManager; + } + + /** + * Method handles messages obtained + * + */ + @Override + public void handleMessage(Message message) { + if (!running) { + return; + //if thread is not running do nothing + } + switch (message.what) { + case R.id.decode: + decode((byte[]) message.obj, message.arg1, message.arg2); + break; + case R.id.quit: + //quit, set running false + running = false; + Looper.myLooper().quit(); + break; + } + } + + + /** + * Decode the data within the viewfinder rectangle, and time how long it took. For efficiency, + * reuse the same reader objects from one decode to the next. + * + * @param data The YUV preview frame. + * @param width The width of the preview frame. + * @param height The height of the preview frame. + */ + private void decode(byte[] data, int width, int height) { + long start = System.currentTimeMillis(); + Result rawResult = null; + PlanarYUVLuminanceSource source = cameraManager.buildLuminanceSource(data, width, height); + if (source != null) { + BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source)); + try { + rawResult = multiFormatReader.decodeWithState(bitmap); + } catch (ReaderException re) { + // continue + } finally { + multiFormatReader.reset(); + } + } + + Handler handler = activity.getHandler(); + if (rawResult != null) { + // Don't log the barcode contents for security. + long end = System.currentTimeMillis(); + Log.d(TAG, "Found barcode in " + (end - start) + " ms"); + if (handler != null) { + Message message = Message.obtain(handler, R.id.decode_succeeded, rawResult); + Bundle bundle = new Bundle(); + bundle.putParcelable(DecodeThread.BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap()); + message.setData(bundle); + message.sendToTarget(); + } + } else { + if (handler != null) { + Message message = Message.obtain(handler, R.id.decode_failed); + message.sendToTarget(); + } + } + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeThread.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeThread.java new file mode 100755 index 0000000000..d48d3e7394 --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeThread.java @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2008 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 org.evergreen.android.barcodescan; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.DecodeHintType; +import com.google.zxing.ResultPointCallback; + +import android.content.SharedPreferences; +import android.os.Handler; +import android.os.Looper; +import android.preference.PreferenceManager; + +import java.util.Hashtable; +import java.util.Vector; +import java.util.concurrent.CountDownLatch; + +import org.evergreen.android.barcodescan.camera.CameraManager; + +/** + * This thread does all the heavy lifting of decoding the images. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +final class DecodeThread extends Thread { + + public static final String BARCODE_BITMAP = "barcode_bitmap"; + + private final CaptureActivity activity; + private final Hashtable hints; + private Handler handler; + private final CountDownLatch handlerInitLatch; + private CameraManager cameraManager; + DecodeThread(CaptureActivity activity, + Vector decodeFormats, + String characterSet, + ResultPointCallback resultPointCallback, CameraManager cameraManager) { + + this.activity = activity; + handlerInitLatch = new CountDownLatch(1); + this.cameraManager = cameraManager; + hints = new Hashtable(3); + + + + // The prefs can't change while the thread is running, so pick them up once here. + if (decodeFormats == null || decodeFormats.isEmpty()) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + decodeFormats = new Vector(); + if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_1D, true)) { + decodeFormats.addAll(DecodeFormatManager.ONE_D_FORMATS); + } + /* + if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_QR, true)) { + decodeFormats.addAll(DecodeFormatManager.QR_CODE_FORMATS); + } + if (prefs.getBoolean(PreferencesActivity.KEY_DECODE_DATA_MATRIX, true)) { + decodeFormats.addAll(DecodeFormatManager.DATA_MATRIX_FORMATS); + } + */ + } + hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats); + + if (characterSet != null) { + hints.put(DecodeHintType.CHARACTER_SET, characterSet); + } + hints.put(DecodeHintType.NEED_RESULT_POINT_CALLBACK, resultPointCallback); + } + + Handler getHandler() { + try { + handlerInitLatch.await(); + } catch (InterruptedException ie) { + // continue? + } + return handler; + } + + @Override + public void run() { + Looper.prepare(); + handler = new DecodeHandler(activity, hints, cameraManager); + handlerInitLatch.countDown(); + Looper.loop(); + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/FinishListener.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/FinishListener.java new file mode 100755 index 0000000000..c184c1aa48 --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/FinishListener.java @@ -0,0 +1,48 @@ +/* + * 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 org.evergreen.android.barcodescan; + +import android.app.Activity; +import android.content.DialogInterface; + +/** + * Simple listener used to exit the app in a few cases. + * + * @author Sean Owen + */ +public final class FinishListener + implements DialogInterface.OnClickListener, DialogInterface.OnCancelListener, Runnable { + + private final Activity activityToFinish; + + public FinishListener(Activity activityToFinish) { + this.activityToFinish = activityToFinish; + } + + public void onCancel(DialogInterface dialogInterface) { + run(); + } + + public void onClick(DialogInterface dialogInterface, int i) { + run(); + } + + public void run() { + activityToFinish.finish(); + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/Functions.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/Functions.java new file mode 100755 index 0000000000..57bee3eb15 --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/Functions.java @@ -0,0 +1,56 @@ +package org.evergreen.android.barcodescan; + +import android.content.Context; +import android.graphics.Color; +import android.widget.TextView; +import android.widget.Toast; + +/** + * + * @author George Oprina + * + * 08.06.2011 + */ + +public class Functions { + + // makes toast length short + public static void makeToast(String s, Context c) { + CharSequence text = s; + int duration = Toast.LENGTH_SHORT; + + Toast toast = Toast.makeText(c, text, duration); + toast.show(); + } + + // makes toast selectable length + public static void makeToast(String s, Context c, int length) { + CharSequence text = s; + int duration = Toast.LENGTH_SHORT; + + if (length == 1) + duration = Toast.LENGTH_LONG; + + Toast toast = Toast.makeText(c, text, duration); + toast.show(); + } + + // makes text view from string + public static TextView makeTextView(String s, Context c) { + TextView text = new TextView(c); + text.setBackgroundColor(Color.WHITE); + text.setText(s); + text.setTextColor(Color.BLACK); + return text; + } + + // thread sleep + public static void sleep(int msec) { + try { + Thread.sleep(msec); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/Intents.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/Intents.java new file mode 100755 index 0000000000..f0edb4264f --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/Intents.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2008 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 org.evergreen.android.barcodescan; + +/** + * This class provides the constants to use when sending an Intent to Barcode Scanner. + * These strings are effectively API and cannot be changed. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class Intents { + private Intents() { + } + + public static final class Scan { + /** + * Send this intent to open the Barcodes app in scanning mode, find a barcode, and return + * the results. + */ + public static final String ACTION = "com.google.zxing.client.android.SCAN"; + + /** + * By default, sending Scan.ACTION will decode all barcodes that we understand. However it + * may be useful to limit scanning to certain formats. Use Intent.putExtra(MODE, value) with + * one of the values below. + * + * Setting this is effectively shorthand for setting explicit formats with {@link #FORMATS}. + * It is overridden by that setting. + */ + public static final String MODE = "SCAN_MODE"; + + /** + * Decode only UPC and EAN barcodes. This is the right choice for shopping apps which get + * prices, reviews, etc. for products. + */ + public static final String PRODUCT_MODE = "PRODUCT_MODE"; + + /** + * Decode only 1D barcodes. + */ + public static final String ONE_D_MODE = "ONE_D_MODE"; + + /** + * Decode only QR codes. + */ + public static final String QR_CODE_MODE = "QR_CODE_MODE"; + + /** + * Decode only Data Matrix codes. + */ + public static final String DATA_MATRIX_MODE = "DATA_MATRIX_MODE"; + + /** + * Comma-separated list of formats to scan for. The values must match the names of + * {@link com.google.zxing.BarcodeFormat}s, e.g. {@link com.google.zxing.BarcodeFormat#EAN_13}. + * Example: "EAN_13,EAN_8,QR_CODE" + * + * This overrides {@link #MODE}. + */ + public static final String FORMATS = "SCAN_FORMATS"; + + /** + * @see com.google.zxing.DecodeHintType#CHARACTER_SET + */ + public static final String CHARACTER_SET = "CHARACTER_SET"; + + /** + * Optional parameters to specify the width and height of the scanning rectangle in pixels. + * The app will try to honor these, but will clamp them to the size of the preview frame. + * You should specify both or neither, and pass the size as an int. + */ + public static final String WIDTH = "SCAN_WIDTH"; + public static final String HEIGHT = "SCAN_HEIGHT"; + + /** + * If a barcode is found, Barcodes returns RESULT_OK to onActivityResult() of the app which + * requested the scan via startSubActivity(). The barcodes contents can be retrieved with + * intent.getStringExtra(RESULT). If the user presses Back, the result code will be + * RESULT_CANCELED. + */ + public static final String RESULT = "SCAN_RESULT"; + + /** + * Call intent.getStringExtra(RESULT_FORMAT) to determine which barcode format was found. + * See Contents.Format for possible values. + */ + public static final String RESULT_FORMAT = "SCAN_RESULT_FORMAT"; + + /** + * Call intent.getByteArrayExtra(RESULT_BYTES) to get a {@link byte[]} of raw bytes in the + * barcode, if available. + */ + public static final String RESULT_BYTES = "SCAN_RESULT_BYTES"; + + /** + * Setting this to false will not save scanned codes in the history. + */ + public static final String SAVE_HISTORY = "SAVE_HISTORY"; + + private Scan() { + } + } + + public static final class Encode { + /** + * Send this intent to encode a piece of data as a QR code and display it full screen, so + * that another person can scan the barcode from your screen. + */ + public static final String ACTION = "com.google.zxing.client.android.ENCODE"; + + /** + * The data to encode. Use Intent.putExtra(DATA, data) where data is either a String or a + * Bundle, depending on the type and format specified. Non-QR Code formats should + * just use a String here. For QR Code, see Contents for details. + */ + public static final String DATA = "ENCODE_DATA"; + + /** + * The type of data being supplied if the format is QR Code. Use + * Intent.putExtra(TYPE, type) with one of Contents.Type. + */ + public static final String TYPE = "ENCODE_TYPE"; + + /** + * The barcode format to be displayed. If this isn't specified or is blank, + * it defaults to QR Code. Use Intent.putExtra(FORMAT, format), where + * format is one of Contents.Format. + */ + public static final String FORMAT = "ENCODE_FORMAT"; + + private Encode() { + } + } + + public static final class SearchBookContents { + /** + * Use Google Book Search to search the contents of the book provided. + */ + public static final String ACTION = "com.google.zxing.client.android.SEARCH_BOOK_CONTENTS"; + + /** + * The book to search, identified by ISBN number. + */ + public static final String ISBN = "ISBN"; + + /** + * An optional field which is the text to search for. + */ + public static final String QUERY = "QUERY"; + + private SearchBookContents() { + } + } + + public static final class WifiConnect { + /** + * Internal intent used to trigger connection to a wi-fi network. + */ + public static final String ACTION = "com.google.zxing.client.android.WIFI_CONNECT"; + + /** + * The network to connect to, all the configuration provided here. + */ + public static final String SSID = "SSID"; + + /** + * The network to connect to, all the configuration provided here. + */ + public static final String TYPE = "TYPE"; + + /** + * The network to connect to, all the configuration provided here. + */ + public static final String PASSWORD = "PASSWORD"; + + private WifiConnect() { + } + } + + public static final class Share { + /** + * Give the user a choice of items to encode as a barcode, then render it as a QR Code and + * display onscreen for a friend to scan with their phone. + */ + public static final String ACTION = "com.google.zxing.client.android.SHARE"; + + private Share() { + } + } +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/PlanarYUVLuminanceSource.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/PlanarYUVLuminanceSource.java new file mode 100755 index 0000000000..86c9f8d33e --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/PlanarYUVLuminanceSource.java @@ -0,0 +1,142 @@ +/* + * Copyright 2009 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 org.evergreen.android.barcodescan; + +import com.google.zxing.LuminanceSource; + +import android.graphics.Bitmap; + +/** + * This object extends LuminanceSource around an array of YUV data returned from the camera driver, + * with the option to crop to a rectangle within the full data. This can be used to exclude + * superfluous pixels around the perimeter and speed up decoding. + * + * It works for any pixel format where the Y channel is planar and appears first, including + * YCbCr_420_SP and YCbCr_422_SP. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class PlanarYUVLuminanceSource extends LuminanceSource { + + private final byte[] yuvData; + private final int dataWidth; + private final int dataHeight; + private final int left; + private final int top; + + public PlanarYUVLuminanceSource(byte[] yuvData, int dataWidth, int dataHeight, int left, int top, + int width, int height, boolean reverseHorizontal) { + super(width, height); + + if (left + width > dataWidth || top + height > dataHeight) { + throw new IllegalArgumentException("Crop rectangle does not fit within image data."); + } + + this.yuvData = yuvData; + this.dataWidth = dataWidth; + this.dataHeight = dataHeight; + this.left = left; + this.top = top; + if (reverseHorizontal) { + reverseHorizontal(width, height); + } + } + + @Override + public byte[] getRow(int y, byte[] row) { + if (y < 0 || y >= getHeight()) { + throw new IllegalArgumentException("Requested row is outside the image: " + y); + } + int width = getWidth(); + if (row == null || row.length < width) { + row = new byte[width]; + } + int offset = (y + top) * dataWidth + left; + System.arraycopy(yuvData, offset, row, 0, width); + return row; + } + + @Override + public byte[] getMatrix() { + int width = getWidth(); + int height = getHeight(); + + // If the caller asks for the entire underlying image, save the copy and give them the + // original data. The docs specifically warn that result.length must be ignored. + if (width == dataWidth && height == dataHeight) { + return yuvData; + } + + int area = width * height; + byte[] matrix = new byte[area]; + int inputOffset = top * dataWidth + left; + + // If the width matches the full width of the underlying data, perform a single copy. + if (width == dataWidth) { + System.arraycopy(yuvData, inputOffset, matrix, 0, area); + return matrix; + } + + // Otherwise copy one cropped row at a time. + byte[] yuv = yuvData; + for (int y = 0; y < height; y++) { + int outputOffset = y * width; + System.arraycopy(yuv, inputOffset, matrix, outputOffset, width); + inputOffset += dataWidth; + } + return matrix; + } + + @Override + public boolean isCropSupported() { + return true; + } + + public Bitmap renderCroppedGreyscaleBitmap() { + int width = getWidth(); + int height = getHeight(); + int[] pixels = new int[width * height]; + byte[] yuv = yuvData; + int inputOffset = top * dataWidth + left; + + for (int y = 0; y < height; y++) { + int outputOffset = y * width; + for (int x = 0; x < width; x++) { + int grey = yuv[inputOffset + x] & 0xff; + pixels[outputOffset + x] = 0xFF000000 | (grey * 0x00010101); + } + inputOffset += dataWidth; + } + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + return bitmap; + } + + private void reverseHorizontal(int width, int height) { + byte[] yuvData = this.yuvData; + for (int y = 0, rowStart = top * dataWidth + left; y < height; y++, rowStart += dataWidth) { + int middle = rowStart + width / 2; + for (int x1 = rowStart, x2 = rowStart + width - 1; x1 < middle; x1++, x2--) { + byte temp = yuvData[x1]; + yuvData[x1] = yuvData[x2]; + yuvData[x2] = temp; + } + } + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/PreferencesActivity.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/PreferencesActivity.java new file mode 100755 index 0000000000..87527e6c9b --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/PreferencesActivity.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2008 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 org.evergreen.android.barcodescan; + +import android.content.SharedPreferences; +import android.content.SharedPreferences.OnSharedPreferenceChangeListener; +import android.os.Bundle; +import android.preference.CheckBoxPreference; +import android.preference.PreferenceActivity; +import android.preference.PreferenceScreen; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * The main settings activity. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class PreferencesActivity extends PreferenceActivity + implements OnSharedPreferenceChangeListener { + + public static final String KEY_DECODE_1D = "preferences_decode_1D"; + public static final String KEY_DECODE_QR = "preferences_decode_QR"; + public static final String KEY_DECODE_DATA_MATRIX = "preferences_decode_Data_Matrix"; + public static final String KEY_CUSTOM_PRODUCT_SEARCH = "preferences_custom_product_search"; + + public static final String KEY_REVERSE_IMAGE = "preferences_reverse_image"; + public static final String KEY_PLAY_BEEP = "preferences_play_beep"; + public static final String KEY_VIBRATE = "preferences_vibrate"; + public static final String KEY_COPY_TO_CLIPBOARD = "preferences_copy_to_clipboard"; + public static final String KEY_FRONT_LIGHT = "preferences_front_light"; + public static final String KEY_BULK_MODE = "preferences_bulk_mode"; + public static final String KEY_REMEMBER_DUPLICATES = "preferences_remember_duplicates"; + public static final String KEY_SUPPLEMENTAL = "preferences_supplemental"; + + public static final String KEY_HELP_VERSION_SHOWN = "preferences_help_version_shown"; + public static final String KEY_NOT_OUR_RESULTS_SHOWN = "preferences_not_out_results_shown"; + + private CheckBoxPreference decode1D; + private CheckBoxPreference decodeQR; + private CheckBoxPreference decodeDataMatrix; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + // addPreferencesFromResource(R.xml.preferences); + + PreferenceScreen preferences = getPreferenceScreen(); + preferences.getSharedPreferences().registerOnSharedPreferenceChangeListener(this); + decode1D = (CheckBoxPreference) preferences.findPreference(KEY_DECODE_1D); + decodeQR = (CheckBoxPreference) preferences.findPreference(KEY_DECODE_QR); + decodeDataMatrix = (CheckBoxPreference) preferences.findPreference(KEY_DECODE_DATA_MATRIX); + disableLastCheckedPref(); + } + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { + disableLastCheckedPref(); + } + + private void disableLastCheckedPref() { + Collection checked = new ArrayList(3); + if (decode1D.isChecked()) { + checked.add(decode1D); + } + if (decodeQR.isChecked()) { + checked.add(decodeQR); + } + if (decodeDataMatrix.isChecked()) { + checked.add(decodeDataMatrix); + } + boolean disable = checked.size() < 2; + CheckBoxPreference[] checkBoxPreferences = {decode1D, decodeQR, decodeDataMatrix}; + for (CheckBoxPreference pref : checkBoxPreferences) { + pref.setEnabled(!(disable && checked.contains(pref))); + } + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/ViewfinderResultPointCallback.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/ViewfinderResultPointCallback.java new file mode 100755 index 0000000000..e64dca3fcb --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/ViewfinderResultPointCallback.java @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009 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 org.evergreen.android.barcodescan; + +import com.google.zxing.ResultPoint; +import com.google.zxing.ResultPointCallback; + +final class ViewfinderResultPointCallback implements ResultPointCallback { + + private final ViewfinderView viewfinderView; + + ViewfinderResultPointCallback(ViewfinderView viewfinderView) { + this.viewfinderView = viewfinderView; + } + + public void foundPossibleResultPoint(ResultPoint point) { + viewfinderView.addPossibleResultPoint(point); + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/ViewfinderView.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/ViewfinderView.java new file mode 100755 index 0000000000..f51c7f13cc --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/ViewfinderView.java @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2008 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 org.evergreen.android.barcodescan; + +import java.util.ArrayList; +import java.util.List; + +import org.evergreen.android.R; +import org.evergreen.android.barcodescan.camera.CameraManager; + +import android.content.Context; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Rect; +import android.util.AttributeSet; +import android.view.View; + +import com.google.zxing.ResultPoint; + +/** + * This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial + * transparency outside it, as well as the laser scanner animation and result points. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public final class ViewfinderView extends View { + + private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64}; + private static final long ANIMATION_DELAY = 80L; + private static final int CURRENT_POINT_OPACITY = 0xA0; + private static final int MAX_RESULT_POINTS = 20; + private static final int POINT_SIZE = 6; + + private CameraManager cameraManager; + private final Paint paint; + private Bitmap resultBitmap; + private int maskColor; + private int resultColor; + private int frameColor; + private int laserColor; + private int resultPointColor; + private int scannerAlpha; + private List possibleResultPoints; + private List lastPossibleResultPoints; + + // This constructor is used when the class is built from an XML resource. + public ViewfinderView(Context context, AttributeSet attrs) { + super(context, attrs); + + // Initialize these once for performance rather than calling them every time in onDraw(). + paint = new Paint(Paint.ANTI_ALIAS_FLAG); + Resources resources = getResources(); + maskColor = resources.getColor(R.color.viewfinder_mask); + resultColor = resources.getColor(R.color.result_view); + frameColor = resources.getColor(R.color.viewfinder_frame); + laserColor = resources.getColor(R.color.viewfinder_laser); + resultPointColor = resources.getColor(R.color.possible_result_points); + scannerAlpha = 0; + possibleResultPoints = new ArrayList(5); + lastPossibleResultPoints = null; + } + + public void setCameraManager(CameraManager cameraManager) { + this.cameraManager = cameraManager; + } + + + @Override + public void onDraw(Canvas canvas) { + Rect frame = cameraManager.getFramingRect(); + if (frame == null) { + return; + } + int width = canvas.getWidth(); + int height = canvas.getHeight(); + + // Draw the exterior (i.e. outside the framing rect) darkened + paint.setColor(resultBitmap != null ? resultColor : maskColor); + + canvas.drawRect(0, 0, width, frame.top, paint); + canvas.drawRect(0, frame.top, frame.left, frame.bottom + 1, paint); + canvas.drawRect(frame.right + 1, frame.top, width, frame.bottom + 1, paint); + canvas.drawRect(0, frame.bottom + 1, width, height, paint); + + if (resultBitmap != null) { + // Draw the opaque result bitmap over the scanning rectangle + paint.setAlpha(CURRENT_POINT_OPACITY); + canvas.drawBitmap(resultBitmap, null, frame, paint); + } else { + + // Draw a two pixel solid black border inside the framing rect + paint.setColor(frameColor); + canvas.drawRect(frame.left, frame.top, frame.right + 1, frame.top + 2, paint); + canvas.drawRect(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1, paint); + canvas.drawRect(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1, paint); + canvas.drawRect(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1, paint); + + // Draw a red "laser scanner" line through the middle to show decoding is active + paint.setColor(laserColor); + paint.setAlpha(SCANNER_ALPHA[scannerAlpha]); + scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length; + int middle = frame.height() / 2 + frame.top; + canvas.drawRect(frame.left + 2, middle - 1, frame.right - 1, middle + 2, paint); + + Rect previewFrame = cameraManager.getFramingRectInPreview(); + float scaleX = frame.width() / (float) previewFrame.width(); + float scaleY = frame.height() / (float) previewFrame.height(); + + List currentPossible = possibleResultPoints; + List currentLast = lastPossibleResultPoints; + int frameLeft = frame.left; + int frameTop = frame.top; + if (currentPossible.isEmpty()) { + lastPossibleResultPoints = null; + } else { + possibleResultPoints = new ArrayList(5); + lastPossibleResultPoints = currentPossible; + paint.setAlpha(CURRENT_POINT_OPACITY); + paint.setColor(resultPointColor); + synchronized (currentPossible) { + for (ResultPoint point : currentPossible) { + canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX), + frameTop + (int) (point.getY() * scaleY), + POINT_SIZE, paint); + } + } + } + if (currentLast != null) { + paint.setAlpha(CURRENT_POINT_OPACITY / 2); + paint.setColor(resultPointColor); + synchronized (currentLast) { + float radius = POINT_SIZE / 2.0f; + for (ResultPoint point : currentLast) { + canvas.drawCircle(frameLeft + (int) (point.getX() * scaleX), + frameTop + (int) (point.getY() * scaleY), + radius, paint); + } + } + } + + // Request another update at the animation interval, but only repaint the laser line, + // not the entire viewfinder mask. + postInvalidateDelayed(ANIMATION_DELAY, + frame.left - POINT_SIZE, + frame.top - POINT_SIZE, + frame.right + POINT_SIZE, + frame.bottom + POINT_SIZE); + } + } + + public void drawViewfinder() { + Bitmap resultBitmap = this.resultBitmap; + this.resultBitmap = null; + if (resultBitmap != null) { + resultBitmap.recycle(); + } + invalidate(); + } + + /** + * Draw a bitmap with the result points highlighted instead of the live scanning display. + * + * @param barcode An image of the decoded barcode. + */ + public void drawResultBitmap(Bitmap barcode) { + resultBitmap = barcode; + invalidate(); + } + + public void addPossibleResultPoint(ResultPoint point) { + List points = possibleResultPoints; + synchronized (points) { + points.add(point); + int size = points.size(); + if (size > MAX_RESULT_POINTS) { + // trim it + points.subList(0, size - MAX_RESULT_POINTS / 2).clear(); + } + } + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/AutoFocusCallback.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/AutoFocusCallback.java new file mode 100755 index 0000000000..3ad0a735ea --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/AutoFocusCallback.java @@ -0,0 +1,51 @@ +/* + * 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 org.evergreen.android.barcodescan.camera; + +import android.hardware.Camera; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +final class AutoFocusCallback implements Camera.AutoFocusCallback { + + private static final String TAG = AutoFocusCallback.class.getSimpleName(); + + private static final long AUTOFOCUS_INTERVAL_MS = 1500L; + + private Handler autoFocusHandler; + private int autoFocusMessage; + + void setHandler(Handler autoFocusHandler, int autoFocusMessage) { + this.autoFocusHandler = autoFocusHandler; + this.autoFocusMessage = autoFocusMessage; + } + + public void onAutoFocus(boolean success, Camera camera) { + if (autoFocusHandler != null) { + Message message = autoFocusHandler.obtainMessage(autoFocusMessage, success); + // Simulate continuous autofocus by sending a focus request every + // AUTOFOCUS_INTERVAL_MS milliseconds. + //Log.d(TAG, "Got auto-focus callback; requesting another"); + autoFocusHandler.sendMessageDelayed(message, AUTOFOCUS_INTERVAL_MS); + autoFocusHandler = null; + } else { + Log.d(TAG, "Got auto-focus callback, but no handler for it"); + } + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/CameraConfigurationManager.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/CameraConfigurationManager.java new file mode 100755 index 0000000000..d2327b0b6e --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/CameraConfigurationManager.java @@ -0,0 +1,180 @@ +/* + * 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 org.evergreen.android.barcodescan.camera; + +import java.util.Collection; + +import org.evergreen.android.barcodescan.PreferencesActivity; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Point; +import android.hardware.Camera; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.Display; +import android.view.WindowManager; + +/** + * A class which deals with reading, parsing, and setting the camera parameters which are used to + * configure the camera hardware. + */ +final class CameraConfigurationManager { + + private static final String TAG = "CameraConfiguration"; + private static final int MIN_PREVIEW_PIXELS = 320 * 240; // small screen + private static final int MAX_PREVIEW_PIXELS = 800 * 480; // large/HD screen + + private final Context context; + private Point screenResolution; + private Point cameraResolution; + + CameraConfigurationManager(Context context) { + this.context = context; + } + + /** + * Reads, one time, values from the camera that are needed by the app. + */ + void initFromCameraParameters(Camera camera) { + Camera.Parameters parameters = camera.getParameters(); + WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE); + Display display = manager.getDefaultDisplay(); + int width = display.getWidth(); + int height = display.getHeight(); + // We're landscape-only, and have apparently seen issues with display thinking it's portrait + // when waking from sleep. If it's not landscape, assume it's mistaken and reverse them: + if (width < height) { + Log.i(TAG, "Display reports portrait orientation; assuming this is incorrect"); + int temp = width; + width = height; + height = temp; + } + screenResolution = new Point(width, height); + Log.i(TAG, "Screen resolution: " + screenResolution); + cameraResolution = findBestPreviewSizeValue(parameters, screenResolution, false); + Log.i(TAG, "Camera resolution: " + cameraResolution); + } + + void setDesiredCameraParameters(Camera camera) { + Camera.Parameters parameters = camera.getParameters(); + + 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); + camera.setParameters(parameters); + } + + Point getCameraResolution() { + return cameraResolution; + } + + Point getScreenResolution() { + return screenResolution; + } + + 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(); + } + } + + private static void initializeTorch(Camera.Parameters parameters, SharedPreferences prefs) { + boolean currentSetting = prefs.getBoolean(PreferencesActivity.KEY_FRONT_LIGHT, false); + doSetTorch(parameters, currentSetting); + } + + 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); + } + if (flashMode != null) { + parameters.setFlashMode(flashMode); + } + } + + private static Point findBestPreviewSizeValue(Camera.Parameters parameters, + Point screenResolution, + boolean portrait) { + Point bestSize = null; + int diff = Integer.MAX_VALUE; + for (Camera.Size supportedPreviewSize : parameters.getSupportedPreviewSizes()) { + int pixels = supportedPreviewSize.height * supportedPreviewSize.width; + if (pixels < MIN_PREVIEW_PIXELS || pixels > MAX_PREVIEW_PIXELS) { + continue; + } + 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) { + bestSize = new Point(supportedWidth, supportedHeight); + break; + } + if (newDiff < diff) { + bestSize = new Point(supportedWidth, supportedHeight); + diff = newDiff; + } + } + if (bestSize == null) { + Camera.Size defaultSize = parameters.getPreviewSize(); + bestSize = new Point(defaultSize.width, defaultSize.height); + } + return bestSize; + } + + private static String findSettableValue(Collection 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; + } + } + } + Log.i(TAG, "Settable value: " + result); + return result; + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/CameraManager.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/CameraManager.java new file mode 100755 index 0000000000..60a442d725 --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/CameraManager.java @@ -0,0 +1,280 @@ +/* + * Copyright (C) 2008 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 org.evergreen.android.barcodescan.camera; + +import java.io.IOException; + +import org.evergreen.android.barcodescan.PlanarYUVLuminanceSource; +import org.evergreen.android.barcodescan.PreferencesActivity; + +import android.content.Context; +import android.content.SharedPreferences; +import android.graphics.Point; +import android.graphics.Rect; +import android.hardware.Camera; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.SurfaceHolder; + +/** + * 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) + */ +public final class CameraManager { + + private static final String TAG = CameraManager.class.getSimpleName(); + + private static final int MIN_FRAME_WIDTH = 240; + private static final int MIN_FRAME_HEIGHT = 240; + private static final int MAX_FRAME_WIDTH = 600; + private static final int MAX_FRAME_HEIGHT = 400; + + private final Context context; + private final CameraConfigurationManager configManager; + private Camera camera; + private Rect framingRect; + private Rect framingRectInPreview; + private boolean initialized; + private boolean previewing; + private boolean reverseImage; + private int requestedFramingRectWidth; + private int requestedFramingRectHeight; + /** + * 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 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); + previewCallback = new PreviewCallback(configManager); + autoFocusCallback = new AutoFocusCallback(); + } + + /** + * 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 void openDriver(SurfaceHolder holder) throws IOException { + Camera theCamera = camera; + if (theCamera == null) { + theCamera = Camera.open(); + if (theCamera == null) { + throw new IOException(); + } + camera = theCamera; + } + theCamera.setPreviewDisplay(holder); + + if (!initialized) { + initialized = true; + configManager.initFromCameraParameters(theCamera); + if (requestedFramingRectWidth > 0 && requestedFramingRectHeight > 0) { + setManualFramingRect(requestedFramingRectWidth, requestedFramingRectHeight); + requestedFramingRectWidth = 0; + requestedFramingRectHeight = 0; + } + } + configManager.setDesiredCameraParameters(theCamera); + + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context); + reverseImage = prefs.getBoolean(PreferencesActivity.KEY_REVERSE_IMAGE, false); + } + + /** + * Closes the camera driver if still in use. + */ + public void closeDriver() { + if (camera != null) { + 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; + framingRectInPreview = null; + } + } + + /** + * Asks the camera hardware to begin drawing preview frames to the screen. + */ + public void startPreview() { + Camera theCamera = camera; + if (theCamera != null && !previewing) { + theCamera.startPreview(); + previewing = true; + } + } + + /** + * Tells the camera to stop drawing preview frames. + */ + public void stopPreview() { + if (camera != null && previewing) { + camera.stopPreview(); + previewCallback.setHandler(null, 0); + autoFocusCallback.setHandler(null, 0); + 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. + * + * @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) { + Camera theCamera = camera; + if (theCamera != null && previewing) { + previewCallback.setHandler(handler, message); + theCamera.setOneShotPreviewCallback(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 (camera != null && previewing) { + autoFocusCallback.setHandler(handler, message); + try { + camera.autoFocus(autoFocusCallback); + } catch (RuntimeException re) { + // Have heard RuntimeException reported in Android 4.0.x+; continue? + Log.w(TAG, "Unexpected exception while focusing", re); + } + } + } + + /** + * Calculates the framing rect which the UI should draw to show the user where to place the + * 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 (framingRect == null) { + if (camera == null) { + return null; + } + Point screenResolution = configManager.getScreenResolution(); + int width = screenResolution.x * 3 / 4; + if (width < MIN_FRAME_WIDTH) { + width = MIN_FRAME_WIDTH; + } else if (width > MAX_FRAME_WIDTH) { + width = MAX_FRAME_WIDTH; + } + int height = screenResolution.y * 3 / 4; + if (height < MIN_FRAME_HEIGHT) { + height = MIN_FRAME_HEIGHT; + } else if (height > MAX_FRAME_HEIGHT) { + height = MAX_FRAME_HEIGHT; + } + int leftOffset = (screenResolution.x - width) / 2; + int topOffset = (screenResolution.y - height) / 2; + framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); + Log.d(TAG, "Calculated framing rect: " + framingRect); + } + return framingRect; + } + + /** + * Like {@link #getFramingRect} but coordinates are in terms of the preview frame, + * not UI / screen. + */ + public Rect getFramingRectInPreview() { + if (framingRectInPreview == null) { + Rect framingRect = getFramingRect(); + if (framingRect == null) { + return null; + } + Rect rect = new Rect(framingRect); + Point cameraResolution = configManager.getCameraResolution(); + Point screenResolution = configManager.getScreenResolution(); + rect.left = rect.left * cameraResolution.x / screenResolution.x; + rect.right = rect.right * cameraResolution.x / screenResolution.x; + rect.top = rect.top * cameraResolution.y / screenResolution.y; + rect.bottom = rect.bottom * cameraResolution.y / screenResolution.y; + framingRectInPreview = rect; + } + return framingRectInPreview; + } + + /** + * Allows third party apps to specify the scanning rectangle dimensions, rather than determine + * them automatically based on screen resolution. + * + * @param width The width in pixels to scan. + * @param height The height in pixels to scan. + */ + public void setManualFramingRect(int width, int height) { + if (initialized) { + Point screenResolution = configManager.getScreenResolution(); + if (width > screenResolution.x) { + width = screenResolution.x; + } + if (height > screenResolution.y) { + height = screenResolution.y; + } + int leftOffset = (screenResolution.x - width) / 2; + int topOffset = (screenResolution.y - height) / 2; + framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height); + Log.d(TAG, "Calculated manual framing rect: " + framingRect); + framingRectInPreview = null; + } else { + requestedFramingRectWidth = width; + requestedFramingRectHeight = height; + } + } + + /** + * A factory method to build the appropriate LuminanceSource object based on the format + * of the preview buffers, as described by Camera.Parameters. + * + * @param data A preview frame. + * @param width The width of the image. + * @param height The height of the image. + * @return A PlanarYUVLuminanceSource instance. + */ + public PlanarYUVLuminanceSource buildLuminanceSource(byte[] data, int width, int height) { + Rect rect = getFramingRectInPreview(); + if (rect == null) { + return null; + } + // 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); + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/FlashlightManager.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/FlashlightManager.java new file mode 100755 index 0000000000..40393e9d6c --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/FlashlightManager.java @@ -0,0 +1,145 @@ +/* + * 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 org.evergreen.android.barcodescan.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(); + setFlashEnabledMethod = getSetFlashEnabledMethod(iHardwareService); + if (iHardwareService == null) { + Log.v(TAG, "This device does supports control of a flashlight"); + } else { + Log.v(TAG, "This device does not support control of a flashlight"); + } + } + + 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); + } + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/PreviewCallback.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/PreviewCallback.java new file mode 100755 index 0000000000..a371a990c9 --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/PreviewCallback.java @@ -0,0 +1,71 @@ +/* + * 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 org.evergreen.android.barcodescan.camera; +/* + * 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. + */ + +import android.graphics.Point; +import android.hardware.Camera; +import android.os.Handler; +import android.os.Message; +import android.util.Log; + +final class PreviewCallback implements Camera.PreviewCallback { + + private static final String TAG = PreviewCallback.class.getSimpleName(); + + private final CameraConfigurationManager configManager; + private Handler previewHandler; + private int previewMessage; + + PreviewCallback(CameraConfigurationManager configManager) { + this.configManager = configManager; + } + + void setHandler(Handler previewHandler, int previewMessage) { + this.previewHandler = previewHandler; + this.previewMessage = previewMessage; + } + + @Override + public void onPreviewFrame(byte[] data, Camera camera) { + Point cameraResolution = configManager.getCameraResolution(); + Handler thePreviewHandler = previewHandler; + if (thePreviewHandler != null) { + Message message = thePreviewHandler.obtainMessage(previewMessage, cameraResolution.x, + cameraResolution.y, data); + message.sendToTarget(); + previewHandler = null; + } else { + Log.d(TAG, "Got preview callback, but no handler for it"); + } + } + +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/result/ResultHandler.java b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/result/ResultHandler.java new file mode 100755 index 0000000000..ed8b3e6585 --- /dev/null +++ b/Open-ILS/src/Android/src/org/evergreen/android/barcodescan/result/ResultHandler.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2008 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 org.evergreen.android.barcodescan.result; + +import com.google.zxing.Result; +import com.google.zxing.client.result.ParsedResult; +import com.google.zxing.client.result.ResultParser; + +import android.app.Activity; + +/** + * A base class for the Android-specific barcode handlers. These allow the app to polymorphically + * suggest the appropriate actions for each data type. + * + * This class also contains a bunch of utility methods to take common actions like opening a URL. + * They could easily be moved into a helper object, but it can't be static because the Activity + * instance is needed to launch an intent. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +public class ResultHandler { + + private final ParsedResult result; + private final Activity activity; + private final Result rawResult; + + public ResultHandler(Activity activity, Result rawResult) { + this.activity = activity; + this.rawResult = rawResult; + result = parseResult(rawResult); + } + + + public ParsedResult getResult() { + return result; + } + + private static ParsedResult parseResult(Result rawResult) { + return ResultParser.parseResult(rawResult); + } + + /** + * Create a possibly styled string for the contents of the current barcode. + * + * @return The text to be displayed. + */ + public CharSequence getDisplayContents() { + String contents = result.getDisplayResult(); + return contents.replace("\r", ""); + } +} diff --git a/Open-ILS/src/Android/src/org/evergreen/android/searchCatalog/SearchCatalogListView.java b/Open-ILS/src/Android/src/org/evergreen/android/searchCatalog/SearchCatalogListView.java index 7eb318cdf8..12fdd1d950 100644 --- a/Open-ILS/src/Android/src/org/evergreen/android/searchCatalog/SearchCatalogListView.java +++ b/Open-ILS/src/Android/src/org/evergreen/android/searchCatalog/SearchCatalogListView.java @@ -8,6 +8,7 @@ import org.evergreen.android.accountAccess.AccountAccess; import org.evergreen.android.accountAccess.SessionNotFoundException; import org.evergreen.android.accountAccess.bookbags.BookBag; import org.evergreen.android.accountAccess.holds.PlaceHold; +import org.evergreen.android.barcodescan.CaptureActivity; import org.evergreen.android.globals.GlobalConfigs; import org.evergreen.android.globals.NoAccessToServer; import org.evergreen.android.globals.NoNetworkAccessException; @@ -168,7 +169,14 @@ public class SearchCatalogListView extends Activity{ }); barcodeScanButton = (ImageButton) findViewById(R.id.barcode_scan_button); - + barcodeScanButton.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + Intent barcodeScan = new Intent(getApplicationContext(), CaptureActivity.class); + startActivityForResult(barcodeScan, 10); + } + }); //singleton initialize necessary IDL and Org data globalConfigs = GlobalConfigs.getGlobalConfigs(this);