barcode scan functionality added (zxing library)
authordrizea <danielrizea27@gmail.com>
Tue, 7 Aug 2012 17:29:04 +0000 (20:29 +0300)
committerdrizea <danielrizea27@gmail.com>
Tue, 7 Aug 2012 17:29:04 +0000 (20:29 +0300)
28 files changed:
Open-ILS/src/Android/.classpath
Open-ILS/src/Android/AndroidManifest.xml
Open-ILS/src/Android/libs/zxing_barcode.jar [new file with mode: 0644]
Open-ILS/src/Android/res/drawable/header_rounded_corners.xml
Open-ILS/src/Android/res/layout/barcode_scan.xml [new file with mode: 0755]
Open-ILS/src/Android/res/layout/copy_information_more.xml
Open-ILS/src/Android/res/values/colors.xml
Open-ILS/src/Android/res/values/ids.xml
Open-ILS/src/Android/res/values/strings.xml
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/CaptureActivity.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/CaptureActivityHandler.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeFormatManager.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeHandler.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/DecodeThread.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/FinishListener.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/Functions.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/Intents.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/PlanarYUVLuminanceSource.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/PreferencesActivity.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/ViewfinderResultPointCallback.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/ViewfinderView.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/AutoFocusCallback.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/CameraConfigurationManager.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/CameraManager.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/FlashlightManager.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/camera/PreviewCallback.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/barcodescan/result/ResultHandler.java [new file with mode: 0755]
Open-ILS/src/Android/src/org/evergreen/android/searchCatalog/SearchCatalogListView.java

index 1b0ce02..3c52578 100644 (file)
@@ -8,5 +8,6 @@
        <classpathentry kind="lib" path="libs/org.openils_idl.jar"/>
        <classpathentry kind="lib" path="libs/org.opensrf2_serialized_reg.jar"/>
        <classpathentry kind="lib" path="libs/simple-xml-2.6.4.jar"/>
+       <classpathentry kind="lib" path="libs/zxing_barcode.jar"/>
        <classpathentry kind="output" path="bin/classes"/>
 </classpath>
index 7685a0d..1c653d3 100644 (file)
@@ -7,6 +7,14 @@
     <uses-sdk android:minSdkVersion="3" android:targetSdkVersion="14" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
        <uses-permission android:name="android.permission.INTERNET" /> 
+       
+       <uses-permission android:name="android.permission.CAMERA" />
+       <uses-permission android:name="android.permission.VIBRATE"/>
+       <uses-permission android:name="android.permission.FLASHLIGHT"/>
+       <uses-permission android:name="android.permission.WAKE_LOCK" />
+       <uses-feature android:name="android.hardware.camera" />
+       <uses-feature android:name="android.hardware.camera.autofocus" />
+       
     <application
         android:icon="@drawable/evergreen_launcher_icon"
         android:label="@string/app_name" 
         <activity 
             android:name=".searchCatalog.AdvancedSearchActivity">
         </activity>
+        <activity android:name=".barcodescan.CaptureActivity"
+                  android:label="@string/app_name"
+                  android:screenOrientation="landscape"
+                  android:theme="@android:style/Theme.NoTitleBar"
+                  android:windowSoftInputMode="stateAlwaysHidden">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        
         
         <!-- Checkout Activities -->
         <activity android:name=".accountAccess.checkout.ItemsCheckOutListView"></activity>
diff --git a/Open-ILS/src/Android/libs/zxing_barcode.jar b/Open-ILS/src/Android/libs/zxing_barcode.jar
new file mode 100644 (file)
index 0000000..6c27323
Binary files /dev/null and b/Open-ILS/src/Android/libs/zxing_barcode.jar differ
index 98b40dd..b78206b 100644 (file)
@@ -8,7 +8,9 @@
             <gradient
                 android:angle="270"
                 android:endColor="@color/header_gradient_stop"
-                android:startColor="@color/header_gradient_start" />
+                android:startColor="@color/header_gradient_start" 
+                
+                />
 
             <corners
                 android:bottomLeftRadius="8dp"
@@ -23,7 +25,9 @@
                 android:angle="270"
                 android:centerColor="#00ffffff"
                 android:endColor="#00ffffff"
-                android:startColor="#40ffffff" />
+                android:startColor="#40ffffff" 
+                android:useLevel="false"
+                />
 
             <corners
                 android:bottomLeftRadius="8dp"
diff --git a/Open-ILS/src/Android/res/layout/barcode_scan.xml b/Open-ILS/src/Android/res/layout/barcode_scan.xml
new file mode 100755 (executable)
index 0000000..ea1f8ef
--- /dev/null
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+             android:layout_width="fill_parent"
+             android:layout_height="fill_parent"
+             android:orientation="vertical">
+
+  
+  <FrameLayout  android:layout_width="fill_parent"
+               android:layout_height="fill_parent">
+         <SurfaceView android:id="@+id/camera_view"
+                      android:layout_width="fill_parent"
+                      android:layout_height="fill_parent"
+                      android:layout_centerInParent="true"/>
+                      
+         <org.evergreen.android.barcodescan.ViewfinderView
+             android:id="@+id/viewfinder_view"
+             android:layout_width="fill_parent"
+             android:layout_height="fill_parent"/>
+             
+         <ImageView    android:id="@+id/actionbar_bottom"
+                                       android:layout_width="fill_parent"
+                       android:layout_height="fill_parent"
+                       />
+
+   </FrameLayout>
+</LinearLayout>
index df1baee..a945767 100644 (file)
@@ -5,10 +5,19 @@
     android:orientation="vertical"
     android:layout_width="fill_parent"
     android:layout_height="fill_parent"
+    android:padding="4dip"
     >
 
        <include android:id="@+id/header_actionbar" layout="@layout/simple_actionbar"/>
        
+       <TextView
+           android:layout_width="wrap_content"
+           android:layout_height="wrap_content"
+           android:text="@string/copy_information"
+           android:layout_marginTop="5dip"
+           android:layout_marginBottom="5dip"
+           />
+       
        <LinearLayout
                android:id="@+id/record_details_copy_information"
                android:layout_width="fill_parent"
index 3b06b16..b5d44b5 100644 (file)
     
     <!-- Search Details -->
     <color name="dark">#000</color>
+    
+    
+    
+    <!-- Barcode -->
+      <color name="contents_text">#ff000000</color>
+  <color name="encode_view">#ffffffff</color>
+  <color name="help_button_view">#ffcccccc</color>
+  <color name="help_view">#ff404040</color>
+  <color name="possible_result_points">#c0ffff00</color>
+  <color name="result_image_border">#ffffffff</color>
+  <color name="result_minor_text">#ffc0c0c0</color>
+  <color name="result_points">#c000ff00</color>
+  <color name="result_text">#ffffffff</color>
+  <color name="result_view">#b0000000</color>
+  <color name="sbc_header_text">#ff808080</color>
+  <color name="sbc_header_view">#ffffffff</color>
+  <color name="sbc_list_item">#fffff0e0</color>
+  <color name="sbc_layout_view">#ffffffff</color>
+  <color name="sbc_page_number_text">#ff000000</color>
+  <color name="sbc_snippet_text">#ff4b4b4b</color>
+  <color name="share_text">#ff000000</color>
+  <color name="status_view">#50000000</color>
+  <color name="status_text">#ffffffff</color>
+  <color name="transparent">#00000000</color>
+  <color name="viewfinder_frame">#ff000000</color>
+  <color name="viewfinder_laser">#ffff0000</color>
+  <color name="viewfinder_mask">#60000000</color>
+  <color name="destionation_color">#a0ff0000</color>
 </resources>
index e0a4745..89a6d61 100644 (file)
     <item type="id" name="actionbar_compat_item_refresh_progress" />
     <item type="id" name="actionbar_compat_item_refresh" />
     <item type="id" name="menu_refresh" />
+    
+      <!-- Messages IDs -->
+  <item type="id" name="auto_focus"/>
+  <item type="id" name="decode"/>
+  <item type="id" name="decode_failed"/>
+  <item type="id" name="decode_succeeded"/>
+  <item type="id" name="launch_product_query"/>
+  <item type="id" name="quit"/>
+  <item type="id" name="restart_preview"/>
+  <item type="id" name="return_scan_result"/>
 </resources>
index 1fe47d9..b1adcc2 100644 (file)
@@ -34,6 +34,8 @@
     <string name="cancel_button">Cancel</string>
     <string name="connect_button">Connect</string>
     
+    <!-- Search -->
+    
     <string name="search_hint">What are you looking for?</string>
     <string name="advanced_search">Search</string>
     
@@ -45,7 +47,7 @@
     <string name="menu_button_library_hours">Library hours</string>
     <string name="menu_button_application_preferences">Preferences</string>
     
-    
+    <string name="copy_information"> Availability of the book :</string>
     <string name="hello">Hello World, EvergreenAppActivity!</string>
     <string name="app_name">EvergreenApp</string>
 
     <string name="create">create</string>
     <string name="bookbag_name">bookbag name</string>
     <string name="add_button">add</string>
+    
+    
+    <!--  Camera -->
+     <string name="msg_camera_framework_bug">Sorry, the Android camera encountered a problem. You may need to restart the device.</string>
+     <string name="button_ok">OK</string>
 </resources>
\ 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 (executable)
index 0000000..3ecade7
--- /dev/null
@@ -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<BarcodeFormat> 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 (executable)
index 0000000..963afb1
--- /dev/null
@@ -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<BarcodeFormat> 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 (executable)
index 0000000..f89f283
--- /dev/null
@@ -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<BarcodeFormat> PRODUCT_FORMATS;
+  static final Vector<BarcodeFormat> ONE_D_FORMATS;
+  static final Vector<BarcodeFormat> QR_CODE_FORMATS;
+  static final Vector<BarcodeFormat> DATA_MATRIX_FORMATS;
+  static {
+    PRODUCT_FORMATS = new Vector<BarcodeFormat>(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<BarcodeFormat>(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<BarcodeFormat>(1);
+    QR_CODE_FORMATS.add(BarcodeFormat.QR_CODE);
+    DATA_MATRIX_FORMATS = new Vector<BarcodeFormat>(1);
+    DATA_MATRIX_FORMATS.add(BarcodeFormat.DATA_MATRIX);
+  }
+
+  private DecodeFormatManager() {}
+
+  static Vector<BarcodeFormat> parseDecodeFormats(Intent intent) {
+    List<String> 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<BarcodeFormat> parseDecodeFormats(Uri inputUri) {
+    List<String> 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<BarcodeFormat> parseDecodeFormats(Iterable<String> scanFormats,
+                                                          String decodeMode) {
+    if (scanFormats != null) {
+      Vector<BarcodeFormat> formats = new Vector<BarcodeFormat>();
+      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 (executable)
index 0000000..8063700
--- /dev/null
@@ -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<DecodeHintType, Object> 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 (executable)
index 0000000..d48d3e7
--- /dev/null
@@ -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<DecodeHintType, Object> hints;
+  private Handler handler;
+  private final CountDownLatch handlerInitLatch;
+  private CameraManager cameraManager;
+  DecodeThread(CaptureActivity activity,
+               Vector<BarcodeFormat> decodeFormats,
+               String characterSet,
+               ResultPointCallback resultPointCallback, CameraManager cameraManager) {
+
+    this.activity = activity;
+    handlerInitLatch = new CountDownLatch(1);
+    this.cameraManager = cameraManager;
+    hints = new Hashtable<DecodeHintType, Object>(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<BarcodeFormat>();
+      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 (executable)
index 0000000..c184c1a
--- /dev/null
@@ -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 (executable)
index 0000000..57bee3e
--- /dev/null
@@ -0,0 +1,56 @@
+package org.evergreen.android.barcodescan;\r
+\r
+import android.content.Context;\r
+import android.graphics.Color;\r
+import android.widget.TextView;\r
+import android.widget.Toast;\r
+\r
+/**\r
+ * \r
+ * @author George Oprina\r
+ * \r
+ *         08.06.2011\r
+ */\r
+\r
+public class Functions {\r
+\r
+       // makes toast length short\r
+       public static void makeToast(String s, Context c) {\r
+               CharSequence text = s;\r
+               int duration = Toast.LENGTH_SHORT;\r
+\r
+               Toast toast = Toast.makeText(c, text, duration);\r
+               toast.show();\r
+       }\r
+\r
+       // makes toast selectable length\r
+       public static void makeToast(String s, Context c, int length) {\r
+               CharSequence text = s;\r
+               int duration = Toast.LENGTH_SHORT;\r
+\r
+               if (length == 1)\r
+                       duration = Toast.LENGTH_LONG;\r
+\r
+               Toast toast = Toast.makeText(c, text, duration);\r
+               toast.show();\r
+       }\r
+\r
+       // makes text view from string\r
+       public static TextView makeTextView(String s, Context c) {\r
+               TextView text = new TextView(c);\r
+               text.setBackgroundColor(Color.WHITE);\r
+               text.setText(s);\r
+               text.setTextColor(Color.BLACK);\r
+               return text;\r
+       }\r
+\r
+       // thread sleep\r
+       public static void sleep(int msec) {\r
+               try {\r
+                       Thread.sleep(msec);\r
+               } catch (InterruptedException e) {\r
+                       e.printStackTrace();\r
+               }\r
+       }\r
+\r
+}\r
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 (executable)
index 0000000..f0edb42
--- /dev/null
@@ -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 (executable)
index 0000000..86c9f8d
--- /dev/null
@@ -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 (executable)
index 0000000..87527e6
--- /dev/null
@@ -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<CheckBoxPreference> checked = new ArrayList<CheckBoxPreference>(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 (executable)
index 0000000..e64dca3
--- /dev/null
@@ -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 (executable)
index 0000000..f51c7f1
--- /dev/null
@@ -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<ResultPoint> possibleResultPoints;
+  private List<ResultPoint> 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<ResultPoint>(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<ResultPoint> currentPossible = possibleResultPoints;
+             List<ResultPoint> currentLast = lastPossibleResultPoints;
+             int frameLeft = frame.left;
+             int frameTop = frame.top;
+             if (currentPossible.isEmpty()) {
+               lastPossibleResultPoints = null;
+             } else {
+               possibleResultPoints = new ArrayList<ResultPoint>(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<ResultPoint> 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 (executable)
index 0000000..3ad0a73
--- /dev/null
@@ -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 (executable)
index 0000000..d2327b0
--- /dev/null
@@ -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<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;
+        }
+      }
+    }
+    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 (executable)
index 0000000..60a442d
--- /dev/null
@@ -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 (executable)
index 0000000..40393e9
--- /dev/null
@@ -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 (executable)
index 0000000..a371a99
--- /dev/null
@@ -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 (executable)
index 0000000..ed8b3e6
--- /dev/null
@@ -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", "");
+  }
+}
index 7eb318c..12fdd1d 100644 (file)
@@ -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);