Lots of updates:

- Added support for new YUYV preview buffer format.
- Made the preview rectangle larger.
- Added support for high resolution screens.
- Updated for Donut but still runs on Cupcake.
- Converted to standard Java coding style for member variables.
- Added many comments and author tags.
- Removed some Petit Four hacks and vestigial code.

*** The app must be built with the Donut SDK from now on.

git-svn-id: https://zxing.googlecode.com/svn/trunk@1049 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
dswitkin 2009-09-10 22:18:58 +00:00
parent e83cb0cef1
commit 833ca52c27
37 changed files with 1249 additions and 849 deletions

View file

@ -20,9 +20,16 @@ version to be published. The next versionCode will be 7, regardless of whether t
versionName is 2.31, 2.4, or 3.0. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.zxing.client.android"
android:versionName="2.93"
android:versionCode="30">
android:versionName="3.0 alpha1"
android:versionCode="32">
<!-- Allows this app to run on Cupcake devices. -->
<uses-sdk android:minSdkVersion="3"/>
<!-- Donut-specific flags which allow us to run on large and high dpi screens. -->
<supports-screens
android:largeScreens="true"
android:normalScreens="true"
android:smallScreens="true"
android:anyDensity="true"/>
<application android:icon="@drawable/launcher_icon"
android:label="@string/app_name">
<activity android:name=".CaptureActivity"

View file

@ -8,4 +8,4 @@
# project structure.
# Project target.
target=Google Inc.:Google APIs:3
target=Google Inc.:Google APIs:4

View file

@ -0,0 +1,61 @@
/*
* 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 com.google.zxing.client.android;
import com.google.zxing.LuminanceSource;
import android.graphics.Bitmap;
/**
* An extension of LuminanceSource which adds some Android-specific methods.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public abstract class BaseLuminanceSource extends LuminanceSource {
public BaseLuminanceSource(int width, int height) {
super(width, height);
}
/**
* Requests the width of the underlying platform's bitmap.
*
* @return The width in pixels.
*/
public abstract int getDataWidth();
/**
* Requests the height of the underlying platform's bitmap.
*
* @return The height in pixels.
*/
public abstract int getDataHeight();
/**
* Creates a greyscale Android Bitmap from the YUV data based on the crop rectangle.
*
* @return An 8888 bitmap.
*/
public abstract Bitmap renderCroppedGreyscaleBitmap();
/**
* Creates a color Android Bitmap from the YUV data, ignoring the crop rectangle.
*
* @param halfSize If true, downsample to 50% in each dimension, otherwise not.
* @return An 8888 bitmap.
*/
public abstract Bitmap renderFullColorBitmap(boolean halfSize);
}

View file

@ -29,9 +29,10 @@ import android.widget.SimpleCursorAdapter;
/**
* This class is only needed because I can't successfully send an ACTION_PICK intent to
* com.android.browser.BrowserBookmarksPage. It can go away if that starts working in the future.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class BookmarkPickerActivity extends ListActivity {
private static final String[] BOOKMARK_PROJECTION = {
Browser.BookmarkColumns.TITLE,
Browser.BookmarkColumns.URL
@ -48,32 +49,31 @@ public final class BookmarkPickerActivity extends ListActivity {
// Without this selection, we'd get all the history entries too
private static final String BOOKMARK_SELECTION = "bookmark = 1";
private Cursor mCursor;
private Cursor cursor;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
mCursor = getContentResolver().query(Browser.BOOKMARKS_URI, BOOKMARK_PROJECTION,
cursor = getContentResolver().query(Browser.BOOKMARKS_URI, BOOKMARK_PROJECTION,
BOOKMARK_SELECTION, null, null);
startManagingCursor(mCursor);
startManagingCursor(cursor);
ListAdapter adapter = new SimpleCursorAdapter(this, R.layout.bookmark_picker_list_item,
mCursor, BOOKMARK_PROJECTION, TWO_LINE_VIEW_IDS);
cursor, BOOKMARK_PROJECTION, TWO_LINE_VIEW_IDS);
setListAdapter(adapter);
}
@Override
protected void onListItemClick(ListView l, View view, int position, long id) {
if (mCursor.moveToPosition(position)) {
if (cursor.moveToPosition(position)) {
Intent intent = new Intent();
intent.putExtra(Browser.BookmarkColumns.TITLE, mCursor.getString(TITLE_COLUMN));
intent.putExtra(Browser.BookmarkColumns.URL, mCursor.getString(URL_COLUMN));
intent.putExtra(Browser.BookmarkColumns.TITLE, cursor.getString(TITLE_COLUMN));
intent.putExtra(Browser.BookmarkColumns.URL, cursor.getString(URL_COLUMN));
setResult(RESULT_OK, intent);
} else {
setResult(RESULT_CANCELED);
}
finish();
}
}

View file

@ -19,6 +19,7 @@ package com.google.zxing.client.android;
import com.google.zxing.ResultPoint;
import android.content.Context;
import android.graphics.PixelFormat;
import android.graphics.Point;
import android.graphics.Rect;
import android.hardware.Camera;
@ -35,47 +36,82 @@ import java.io.IOException;
* 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)
*/
final class CameraManager {
private static final String TAG = "CameraManager";
private static final int MIN_FRAME_WIDTH = 240;
private static final int MIN_FRAME_HEIGHT = 240;
private static final int MAX_FRAME_WIDTH = 480;
private static final int MAX_FRAME_HEIGHT = 360;
private static CameraManager mCameraManager;
private Camera mCamera;
private final Context mContext;
private Point mScreenResolution;
private Rect mFramingRect;
private Handler mPreviewHandler;
private int mPreviewMessage;
private Handler mAutoFocusHandler;
private int mAutoFocusMessage;
private boolean mInitialized;
private boolean mPreviewing;
private static CameraManager cameraManager;
private Camera camera;
private final Context context;
private Point screenResolution;
private Point cameraResolution;
private Rect framingRect;
private Handler previewHandler;
private int previewMessage;
private Handler autoFocusHandler;
private int autoFocusMessage;
private boolean initialized;
private boolean previewing;
private int previewFormat;
private String previewFormatString;
/**
* 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 Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
camera.setPreviewCallback(null);
if (previewHandler != null) {
Message message = previewHandler.obtainMessage(previewMessage, cameraResolution.x,
cameraResolution.y, data);
message.sendToTarget();
previewHandler = null;
}
}
};
private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
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 1.5 seconds.
autoFocusHandler.sendMessageDelayed(message, 1500L);
autoFocusHandler = null;
}
}
};
public static void init(Context context) {
if (mCameraManager == null) {
mCameraManager = new CameraManager(context);
if (cameraManager == null) {
cameraManager = new CameraManager(context);
}
}
public static CameraManager get() {
return mCameraManager;
return cameraManager;
}
private CameraManager(Context context) {
mContext = context;
mCamera = null;
mInitialized = false;
mPreviewing = false;
this.context = context;
camera = null;
initialized = false;
previewing = false;
}
public void openDriver(SurfaceHolder holder) throws IOException {
if (mCamera == null) {
mCamera = Camera.open();
mCamera.setPreviewDisplay(holder);
if (camera == null) {
camera = Camera.open();
camera.setPreviewDisplay(holder);
if (!mInitialized) {
mInitialized = true;
if (!initialized) {
initialized = true;
getScreenResolution();
}
@ -84,26 +120,26 @@ final class CameraManager {
}
public void closeDriver() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
if (camera != null) {
camera.release();
camera = null;
}
}
public void startPreview() {
if (mCamera != null && !mPreviewing) {
mCamera.startPreview();
mPreviewing = true;
if (camera != null && !previewing) {
camera.startPreview();
previewing = true;
}
}
public void stopPreview() {
if (mCamera != null && mPreviewing) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mPreviewHandler = null;
mAutoFocusHandler = null;
mPreviewing = false;
if (camera != null && previewing) {
camera.setPreviewCallback(null);
camera.stopPreview();
previewHandler = null;
autoFocusHandler = null;
previewing = false;
}
}
@ -116,39 +152,48 @@ final class CameraManager {
* @param message The what field of the message to be sent.
*/
public void requestPreviewFrame(Handler handler, int message) {
if (mCamera != null && mPreviewing) {
mPreviewHandler = handler;
mPreviewMessage = message;
mCamera.setPreviewCallback(previewCallback);
if (camera != null && previewing) {
previewHandler = handler;
previewMessage = message;
camera.setPreviewCallback(previewCallback);
}
}
public void requestAutoFocus(Handler handler, int message) {
if (mCamera != null && mPreviewing) {
mAutoFocusHandler = handler;
mAutoFocusMessage = message;
mCamera.autoFocus(autoFocusCallback);
if (camera != null && previewing) {
autoFocusHandler = handler;
autoFocusMessage = message;
camera.autoFocus(autoFocusCallback);
}
}
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
* barcode. The actual captured image should be a bit larger than indicated because they might
* frame the shot too tightly. 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.
* 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 (mFramingRect == null) {
int size = (mScreenResolution.x < mScreenResolution.y ? mScreenResolution.x :
mScreenResolution.y) * 3 / 4;
int leftOffset = (mScreenResolution.x - size) / 2;
int topOffset = (mScreenResolution.y - size) / 2;
mFramingRect = new Rect(leftOffset, topOffset, leftOffset + size, topOffset + size);
Log.v(TAG, "Calculated framing rect: " + mFramingRect);
if (framingRect == null) {
int width = cameraResolution.x * 3 / 4;
if (width < MIN_FRAME_WIDTH) {
width = MIN_FRAME_WIDTH;
} else if (width > MAX_FRAME_WIDTH) {
width = MAX_FRAME_WIDTH;
}
int height = cameraResolution.y * 3 / 4;
if (height < MIN_FRAME_HEIGHT) {
height = MIN_FRAME_HEIGHT;
} else if (height > MAX_FRAME_HEIGHT) {
height = MAX_FRAME_HEIGHT;
}
int leftOffset = (cameraResolution.x - width) / 2;
int topOffset = (cameraResolution.y - height) / 2;
framingRect = new Rect(leftOffset, topOffset, leftOffset + width, topOffset + height);
Log.v(TAG, "Calculated framing rect: " + framingRect);
}
return mFramingRect;
return framingRect;
}
/**
@ -171,31 +216,31 @@ final class CameraManager {
}
/**
* 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.
* 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 BaseLuminanceSource subclass.
*/
private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
camera.setPreviewCallback(null);
if (mPreviewHandler != null) {
Message message = mPreviewHandler.obtainMessage(mPreviewMessage, mScreenResolution.x,
mScreenResolution.y, data);
message.sendToTarget();
mPreviewHandler = null;
}
public BaseLuminanceSource buildLuminanceSource(byte[] data, int width, int height) {
Rect rect = getFramingRect();
switch (previewFormat) {
case PixelFormat.YCbCr_420_SP:
case PixelFormat.YCbCr_422_SP:
return new PlanarYUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height());
default:
// There's no PixelFormat constant for this buffer format yet.
if (previewFormatString.equals("yuv422i-yuyv")) {
return new InterleavedYUV422LuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height());
}
break;
}
};
private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
if (mAutoFocusHandler != null) {
Message message = mAutoFocusHandler.obtainMessage(mAutoFocusMessage, success);
// Simulate continuous autofocus by sending a focus request every 1.5 seconds.
mAutoFocusHandler.sendMessageDelayed(message, 1500L);
mAutoFocusHandler = null;
}
}
};
return null;
}
/**
* Sets the camera up to take preview images which are used for both preview and decoding. We're
@ -203,13 +248,21 @@ final class CameraManager {
* specify it explicitly with setPreviewFormat().
*/
private void setCameraParameters() {
Camera.Parameters parameters = mCamera.getParameters();
Camera.Parameters parameters = camera.getParameters();
Camera.Size size = parameters.getPreviewSize();
Log.v(TAG, "Default preview size: " + size.width + ", " + size.height);
Log.v(TAG, "Default preview format: " + parameters.getPreviewFormat());
Log.v(TAG, "Setting preview size: " + mScreenResolution.x + ", " + mScreenResolution.y);
previewFormat = parameters.getPreviewFormat();
previewFormatString = parameters.get("preview-format");
Log.v(TAG, "Default preview format: " + previewFormat);
parameters.setPreviewSize(mScreenResolution.x, mScreenResolution.y);
// Ensure that the camera resolution is a multiple of 8, as the screen may not be.
// TODO: A better solution would be to request the supported preview resolutions
// and pick the best match, but this parameter is not standardized in Cupcake.
cameraResolution = new Point();
cameraResolution.x = (screenResolution.x >> 3) << 3;
cameraResolution.y = (screenResolution.y >> 3) << 3;
Log.v(TAG, "Setting preview size: " + cameraResolution.x + ", " + cameraResolution.y);
parameters.setPreviewSize(cameraResolution.x, cameraResolution.y);
// FIXME: This is a hack to turn the flash off on the Samsung Galaxy.
parameters.set("flash-value", 2);
@ -217,16 +270,16 @@ final class CameraManager {
// This is the standard setting to turn the flash off that all devices should honor.
parameters.set("flash-mode", "off");
mCamera.setParameters(parameters);
camera.setParameters(parameters);
}
private Point getScreenResolution() {
if (mScreenResolution == null) {
WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
if (screenResolution == null) {
WindowManager manager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
mScreenResolution = new Point(display.getWidth(), display.getHeight());
screenResolution = new Point(display.getWidth(), display.getHeight());
}
return mScreenResolution;
return screenResolution;
}
}

View file

@ -16,6 +16,12 @@
package com.google.zxing.client.android;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.result.ResultButtonListener;
import com.google.zxing.client.android.result.ResultHandler;
import com.google.zxing.client.android.result.ResultHandlerFactory;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
@ -54,20 +60,16 @@ import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.result.ResultButtonListener;
import com.google.zxing.client.android.result.ResultHandler;
import com.google.zxing.client.android.result.ResultHandlerFactory;
import java.io.IOException;
/**
* The barcode reader activity itself. This is loosely based on the CameraPreview
* example included in the Android SDK.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class CaptureActivity extends Activity implements SurfaceHolder.Callback {
private static final String TAG = "CaptureActivity";
private static final int SHARE_ID = Menu.FIRST;
@ -92,23 +94,31 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
NONE
}
public CaptureActivityHandler mHandler;
public CaptureActivityHandler handler;
private ViewfinderView mViewfinderView;
private View mStatusView;
private View mResultView;
private MediaPlayer mMediaPlayer;
private Result mLastResult;
private boolean mHasSurface;
private boolean mPlayBeep;
private boolean mVibrate;
private boolean mCopyToClipboard;
private Source mSource;
private String mSourceUrl;
private String mDecodeMode;
private String mVersionName;
private ViewfinderView viewfinderView;
private View statusView;
private View resultView;
private MediaPlayer mediaPlayer;
private Result lastResult;
private boolean hasSurface;
private boolean playBeep;
private boolean vibrate;
private boolean copyToClipboard;
private Source source;
private String sourceUrl;
private String decodeMode;
private String versionName;
private final OnCompletionListener beepListener = new BeepListener();
private final OnCompletionListener mBeepListener = new BeepListener();
private final DialogInterface.OnClickListener aboutListener =
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.zxing_url)));
startActivity(intent);
}
};
@Override
public void onCreate(Bundle icicle) {
@ -120,12 +130,12 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
setContentView(R.layout.capture);
CameraManager.init(getApplication());
mViewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
mResultView = findViewById(R.id.result_view);
mStatusView = findViewById(R.id.status_view);
mHandler = null;
mLastResult = null;
mHasSurface = false;
viewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
resultView = findViewById(R.id.result_view);
statusView = findViewById(R.id.status_view);
handler = null;
lastResult = null;
hasSurface = false;
showHelpOnFirstLaunch();
}
@ -136,7 +146,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
if (mHasSurface) {
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);
@ -150,52 +160,52 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
String action = intent == null ? null : intent.getAction();
String dataString = intent == null ? null : intent.getDataString();
if (intent != null && action != null) {
if (action.equals(Intents.Scan.ACTION) || action.equals(Intents.Scan.DEPRECATED_ACTION)) {
if (action.equals(Intents.Scan.ACTION)) {
// Scan the formats the intent requested, and return the result to the calling activity.
mSource = Source.NATIVE_APP_INTENT;
mDecodeMode = intent.getStringExtra(Intents.Scan.MODE);
source = Source.NATIVE_APP_INTENT;
decodeMode = intent.getStringExtra(Intents.Scan.MODE);
resetStatusView();
} else if (dataString != null && dataString.contains(PRODUCT_SEARCH_URL_PREFIX) &&
dataString.contains(PRODUCT_SEARCH_URL_SUFFIX)) {
// Scan only products and send the result to mobile Product Search.
mSource = Source.PRODUCT_SEARCH_LINK;
mSourceUrl = dataString;
mDecodeMode = Intents.Scan.PRODUCT_MODE;
source = Source.PRODUCT_SEARCH_LINK;
sourceUrl = dataString;
decodeMode = Intents.Scan.PRODUCT_MODE;
resetStatusView();
} else if (dataString != null && dataString.equals(ZXING_URL)) {
// Scan all formats and handle the results ourselves.
// TODO: In the future we could allow the hyperlink to include a URL to send the results to.
mSource = Source.ZXING_LINK;
mSourceUrl = dataString;
mDecodeMode = null;
source = Source.ZXING_LINK;
sourceUrl = dataString;
decodeMode = null;
resetStatusView();
} else {
// Scan all formats and handle the results ourselves (launched from Home).
mSource = Source.NONE;
mDecodeMode = null;
source = Source.NONE;
decodeMode = null;
resetStatusView();
}
} else {
mSource = Source.NONE;
mDecodeMode = null;
if (mLastResult == null) {
source = Source.NONE;
decodeMode = null;
if (lastResult == null) {
resetStatusView();
}
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mPlayBeep = prefs.getBoolean(PreferencesActivity.KEY_PLAY_BEEP, true);
mVibrate = prefs.getBoolean(PreferencesActivity.KEY_VIBRATE, false);
mCopyToClipboard = prefs.getBoolean(PreferencesActivity.KEY_COPY_TO_CLIPBOARD, true);
playBeep = prefs.getBoolean(PreferencesActivity.KEY_PLAY_BEEP, true);
vibrate = prefs.getBoolean(PreferencesActivity.KEY_VIBRATE, false);
copyToClipboard = prefs.getBoolean(PreferencesActivity.KEY_COPY_TO_CLIPBOARD, true);
initBeepSound();
}
@Override
protected void onPause() {
super.onPause();
if (mHandler != null) {
mHandler.quitSynchronously();
mHandler = null;
if (handler != null) {
handler.quitSynchronously();
handler = null;
}
CameraManager.get().closeDriver();
}
@ -203,13 +213,13 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mSource == Source.NATIVE_APP_INTENT) {
if (source == Source.NATIVE_APP_INTENT) {
setResult(RESULT_CANCELED);
finish();
return true;
} else if ((mSource == Source.NONE || mSource == Source.ZXING_LINK) && mLastResult != null) {
} else if ((source == Source.NONE || source == Source.ZXING_LINK) && lastResult != null) {
resetStatusView();
mHandler.sendEmptyMessage(R.id.restart_preview);
handler.sendEmptyMessage(R.id.restart_preview);
return true;
}
} else if (keyCode == KeyEvent.KEYCODE_FOCUS || keyCode == KeyEvent.KEYCODE_CAMERA) {
@ -236,7 +246,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
super.onPrepareOptionsMenu(menu);
menu.findItem(SHARE_ID).setVisible(mLastResult == null);
menu.findItem(SHARE_ID).setVisible(lastResult == null);
return true;
}
@ -263,10 +273,10 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
}
case ABOUT_ID:
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(getString(R.string.title_about) + mVersionName);
builder.setTitle(getString(R.string.title_about) + versionName);
builder.setMessage(getString(R.string.msg_about) + "\n\n" + getString(R.string.zxing_url));
builder.setIcon(R.drawable.zxing_icon);
builder.setPositiveButton(R.string.button_open_browser, mAboutListener);
builder.setPositiveButton(R.string.button_open_browser, aboutListener);
builder.setNegativeButton(R.string.button_cancel, null);
builder.show();
break;
@ -280,22 +290,15 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
super.onConfigurationChanged(config);
}
private final DialogInterface.OnClickListener mAboutListener = new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialogInterface, int i) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.zxing_url)));
startActivity(intent);
}
};
public void surfaceCreated(SurfaceHolder holder) {
if (!mHasSurface) {
mHasSurface = true;
if (!hasSurface) {
hasSurface = true;
initCamera(holder);
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
mHasSurface = false;
hasSurface = false;
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
@ -309,11 +312,11 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
* @param barcode A greyscale bitmap of the camera data which was decoded.
*/
public void handleDecode(Result rawResult, Bitmap barcode) {
mLastResult = rawResult;
lastResult = rawResult;
playBeepSoundAndVibrate();
drawResultPoints(barcode, rawResult);
switch (mSource) {
switch (source) {
case NATIVE_APP_INTENT:
case PRODUCT_SEARCH_LINK:
handleDecodeExternally(rawResult, barcode);
@ -358,9 +361,9 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
// Put up our own UI for how to handle the decoded contents.
private void handleDecodeInternally(Result rawResult, Bitmap barcode) {
mStatusView.setVisibility(View.GONE);
mViewfinderView.setVisibility(View.GONE);
mResultView.setVisibility(View.VISIBLE);
statusView.setVisibility(View.GONE);
viewfinderView.setVisibility(View.GONE);
resultView.setVisibility(View.VISIBLE);
ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view);
barcodeImageView.setMaxWidth(MAX_RESULT_IMAGE_SIZE);
@ -398,7 +401,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
}
}
if (mCopyToClipboard) {
if (copyToClipboard) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clipboard.setText(displayContents);
}
@ -406,7 +409,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
// Briefly show the contents of the barcode, then handle the result outside Barcode Scanner.
private void handleDecodeExternally(Result rawResult, Bitmap barcode) {
mViewfinderView.drawResultBitmap(barcode);
viewfinderView.drawResultBitmap(barcode);
// Since this message will only be shown for a second, just tell the user what kind of
// barcode was found (e.g. contact info) rather than the full contents, which they won't
@ -417,30 +420,30 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
textView.setTextSize(18.0f);
textView.setText(getString(resultHandler.getDisplayTitle()));
mStatusView.setBackgroundColor(getResources().getColor(R.color.transparent));
statusView.setBackgroundColor(getResources().getColor(R.color.transparent));
if (mCopyToClipboard) {
if (copyToClipboard) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clipboard.setText(resultHandler.getDisplayContents());
}
if (mSource == Source.NATIVE_APP_INTENT) {
if (source == Source.NATIVE_APP_INTENT) {
// Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
// the deprecated intent is retired.
Intent intent = new Intent(getIntent().getAction());
intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
Message message = Message.obtain(mHandler, R.id.return_scan_result);
Message message = Message.obtain(handler, R.id.return_scan_result);
message.obj = intent;
mHandler.sendMessageDelayed(message, INTENT_RESULT_DURATION);
} else if (mSource == Source.PRODUCT_SEARCH_LINK) {
handler.sendMessageDelayed(message, INTENT_RESULT_DURATION);
} else if (source == Source.PRODUCT_SEARCH_LINK) {
// Reformulate the URL which triggered us into a query, so that the request goes to the same
// TLD as the scan URL.
Message message = Message.obtain(mHandler, R.id.launch_product_query);
int end = mSourceUrl.lastIndexOf("/scan");
message.obj = mSourceUrl.substring(0, end) + "?q=" +
Message message = Message.obtain(handler, R.id.launch_product_query);
int end = sourceUrl.lastIndexOf("/scan");
message.obj = sourceUrl.substring(0, end) + "?q=" +
resultHandler.getDisplayContents().toString() + "&source=zxing";
mHandler.sendMessageDelayed(message, INTENT_RESULT_DURATION);
handler.sendMessageDelayed(message, INTENT_RESULT_DURATION);
}
}
@ -455,7 +458,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
int currentVersion = info.versionCode;
// Since we're paying to talk to the PackageManager anyway, it makes sense to cache the app
// version name here for display in the about box later.
this.mVersionName = info.versionName;
this.versionName = info.versionName;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
int lastVersion = prefs.getInt(PreferencesActivity.KEY_HELP_VERSION_SHOWN, 0);
if (currentVersion > lastVersion) {
@ -474,29 +477,29 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
* latency possible.
*/
private void initBeepSound() {
if (mPlayBeep && mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_SYSTEM);
mMediaPlayer.setOnCompletionListener(mBeepListener);
if (playBeep && mediaPlayer == null) {
mediaPlayer = new MediaPlayer();
mediaPlayer.setAudioStreamType(AudioManager.STREAM_SYSTEM);
mediaPlayer.setOnCompletionListener(beepListener);
AssetFileDescriptor file = getResources().openRawResourceFd(R.raw.beep);
try {
mMediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(),
mediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(),
file.getLength());
file.close();
mMediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
mMediaPlayer.prepare();
mediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
mediaPlayer.prepare();
} catch (IOException e) {
mMediaPlayer = null;
mediaPlayer = null;
}
}
}
private void playBeepSoundAndVibrate() {
if (mPlayBeep && mMediaPlayer != null) {
mMediaPlayer.start();
if (playBeep && mediaPlayer != null) {
mediaPlayer.start();
}
if (mVibrate) {
if (vibrate) {
Vibrator vibrator = (Vibrator) getSystemService(VIBRATOR_SERVICE);
vibrator.vibrate(VIBRATE_DURATION);
}
@ -509,27 +512,27 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
Log.w(TAG, ioe);
return;
}
if (mHandler == null) {
boolean beginScanning = mLastResult == null;
mHandler = new CaptureActivityHandler(this, mDecodeMode, beginScanning);
if (handler == null) {
boolean beginScanning = lastResult == null;
handler = new CaptureActivityHandler(this, decodeMode, beginScanning);
}
}
private void resetStatusView() {
mResultView.setVisibility(View.GONE);
mStatusView.setVisibility(View.VISIBLE);
mStatusView.setBackgroundColor(getResources().getColor(R.color.status_view));
mViewfinderView.setVisibility(View.VISIBLE);
resultView.setVisibility(View.GONE);
statusView.setVisibility(View.VISIBLE);
statusView.setBackgroundColor(getResources().getColor(R.color.status_view));
viewfinderView.setVisibility(View.VISIBLE);
TextView textView = (TextView) findViewById(R.id.status_text_view);
textView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
textView.setTextSize(14.0f);
textView.setText(R.string.msg_default_status);
mLastResult = null;
lastResult = null;
}
public void drawViewfinder() {
mViewfinderView.drawViewfinder();
viewfinderView.drawViewfinder();
}
/**
@ -540,5 +543,4 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
mediaPlayer.seekTo(0);
}
}
}

View file

@ -16,6 +16,8 @@
package com.google.zxing.client.android;
import com.google.zxing.Result;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
@ -23,16 +25,16 @@ import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import com.google.zxing.Result;
/**
* This class handles all the messaging which comprises the state machine for capture.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class CaptureActivityHandler extends Handler {
private final CaptureActivity mActivity;
private final DecodeThread mDecodeThread;
private State mState;
private final CaptureActivity activity;
private final DecodeThread decodeThread;
private State state;
private enum State {
PREVIEW,
@ -42,10 +44,10 @@ public final class CaptureActivityHandler extends Handler {
CaptureActivityHandler(CaptureActivity activity, String decodeMode,
boolean beginScanning) {
mActivity = activity;
mDecodeThread = new DecodeThread(activity, decodeMode);
mDecodeThread.start();
mState = State.SUCCESS;
this.activity = activity;
decodeThread = new DecodeThread(activity, decodeMode);
decodeThread.start();
state = State.SUCCESS;
// Start ourselves capturing previews and decoding.
CameraManager.get().startPreview();
@ -60,7 +62,7 @@ public final class CaptureActivityHandler extends Handler {
case R.id.auto_focus:
// When one auto focus pass finishes, start another. This is the closest thing to
// continuous AF. It does seem to hunt a bit, but I'm not sure what else to do.
if (mState == State.PREVIEW) {
if (state == State.PREVIEW) {
CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
}
break;
@ -68,34 +70,34 @@ public final class CaptureActivityHandler extends Handler {
restartPreviewAndDecode();
break;
case R.id.decode_succeeded:
mState = State.SUCCESS;
state = State.SUCCESS;
Bundle bundle = message.getData();
Bitmap barcode = bundle.getParcelable(DecodeThread.BARCODE_BITMAP);
mActivity.handleDecode((Result) message.obj, barcode);
activity.handleDecode((Result) message.obj, barcode);
break;
case R.id.decode_failed:
// We're decoding as fast as possible, so when one decode fails, start another.
mState = State.PREVIEW;
CameraManager.get().requestPreviewFrame(mDecodeThread.mHandler, R.id.decode);
state = State.PREVIEW;
CameraManager.get().requestPreviewFrame(decodeThread.handler, R.id.decode);
break;
case R.id.return_scan_result:
mActivity.setResult(Activity.RESULT_OK, (Intent) message.obj);
mActivity.finish();
activity.setResult(Activity.RESULT_OK, (Intent) message.obj);
activity.finish();
break;
case R.id.launch_product_query:
String url = (String) message.obj;
mActivity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
break;
}
}
public void quitSynchronously() {
mState = State.DONE;
state = State.DONE;
CameraManager.get().stopPreview();
Message quit = Message.obtain(mDecodeThread.mHandler, R.id.quit);
Message quit = Message.obtain(decodeThread.handler, R.id.quit);
quit.sendToTarget();
try {
mDecodeThread.join();
decodeThread.join();
} catch (InterruptedException e) {
}
@ -105,12 +107,11 @@ public final class CaptureActivityHandler extends Handler {
}
private void restartPreviewAndDecode() {
if (mState == State.SUCCESS) {
mState = State.PREVIEW;
CameraManager.get().requestPreviewFrame(mDecodeThread.mHandler, R.id.decode);
if (state == State.SUCCESS) {
state = State.PREVIEW;
CameraManager.get().requestPreviewFrame(decodeThread.handler, R.id.decode);
CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
mActivity.drawViewfinder();
activity.drawViewfinder();
}
}
}

View file

@ -18,8 +18,13 @@ package com.google.zxing.client.android;
import android.provider.Contacts;
/**
* The set of constants to use when sending Barcode Scanner an Intent which requests a barcode
* to be encoded.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class Contents {
private Contents() {
}
@ -89,24 +94,19 @@ public final class Contents {
}
}
// These are new constants in Contacts.Intents.Insert for Android 1.1.
// TODO: Remove these constants once we can build against the 1.1 SDK.
private static final String SECONDARY_PHONE = "secondary_phone";
private static final String TERTIARY_PHONE = "tertiary_phone";
private static final String SECONDARY_EMAIL = "secondary_email";
private static final String TERTIARY_EMAIL = "tertiary_email";
/**
* When using Type.CONTACT, these arrays provide the keys for adding or retrieving multiple
* phone numbers and addresses.
*/
public static final String[] PHONE_KEYS = {
Contacts.Intents.Insert.PHONE, SECONDARY_PHONE, TERTIARY_PHONE
Contacts.Intents.Insert.PHONE,
Contacts.Intents.Insert.SECONDARY_PHONE,
Contacts.Intents.Insert.TERTIARY_PHONE
};
public static final String[] EMAIL_KEYS = {
Contacts.Intents.Insert.EMAIL, SECONDARY_EMAIL, TERTIARY_EMAIL
Contacts.Intents.Insert.EMAIL,
Contacts.Intents.Insert.SECONDARY_EMAIL,
Contacts.Intents.Insert.TERTIARY_EMAIL
};
}

View file

@ -25,7 +25,6 @@ import com.google.zxing.Result;
import com.google.zxing.common.GlobalHistogramBinarizer;
import android.content.SharedPreferences;
import android.graphics.Rect;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
@ -38,19 +37,20 @@ import java.util.Vector;
/**
* 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 static final String TAG = "DecodeThread";
public Handler mHandler;
private final CaptureActivity mActivity;
private final MultiFormatReader mMultiFormatReader;
public Handler handler;
private final CaptureActivity activity;
private final MultiFormatReader multiFormatReader;
DecodeThread(CaptureActivity activity, String mode) {
mActivity = activity;
mMultiFormatReader = new MultiFormatReader();
this.activity = activity;
multiFormatReader = new MultiFormatReader();
// The prefs can't change while the thread is running, so pick them up once here.
if (mode == null || mode.length() == 0) {
@ -80,7 +80,7 @@ final class DecodeThread extends Thread {
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
handler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
@ -104,7 +104,7 @@ final class DecodeThread extends Thread {
vector.addElement(BarcodeFormat.EAN_13);
vector.addElement(BarcodeFormat.EAN_8);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
mMultiFormatReader.setHints(hints);
multiFormatReader.setHints(hints);
}
/**
@ -121,7 +121,7 @@ final class DecodeThread extends Thread {
vector.addElement(BarcodeFormat.CODE_128);
vector.addElement(BarcodeFormat.ITF);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
mMultiFormatReader.setHints(hints);
multiFormatReader.setHints(hints);
}
private void setDecodeQRMode() {
@ -129,7 +129,7 @@ final class DecodeThread extends Thread {
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>(1);
vector.addElement(BarcodeFormat.QR_CODE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
mMultiFormatReader.setHints(hints);
multiFormatReader.setHints(hints);
}
/**
@ -148,7 +148,7 @@ final class DecodeThread extends Thread {
vector.addElement(BarcodeFormat.ITF);
vector.addElement(BarcodeFormat.QR_CODE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
mMultiFormatReader.setHints(hints);
multiFormatReader.setHints(hints);
}
/**
@ -163,12 +163,10 @@ final class DecodeThread extends Thread {
long start = System.currentTimeMillis();
boolean success;
Result rawResult = null;
Rect rect = CameraManager.get().getFramingRect();
YUVLuminanceSource source = new YUVLuminanceSource(data, width, height, rect.left, rect.top,
rect.width(), rect.height());
BaseLuminanceSource source = CameraManager.get().buildLuminanceSource(data, width, height);
BinaryBitmap bitmap = new BinaryBitmap(new GlobalHistogramBinarizer(source));
try {
rawResult = mMultiFormatReader.decodeWithState(bitmap);
rawResult = multiFormatReader.decodeWithState(bitmap);
success = true;
} catch (ReaderException e) {
success = false;
@ -177,15 +175,14 @@ final class DecodeThread extends Thread {
if (success) {
Log.v(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString());
Message message = Message.obtain(mActivity.mHandler, R.id.decode_succeeded, rawResult);
Message message = Message.obtain(activity.handler, R.id.decode_succeeded, rawResult);
Bundle bundle = new Bundle();
bundle.putParcelable(BARCODE_BITMAP, source.renderToBitmap());
bundle.putParcelable(BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap());
message.setData(bundle);
message.sendToTarget();
} else {
Message message = Message.obtain(mActivity.mHandler, R.id.decode_failed);
Message message = Message.obtain(activity.handler, R.id.decode_failed);
message.sendToTarget();
}
}
}

View file

@ -35,20 +35,82 @@ import android.widget.TextView;
/**
* This class encodes data from an Intent into a QR code, and then displays it full screen so that
* another person can scan it with their device.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class EncodeActivity extends Activity {
private QRCodeEncoder qrCodeEncoder;
private ProgressDialog progressDialog;
private boolean firstLayout;
private QRCodeEncoder mQRCodeEncoder;
private ProgressDialog mProgressDialog;
private boolean mFirstLayout;
/**
* This needs to be delayed until after the first layout so that the view dimensions will be
* available.
*/
public final OnGlobalLayoutListener layoutListener = new OnGlobalLayoutListener() {
public void onGlobalLayout() {
if (firstLayout) {
View layout = findViewById(R.id.encode_view);
int width = layout.getWidth();
int height = layout.getHeight();
int smallerDimension = width < height ? width : height;
smallerDimension = smallerDimension * 7 / 8;
Intent intent = getIntent();
try {
qrCodeEncoder = new QRCodeEncoder(EncodeActivity.this, intent);
setTitle(getString(R.string.app_name) + " - " + qrCodeEncoder.getTitle());
qrCodeEncoder.requestBarcode(handler, smallerDimension);
progressDialog = ProgressDialog.show(EncodeActivity.this, null,
getString(R.string.msg_encode_in_progress), true, true, cancelListener);
} catch (IllegalArgumentException e) {
showErrorMessage(R.string.msg_encode_contents_failed);
}
firstLayout = false;
}
}
};
public final Handler handler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case R.id.encode_succeeded:
progressDialog.dismiss();
progressDialog = null;
Bitmap image = (Bitmap) message.obj;
ImageView view = (ImageView) findViewById(R.id.image_view);
view.setImageBitmap(image);
TextView contents = (TextView) findViewById(R.id.contents_text_view);
contents.setText(qrCodeEncoder.getDisplayContents());
qrCodeEncoder = null;
break;
case R.id.encode_failed:
showErrorMessage(R.string.msg_encode_barcode_failed);
qrCodeEncoder = null;
break;
}
}
};
private final OnClickListener clickListener = new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
};
private final OnCancelListener cancelListener = new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
finish();
}
};
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Intent intent = getIntent();
if (intent != null && (intent.getAction().equals(Intents.Encode.ACTION) ||
intent.getAction().equals(Intents.Encode.DEPRECATED_ACTION))) {
if (intent != null && (intent.getAction().equals(Intents.Encode.ACTION))) {
setContentView(R.layout.encode);
} else {
finish();
@ -60,81 +122,18 @@ public final class EncodeActivity extends Activity {
super.onResume();
View layout = findViewById(R.id.encode_view);
layout.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener);
mFirstLayout = true;
layout.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener);
firstLayout = true;
}
/**
* This needs to be delayed until after the first layout so that the view dimensions will be
* available.
*/
public final OnGlobalLayoutListener mLayoutListener = new OnGlobalLayoutListener() {
public void onGlobalLayout() {
if (mFirstLayout) {
View layout = findViewById(R.id.encode_view);
int width = layout.getWidth();
int height = layout.getHeight();
int smallerDimension = width < height ? width : height;
smallerDimension = smallerDimension * 7 / 8;
Intent intent = getIntent();
try {
mQRCodeEncoder = new QRCodeEncoder(EncodeActivity.this, intent);
setTitle(getString(R.string.app_name) + " - " + mQRCodeEncoder.getTitle());
mQRCodeEncoder.requestBarcode(mHandler, smallerDimension);
mProgressDialog = ProgressDialog.show(EncodeActivity.this, null,
getString(R.string.msg_encode_in_progress), true, true, mCancelListener);
} catch (IllegalArgumentException e) {
showErrorMessage(R.string.msg_encode_contents_failed);
}
mFirstLayout = false;
}
}
};
public final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case R.id.encode_succeeded:
mProgressDialog.dismiss();
mProgressDialog = null;
Bitmap image = (Bitmap) message.obj;
ImageView view = (ImageView) findViewById(R.id.image_view);
view.setImageBitmap(image);
TextView contents = (TextView) findViewById(R.id.contents_text_view);
contents.setText(mQRCodeEncoder.getDisplayContents());
mQRCodeEncoder = null;
break;
case R.id.encode_failed:
showErrorMessage(R.string.msg_encode_barcode_failed);
mQRCodeEncoder = null;
break;
}
}
};
private void showErrorMessage(int message) {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
mProgressDialog = null;
if (progressDialog != null) {
progressDialog.dismiss();
progressDialog = null;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(message);
builder.setPositiveButton(R.string.button_ok, mClickListener);
builder.setPositiveButton(R.string.button_ok, clickListener);
builder.show();
}
private final OnClickListener mClickListener = new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
};
private final OnCancelListener mCancelListener = new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
finish();
}
};
}

View file

@ -25,33 +25,46 @@ import android.webkit.WebViewClient;
import android.widget.Button;
/**
* An HTML-based help screen with Back and Done buttons at the bottom.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class HelpActivity extends Activity {
private static final String DEFAULT_URL = "file:///android_asset/html/index.html";
private WebView mWebView;
private Button mBackButton;
private WebView webView;
private Button backButton;
private final Button.OnClickListener backListener = new Button.OnClickListener() {
public void onClick(View view) {
webView.goBack();
}
};
private final Button.OnClickListener doneListener = new Button.OnClickListener() {
public void onClick(View view) {
finish();
}
};
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.help);
mWebView = (WebView)findViewById(R.id.help_contents);
mWebView.setWebViewClient(new HelpClient());
webView = (WebView)findViewById(R.id.help_contents);
webView.setWebViewClient(new HelpClient());
if (icicle != null) {
mWebView.restoreState(icicle);
webView.restoreState(icicle);
} else {
mWebView.loadUrl(DEFAULT_URL);
webView.loadUrl(DEFAULT_URL);
}
mBackButton = (Button)findViewById(R.id.back_button);
mBackButton.setOnClickListener(mBackListener);
backButton = (Button)findViewById(R.id.back_button);
backButton.setOnClickListener(backListener);
Button doneButton = (Button)findViewById(R.id.done_button);
doneButton.setOnClickListener(mDoneListener);
doneButton.setOnClickListener(doneListener);
}
@Override
@ -61,40 +74,26 @@ public final class HelpActivity extends Activity {
@Override
protected void onSaveInstanceState(Bundle state) {
mWebView.saveState(state);
webView.saveState(state);
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mWebView.canGoBack()) {
mWebView.goBack();
if (webView.canGoBack()) {
webView.goBack();
return true;
}
}
return super.onKeyDown(keyCode, event);
}
private final Button.OnClickListener mBackListener = new Button.OnClickListener() {
public void onClick(View view) {
mWebView.goBack();
}
};
private final Button.OnClickListener mDoneListener = new Button.OnClickListener() {
public void onClick(View view) {
finish();
}
};
private final class HelpClient extends WebViewClient {
@Override
public void onPageFinished(WebView view, String url) {
setTitle(view.getTitle());
mBackButton.setEnabled(view.canGoBack());
backButton.setEnabled(view.canGoBack());
}
}
}

View file

@ -16,8 +16,13 @@
package com.google.zxing.client.android;
/**
* 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() {
}
@ -28,9 +33,6 @@ public final class Intents {
*/
public static final String ACTION = "com.google.zxing.client.android.SCAN";
// For compatibility only - do not use in new code, this will go away!
public static final String DEPRECATED_ACTION = "com.android.barcodes.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
@ -79,9 +81,6 @@ public final class Intents {
*/
public static final String ACTION = "com.google.zxing.client.android.ENCODE";
// For compatibility only - do not use in new code, this will go away!
public static final String DEPRECATED_ACTION = "com.android.barcodes.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
@ -112,9 +111,6 @@ public final class Intents {
*/
public static final String ACTION = "com.google.zxing.client.android.SEARCH_BOOK_CONTENTS";
// For compatibility only - do not use in new code, this will go away!
public static final String DEPRECATED_ACTION = "com.android.barcodes.SEARCH_BOOK_CONTENTS";
/**
* The book to search, identified by ISBN number.
*/
@ -139,5 +135,4 @@ public final class Intents {
private Share() {
}
}
}

View file

@ -0,0 +1,138 @@
/*
* 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 com.google.zxing.client.android;
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 handles YUV 422 interleaved data, where each pixel consists of first a Y value, then
* a color value, with U and V alternating at each pixel.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class InterleavedYUV422LuminanceSource extends BaseLuminanceSource {
private final byte[] yuvData;
private final int dataWidth;
private final int dataHeight;
private final int left;
private final int top;
public InterleavedYUV422LuminanceSource(byte[] yuvData, int dataWidth, int dataHeight,
int left, int top, int width, int height) {
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;
}
@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 * 2 + (left * 2);
byte[] yuv = yuvData;
for (int x = 0; x < width; x++) {
row[x] = yuv[offset + (x << 1)];
}
return row;
}
@Override
public byte[] getMatrix() {
int width = getWidth();
int height = getHeight();
int area = width * height;
byte[] matrix = new byte[area];
int inputOffset = top * dataWidth * 2 + (left * 2);
byte[] yuv = yuvData;
for (int y = 0; y < height; y++) {
int outputOffset = y * width;
for (int x = 0; x < width; x++) {
matrix[outputOffset + x] = yuv[inputOffset + (x << 1)];
}
inputOffset += (dataWidth * 2);
}
return matrix;
}
@Override
public boolean isCropSupported() {
return true;
}
@Override
public LuminanceSource crop(int left, int top, int width, int height) {
return new InterleavedYUV422LuminanceSource(yuvData, dataWidth, dataHeight, left, top,
width, height);
}
@Override
public int getDataWidth() {
return dataWidth;
}
@Override
public int getDataHeight() {
return dataHeight;
}
@Override
public Bitmap renderCroppedGreyscaleBitmap() {
int width = getWidth();
int height = getHeight();
int[] pixels = new int[width * height];
byte[] yuv = yuvData;
int inputOffset = top * dataWidth * 2 + (left * 2);
for (int y = 0; y < height; y++) {
int outputOffset = y * width;
for (int x = 0; x < width; x++) {
int grey = yuv[inputOffset + (x << 1)] & 0xff;
pixels[outputOffset + x] = (0xff000000) | (grey * 0x00010101);
}
inputOffset += (dataWidth * 2);
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
@Override
public Bitmap renderFullColorBitmap(boolean halfSize) {
return null;
}
}

View file

@ -22,9 +22,10 @@ import java.util.HashMap;
/**
* Handles any locale-specific logic for the client.
*
* @author Sean Owen
*/
public final class LocaleManager {
private static final String DEFAULT_TLD = "com";
private static final Map<Locale,String> GOOGLE_COUNTRY_TLD;
static {
@ -93,5 +94,4 @@ public final class LocaleManager {
}
return tld;
}
}

View file

@ -0,0 +1,147 @@
/*
* 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 com.google.zxing.client.android;
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. Any subsequent color data will be ignored.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class PlanarYUVLuminanceSource extends BaseLuminanceSource {
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) {
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;
}
@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;
}
@Override
public LuminanceSource crop(int left, int top, int width, int height) {
return new PlanarYUVLuminanceSource(yuvData, dataWidth, dataHeight, left, top, width, height);
}
@Override
public int getDataWidth() {
return dataWidth;
}
@Override
public int getDataHeight() {
return dataHeight;
}
@Override
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;
}
// Can't be implemented here, as the color representations vary.
@Override
public Bitmap renderFullColorBitmap(boolean halfSize) {
return null;
}
}

View file

@ -23,6 +23,11 @@ import android.preference.CheckBoxPreference;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
/**
* The main settings activity.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class PreferencesActivity extends PreferenceActivity
implements OnSharedPreferenceChangeListener {
@ -36,8 +41,8 @@ public final class PreferencesActivity extends PreferenceActivity
static final String KEY_HELP_VERSION_SHOWN = "preferences_help_version_shown";
CheckBoxPreference mDecode1D;
CheckBoxPreference mDecodeQR;
CheckBoxPreference decode1D;
CheckBoxPreference decodeQR;
@Override
protected void onCreate(Bundle icicle) {
@ -46,19 +51,18 @@ public final class PreferencesActivity extends PreferenceActivity
PreferenceScreen preferences = getPreferenceScreen();
preferences.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
mDecode1D = (CheckBoxPreference) preferences.findPreference(KEY_DECODE_1D);
mDecodeQR = (CheckBoxPreference) preferences.findPreference(KEY_DECODE_QR);
decode1D = (CheckBoxPreference) preferences.findPreference(KEY_DECODE_1D);
decodeQR = (CheckBoxPreference) preferences.findPreference(KEY_DECODE_QR);
}
// Prevent the user from turning off both decode options
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KEY_DECODE_1D)) {
mDecodeQR.setEnabled(mDecode1D.isChecked());
mDecodeQR.setChecked(true);
decodeQR.setEnabled(decode1D.isChecked());
decodeQR.setChecked(true);
} else if (key.equals(KEY_DECODE_QR)) {
mDecode1D.setEnabled(mDecodeQR.isChecked());
mDecode1D.setChecked(true);
decode1D.setEnabled(decodeQR.isChecked());
decode1D.setChecked(true);
}
}
}

View file

@ -16,6 +16,11 @@
package com.google.zxing.client.android;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.ByteMatrix;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
@ -23,48 +28,49 @@ import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.Contacts;
import android.util.Log;
import android.telephony.PhoneNumberUtils;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.MultiFormatWriter;
import com.google.zxing.WriterException;
import com.google.zxing.common.ByteMatrix;
import android.util.Log;
/**
* This class does the work of decoding the user's request and extracting all the data
* to be encoded in a QR Code.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class QRCodeEncoder {
private final Activity mActivity;
private String mContents;
private String mDisplayContents;
private String mTitle;
private BarcodeFormat mFormat;
private final Activity activity;
private String contents;
private String displayContents;
private String title;
private BarcodeFormat format;
public QRCodeEncoder(Activity activity, Intent intent) {
mActivity = activity;
this.activity = activity;
if (!encodeContents(intent)) {
throw new IllegalArgumentException("No valid data to encode.");
}
}
public void requestBarcode(Handler handler, int pixelResolution) {
Thread encodeThread = new EncodeThread(mContents, handler, pixelResolution,
mFormat);
Thread encodeThread = new EncodeThread(contents, handler, pixelResolution,
format);
encodeThread.start();
}
public String getContents() {
return mContents;
return contents;
}
public String getDisplayContents() {
return mDisplayContents;
return displayContents;
}
public String getTitle() {
return mTitle;
return title;
}
public String getFormat() {
return mFormat.toString();
return format.toString();
}
// It would be nice if the string encoding lived in the core ZXing library,
@ -82,59 +88,59 @@ public final class QRCodeEncoder {
if (type == null || type.length() == 0) {
return false;
}
mFormat = BarcodeFormat.QR_CODE;
this.format = BarcodeFormat.QR_CODE;
encodeQRCodeContents(intent, type);
} else {
String data = intent.getStringExtra(Intents.Encode.DATA);
if (data != null && data.length() != 0) {
mContents = data;
mDisplayContents = data;
mTitle = mActivity.getString(R.string.contents_text);
contents = data;
displayContents = data;
title = activity.getString(R.string.contents_text);
if (format.equals(Contents.Format.CODE_128))
mFormat = BarcodeFormat.CODE_128;
this.format = BarcodeFormat.CODE_128;
else if (format.equals(Contents.Format.CODE_39))
mFormat = BarcodeFormat.CODE_39;
this.format = BarcodeFormat.CODE_39;
else if (format.equals(Contents.Format.EAN_8))
mFormat = BarcodeFormat.EAN_8;
this.format = BarcodeFormat.EAN_8;
else if (format.equals(Contents.Format.EAN_13))
mFormat = BarcodeFormat.EAN_13;
this.format = BarcodeFormat.EAN_13;
else if (format.equals(Contents.Format.UPC_A))
mFormat = BarcodeFormat.UPC_A;
this.format = BarcodeFormat.UPC_A;
else if (format.equals(Contents.Format.UPC_E))
mFormat = BarcodeFormat.UPC_E;
this.format = BarcodeFormat.UPC_E;
}
}
return mContents != null && mContents.length() > 0;
return contents != null && contents.length() > 0;
}
private void encodeQRCodeContents(Intent intent, String type) {
if (type.equals(Contents.Type.TEXT)) {
String data = intent.getStringExtra(Intents.Encode.DATA);
if (data != null && data.length() > 0) {
mContents = data;
mDisplayContents = data;
mTitle = mActivity.getString(R.string.contents_text);
contents = data;
displayContents = data;
title = activity.getString(R.string.contents_text);
}
} else if (type.equals(Contents.Type.EMAIL)) {
String data = intent.getStringExtra(Intents.Encode.DATA);
if (data != null && data.length() > 0) {
mContents = "mailto:" + data;
mDisplayContents = data;
mTitle = mActivity.getString(R.string.contents_email);
contents = "mailto:" + data;
displayContents = data;
title = activity.getString(R.string.contents_email);
}
} else if (type.equals(Contents.Type.PHONE)) {
String data = intent.getStringExtra(Intents.Encode.DATA);
if (data != null && data.length() > 0) {
mContents = "tel:" + data;
mDisplayContents = PhoneNumberUtils.formatNumber(data);
mTitle = mActivity.getString(R.string.contents_phone);
contents = "tel:" + data;
displayContents = PhoneNumberUtils.formatNumber(data);
title = activity.getString(R.string.contents_phone);
}
} else if (type.equals(Contents.Type.SMS)) {
String data = intent.getStringExtra(Intents.Encode.DATA);
if (data != null && data.length() > 0) {
mContents = "sms:" + data;
mDisplayContents = PhoneNumberUtils.formatNumber(data);
mTitle = mActivity.getString(R.string.contents_sms);
contents = "sms:" + data;
displayContents = PhoneNumberUtils.formatNumber(data);
title = activity.getString(R.string.contents_sms);
}
} else if (type.equals(Contents.Type.CONTACT)) {
Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
@ -169,12 +175,12 @@ public final class QRCodeEncoder {
// Make sure we've encoded at least one field.
if (newDisplayContents.length() > 0) {
newContents.append(';');
mContents = newContents.toString();
mDisplayContents = newDisplayContents.toString();
mTitle = mActivity.getString(R.string.contents_contact);
contents = newContents.toString();
displayContents = newDisplayContents.toString();
title = activity.getString(R.string.contents_contact);
} else {
mContents = null;
mDisplayContents = null;
contents = null;
displayContents = null;
}
}
} else if (type.equals(Contents.Type.LOCATION)) {
@ -184,36 +190,35 @@ public final class QRCodeEncoder {
float latitude = bundle.getFloat("LAT", Float.MAX_VALUE);
float longitude = bundle.getFloat("LONG", Float.MAX_VALUE);
if (latitude != Float.MAX_VALUE && longitude != Float.MAX_VALUE) {
mContents = "geo:" + latitude + ',' + longitude;
mDisplayContents = latitude + "," + longitude;
mTitle = mActivity.getString(R.string.contents_location);
contents = "geo:" + latitude + ',' + longitude;
displayContents = latitude + "," + longitude;
title = activity.getString(R.string.contents_location);
}
}
}
}
private static final class EncodeThread extends Thread {
private static final String TAG = "EncodeThread";
private final String mContents;
private final Handler mHandler;
private final int mPixelResolution;
private final BarcodeFormat mFormat;
private final String contents;
private final Handler handler;
private final int pixelResolution;
private final BarcodeFormat format;
EncodeThread(String contents, Handler handler, int pixelResolution,
BarcodeFormat format) {
mContents = contents;
mHandler = handler;
mPixelResolution = pixelResolution;
mFormat = format;
this.contents = contents;
this.handler = handler;
this.pixelResolution = pixelResolution;
this.format = format;
}
@Override
public void run() {
try {
ByteMatrix result = new MultiFormatWriter().encode(mContents,
mFormat, mPixelResolution, mPixelResolution);
ByteMatrix result = new MultiFormatWriter().encode(contents, format,
pixelResolution, pixelResolution);
int width = result.getWidth();
int height = result.getHeight();
byte[][] array = result.getArray();
@ -228,19 +233,18 @@ public final class QRCodeEncoder {
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
Message message = Message.obtain(mHandler, R.id.encode_succeeded);
Message message = Message.obtain(handler, R.id.encode_succeeded);
message.obj = bitmap;
message.sendToTarget();
} catch (WriterException e) {
Log.e(TAG, e.toString());
Message message = Message.obtain(mHandler, R.id.encode_failed);
Message message = Message.obtain(handler, R.id.encode_failed);
message.sendToTarget();
} catch (IllegalArgumentException e) {
Log.e(TAG, e.toString());
Message message = Message.obtain(mHandler, R.id.encode_failed);
Message message = Message.obtain(handler, R.id.encode_failed);
message.sendToTarget();
}
}
}
}

View file

@ -48,17 +48,53 @@ import java.net.URI;
import java.util.ArrayList;
import java.util.List;
/**
* Uses Google Book Search to find a word or phrase in the requested book.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class SearchBookContentsActivity extends Activity {
private static final String TAG = "SearchBookContents";
private static final String USER_AGENT = "ZXing (Android)";
private NetworkThread mNetworkThread;
private String mISBN;
private EditText mQueryTextView;
private Button mQueryButton;
private ListView mResultListView;
private TextView mHeaderView;
private NetworkThread networkThread;
private String isbn;
private EditText queryTextView;
private Button queryButton;
private ListView resultListView;
private TextView headerView;
public final Handler handler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case R.id.search_book_contents_succeeded:
handleSearchResults((JSONObject) message.obj);
resetForNewQuery();
break;
case R.id.search_book_contents_failed:
resetForNewQuery();
headerView.setText(R.string.msg_sbc_failed);
break;
}
}
};
private final Button.OnClickListener buttonListener = new Button.OnClickListener() {
public void onClick(View view) {
launchSearch();
}
};
private final View.OnKeyListener keyListener = new View.OnKeyListener() {
public boolean onKey(View view, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
launchSearch();
return true;
}
return false;
}
};
@Override
public void onCreate(Bundle icicle) {
@ -69,39 +105,38 @@ public final class SearchBookContentsActivity extends Activity {
CookieManager.getInstance().removeExpiredCookie();
Intent intent = getIntent();
if (intent == null || (!intent.getAction().equals(Intents.SearchBookContents.ACTION) &&
!intent.getAction().equals(Intents.SearchBookContents.DEPRECATED_ACTION))) {
if (intent == null || (!intent.getAction().equals(Intents.SearchBookContents.ACTION))) {
finish();
return;
}
mISBN = intent.getStringExtra(Intents.SearchBookContents.ISBN);
setTitle(getString(R.string.sbc_name) + ": ISBN " + mISBN);
isbn = intent.getStringExtra(Intents.SearchBookContents.ISBN);
setTitle(getString(R.string.sbc_name) + ": ISBN " + isbn);
setContentView(R.layout.search_book_contents);
mQueryTextView = (EditText) findViewById(R.id.query_text_view);
queryTextView = (EditText) findViewById(R.id.query_text_view);
String initialQuery = intent.getStringExtra(Intents.SearchBookContents.QUERY);
if (initialQuery != null && initialQuery.length() > 0) {
// Populate the search box but don't trigger the search
mQueryTextView.setText(initialQuery);
queryTextView.setText(initialQuery);
}
mQueryTextView.setOnKeyListener(mKeyListener);
queryTextView.setOnKeyListener(keyListener);
mQueryButton = (Button) findViewById(R.id.query_button);
mQueryButton.setOnClickListener(mButtonListener);
queryButton = (Button) findViewById(R.id.query_button);
queryButton.setOnClickListener(buttonListener);
mResultListView = (ListView) findViewById(R.id.result_list_view);
resultListView = (ListView) findViewById(R.id.result_list_view);
LayoutInflater factory = LayoutInflater.from(this);
mHeaderView = (TextView) factory.inflate(R.layout.search_book_contents_header,
mResultListView, false);
mResultListView.addHeaderView(mHeaderView);
headerView = (TextView) factory.inflate(R.layout.search_book_contents_header,
resultListView, false);
resultListView.addHeaderView(headerView);
}
@Override
protected void onResume() {
super.onResume();
mQueryTextView.selectAll();
queryTextView.selectAll();
}
@Override
@ -110,55 +145,23 @@ public final class SearchBookContentsActivity extends Activity {
super.onConfigurationChanged(config);
}
public final Handler mHandler = new Handler() {
@Override
public void handleMessage(Message message) {
switch (message.what) {
case R.id.search_book_contents_succeeded:
handleSearchResults((JSONObject) message.obj);
resetForNewQuery();
break;
case R.id.search_book_contents_failed:
resetForNewQuery();
mHeaderView.setText(R.string.msg_sbc_failed);
break;
}
}
};
private void resetForNewQuery() {
mNetworkThread = null;
mQueryTextView.setEnabled(true);
mQueryTextView.selectAll();
mQueryButton.setEnabled(true);
networkThread = null;
queryTextView.setEnabled(true);
queryTextView.selectAll();
queryButton.setEnabled(true);
}
private final Button.OnClickListener mButtonListener = new Button.OnClickListener() {
public void onClick(View view) {
launchSearch();
}
};
private final View.OnKeyListener mKeyListener = new View.OnKeyListener() {
public boolean onKey(View view, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
launchSearch();
return true;
}
return false;
}
};
private void launchSearch() {
if (mNetworkThread == null) {
String query = mQueryTextView.getText().toString();
if (networkThread == null) {
String query = queryTextView.getText().toString();
if (query != null && query.length() > 0) {
mNetworkThread = new NetworkThread(mISBN, query, mHandler);
mNetworkThread.start();
mHeaderView.setText(R.string.msg_sbc_searching_book);
mResultListView.setAdapter(null);
mQueryTextView.setEnabled(false);
mQueryButton.setEnabled(false);
networkThread = new NetworkThread(isbn, query, handler);
networkThread.start();
headerView.setText(R.string.msg_sbc_searching_book);
resultListView.setAdapter(null);
queryTextView.setEnabled(false);
queryButton.setEnabled(false);
}
}
}
@ -168,26 +171,26 @@ public final class SearchBookContentsActivity extends Activity {
private void handleSearchResults(JSONObject json) {
try {
int count = json.getInt("number_of_results");
mHeaderView.setText("Found " + (count == 1 ? "1 result" : count + " results"));
headerView.setText("Found " + (count == 1 ? "1 result" : count + " results"));
if (count > 0) {
JSONArray results = json.getJSONArray("search_results");
SearchBookContentsResult.setQuery(mQueryTextView.getText().toString());
SearchBookContentsResult.setQuery(queryTextView.getText().toString());
List<SearchBookContentsResult> items = new ArrayList<SearchBookContentsResult>(count);
for (int x = 0; x < count; x++) {
items.add(parseResult(results.getJSONObject(x)));
}
mResultListView.setAdapter(new SearchBookContentsAdapter(this, items));
resultListView.setAdapter(new SearchBookContentsAdapter(this, items));
} else {
String searchable = json.optString("searchable");
if ("false".equals(searchable)) {
mHeaderView.setText(R.string.msg_sbc_book_not_searchable);
headerView.setText(R.string.msg_sbc_book_not_searchable);
}
mResultListView.setAdapter(null);
resultListView.setAdapter(null);
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
mResultListView.setAdapter(null);
mHeaderView.setText(R.string.msg_sbc_failed);
resultListView.setAdapter(null);
headerView.setText(R.string.msg_sbc_failed);
}
}
@ -223,15 +226,14 @@ public final class SearchBookContentsActivity extends Activity {
}
private static final class NetworkThread extends Thread {
private final String mISBN;
private final String mQuery;
private final Handler mHandler;
private final String isbn;
private final String query;
private final Handler handler;
NetworkThread(String isbn, String query, Handler handler) {
mISBN = isbn;
mQuery = query;
mHandler = handler;
this.isbn = isbn;
this.query = query;
this.handler = handler;
}
@Override
@ -241,8 +243,8 @@ public final class SearchBookContentsActivity extends Activity {
// These return a JSON result which describes if and where the query was found. This API may
// break or disappear at any time in the future. Since this is an API call rather than a
// website, we don't use LocaleManager to change the TLD.
URI uri = new URI("http", null, "www.google.com", -1, "/books", "vid=isbn" + mISBN +
"&jscmd=SearchWithinVolume2&q=" + mQuery, null);
URI uri = new URI("http", null, "www.google.com", -1, "/books", "vid=isbn" + isbn +
"&jscmd=SearchWithinVolume2&q=" + query, null);
HttpUriRequest get = new HttpGet(uri);
get.setHeader("cookie", getCookie(uri.toString()));
client = AndroidHttpClient.newInstance(USER_AGENT);
@ -255,17 +257,17 @@ public final class SearchBookContentsActivity extends Activity {
JSONObject json = new JSONObject(jsonHolder.toString(getEncoding(entity)));
jsonHolder.close();
Message message = Message.obtain(mHandler, R.id.search_book_contents_succeeded);
Message message = Message.obtain(handler, R.id.search_book_contents_succeeded);
message.obj = json;
message.sendToTarget();
} else {
Log.e(TAG, "HTTP returned " + response.getStatusLine().getStatusCode() + " for " + uri);
Message message = Message.obtain(mHandler, R.id.search_book_contents_failed);
Message message = Message.obtain(handler, R.id.search_book_contents_failed);
message.sendToTarget();
}
} catch (Exception e) {
Log.e(TAG, e.toString());
Message message = Message.obtain(mHandler, R.id.search_book_contents_failed);
Message message = Message.obtain(handler, R.id.search_book_contents_failed);
message.sendToTarget();
} finally {
if (client != null) {
@ -316,5 +318,4 @@ public final class SearchBookContentsActivity extends Activity {
// return "UTF-8";
}
}
}

View file

@ -24,8 +24,12 @@ import android.widget.ArrayAdapter;
import java.util.List;
/**
* Manufactures list items which represent SBC results.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class SearchBookContentsAdapter extends ArrayAdapter<SearchBookContentsResult> {
public SearchBookContentsAdapter(Context context, List<SearchBookContentsResult> items) {
super(context, R.layout.search_book_contents_list_item, 0, items);
}
@ -50,5 +54,4 @@ public final class SearchBookContentsAdapter extends ArrayAdapter<SearchBookCont
listItem.set(result);
return listItem;
}
}

View file

@ -25,10 +25,14 @@ import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;
/**
* A list item which displays the page number and snippet of this search result.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class SearchBookContentsListItem extends LinearLayout {
private TextView mPageNumberView;
private TextView mSnippetView;
private TextView pageNumberView;
private TextView snippetView;
SearchBookContentsListItem(Context context) {
super(context);
@ -41,12 +45,12 @@ public final class SearchBookContentsListItem extends LinearLayout {
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mPageNumberView = (TextView) findViewById(R.id.page_number_view);
mSnippetView = (TextView) findViewById(R.id.snippet_view);
pageNumberView = (TextView) findViewById(R.id.page_number_view);
snippetView = (TextView) findViewById(R.id.snippet_view);
}
public void set(SearchBookContentsResult result) {
mPageNumberView.setText(result.getPageNumber());
pageNumberView.setText(result.getPageNumber());
String snippet = result.getSnippet();
if (snippet.length() > 0) {
if (result.getValidSnippet()) {
@ -64,14 +68,13 @@ public final class SearchBookContentsListItem extends LinearLayout {
styledSnippet.setSpan(boldSpan, pos, pos + queryLength, 0);
offset = pos + queryLength;
}
mSnippetView.setText(styledSnippet);
snippetView.setText(styledSnippet);
} else {
// This may be an error message, so don't try to bold the query terms within it
mSnippetView.setText(snippet);
snippetView.setText(snippet);
}
} else {
mSnippetView.setText("");
snippetView.setText("");
}
}
}

View file

@ -16,38 +16,41 @@
package com.google.zxing.client.android;
/**
* The underlying data for a SBC result.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class SearchBookContentsResult {
private static String query;
private static String sQuery;
private final String mPageNumber;
private final String mSnippet;
private final boolean mValidSnippet;
private final String pageNumber;
private final String snippet;
private final boolean validSnippet;
public SearchBookContentsResult(String pageNumber, String snippet, boolean validSnippet) {
mPageNumber = pageNumber;
mSnippet = snippet;
mValidSnippet = validSnippet;
this.pageNumber = pageNumber;
this.snippet = snippet;
this.validSnippet = validSnippet;
}
public static void setQuery(String query) {
sQuery = query;
SearchBookContentsResult.query = query;
}
public String getPageNumber() {
return mPageNumber;
return pageNumber;
}
public String getSnippet() {
return mSnippet;
return snippet;
}
public boolean getValidSnippet() {
return mValidSnippet;
return validSnippet;
}
public static String getQuery() {
return sQuery;
return query;
}
}

View file

@ -29,8 +29,13 @@ import android.text.ClipboardManager;
import android.view.View;
import android.widget.Button;
/**
* Barcode Scanner can share data like contacts and bookmarks by displaying a QR Code on screen,
* such that another user can scan the barcode with their phone.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ShareActivity extends Activity {
private static final int PICK_BOOKMARK = 0;
private static final int PICK_CONTACT = 1;
@ -51,43 +56,16 @@ public final class ShareActivity extends Activity {
Contacts.PhonesColumns.NUMBER // 1
};
private Button mClipboardButton;
private Button clipboardButton;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.share);
Button mContactButton = (Button) findViewById(R.id.contact_button);
mContactButton.setOnClickListener(mContactListener);
Button mBookmarkButton = (Button) findViewById(R.id.bookmark_button);
mBookmarkButton.setOnClickListener(mBookmarkListener);
mClipboardButton = (Button) findViewById(R.id.clipboard_button);
mClipboardButton.setOnClickListener(mClipboardListener);
}
@Override
protected void onResume() {
super.onResume();
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
if (clipboard.hasText()) {
mClipboardButton.setEnabled(true);
mClipboardButton.setText(R.string.button_share_clipboard);
} else {
mClipboardButton.setEnabled(false);
mClipboardButton.setText(R.string.button_clipboard_empty);
}
}
private final Button.OnClickListener mContactListener = new Button.OnClickListener() {
private final Button.OnClickListener contactListener = new Button.OnClickListener() {
public void onClick(View v) {
startActivityForResult(new Intent(Intent.ACTION_PICK, Contacts.People.CONTENT_URI),
PICK_CONTACT);
}
};
private final Button.OnClickListener mBookmarkListener = new Button.OnClickListener() {
private final Button.OnClickListener bookmarkListener = new Button.OnClickListener() {
public void onClick(View v) {
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setClassName(ShareActivity.this, BookmarkPickerActivity.class.getName());
@ -95,7 +73,7 @@ public final class ShareActivity extends Activity {
}
};
private final Button.OnClickListener mClipboardListener = new Button.OnClickListener() {
private final Button.OnClickListener clipboardListener = new Button.OnClickListener() {
public void onClick(View v) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
// Should always be true, because we grey out the clipboard button in onResume() if it's empty
@ -109,6 +87,33 @@ public final class ShareActivity extends Activity {
}
};
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
setContentView(R.layout.share);
Button mContactButton = (Button) findViewById(R.id.contact_button);
mContactButton.setOnClickListener(contactListener);
Button mBookmarkButton = (Button) findViewById(R.id.bookmark_button);
mBookmarkButton.setOnClickListener(bookmarkListener);
clipboardButton = (Button) findViewById(R.id.clipboard_button);
clipboardButton.setOnClickListener(clipboardListener);
}
@Override
protected void onResume() {
super.onResume();
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
if (clipboard.hasText()) {
clipboardButton.setEnabled(true);
clipboardButton.setText(R.string.button_share_clipboard);
} else {
clipboardButton.setEnabled(false);
clipboardButton.setText(R.string.button_clipboard_empty);
}
}
@Override
public void onActivityResult(int requestCode, int resultCode, Intent intent) {
if (resultCode == RESULT_OK) {
@ -213,5 +218,4 @@ public final class ShareActivity extends Activity {
}
return data;
}
}

View file

@ -28,34 +28,35 @@ import android.view.View;
/**
* 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 = 100L;
private final Paint mPaint;
private final Rect mBox;
private Bitmap mResultBitmap;
private final int mMaskColor;
private final int mResultColor;
private final int mFrameColor;
private final int mLaserColor;
private int mScannerAlpha;
private final Paint paint;
private final Rect box;
private Bitmap resultBitmap;
private final int maskColor;
private final int resultColor;
private final int frameColor;
private final int laserColor;
private int scannerAlpha;
// 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().
mPaint = new Paint();
mBox = new Rect();
paint = new Paint();
box = new Rect();
Resources resources = getResources();
mMaskColor = resources.getColor(R.color.viewfinder_mask);
mResultColor = resources.getColor(R.color.result_view);
mFrameColor = resources.getColor(R.color.viewfinder_frame);
mLaserColor = resources.getColor(R.color.viewfinder_laser);
mScannerAlpha = 0;
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);
scannerAlpha = 0;
}
@Override
@ -65,48 +66,48 @@ public final class ViewfinderView extends View {
int height = canvas.getHeight();
// Draw the exterior (i.e. outside the framing rect) darkened
mPaint.setColor(mResultBitmap != null ? mResultColor : mMaskColor);
mBox.set(0, 0, width, frame.top);
canvas.drawRect(mBox, mPaint);
mBox.set(0, frame.top, frame.left, frame.bottom + 1);
canvas.drawRect(mBox, mPaint);
mBox.set(frame.right + 1, frame.top, width, frame.bottom + 1);
canvas.drawRect(mBox, mPaint);
mBox.set(0, frame.bottom + 1, width, height);
canvas.drawRect(mBox, mPaint);
paint.setColor(resultBitmap != null ? resultColor : maskColor);
box.set(0, 0, width, frame.top);
canvas.drawRect(box, paint);
box.set(0, frame.top, frame.left, frame.bottom + 1);
canvas.drawRect(box, paint);
box.set(frame.right + 1, frame.top, width, frame.bottom + 1);
canvas.drawRect(box, paint);
box.set(0, frame.bottom + 1, width, height);
canvas.drawRect(box, paint);
if (mResultBitmap != null) {
if (resultBitmap != null) {
// Draw the opaque result bitmap over the scanning rectangle
mPaint.setAlpha(255);
canvas.drawBitmap(mResultBitmap, frame.left, frame.top, mPaint);
paint.setAlpha(255);
canvas.drawBitmap(resultBitmap, frame.left, frame.top, paint);
} else {
// Draw a two pixel solid black border inside the framing rect
mPaint.setColor(mFrameColor);
mBox.set(frame.left, frame.top, frame.right + 1, frame.top + 2);
canvas.drawRect(mBox, mPaint);
mBox.set(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1);
canvas.drawRect(mBox, mPaint);
mBox.set(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1);
canvas.drawRect(mBox, mPaint);
mBox.set(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1);
canvas.drawRect(mBox, mPaint);
paint.setColor(frameColor);
box.set(frame.left, frame.top, frame.right + 1, frame.top + 2);
canvas.drawRect(box, paint);
box.set(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1);
canvas.drawRect(box, paint);
box.set(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1);
canvas.drawRect(box, paint);
box.set(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1);
canvas.drawRect(box, paint);
// Draw a red "laser scanner" line through the middle to show decoding is active
mPaint.setColor(mLaserColor);
mPaint.setAlpha(SCANNER_ALPHA[mScannerAlpha]);
mScannerAlpha = (mScannerAlpha + 1) % SCANNER_ALPHA.length;
paint.setColor(laserColor);
paint.setAlpha(SCANNER_ALPHA[scannerAlpha]);
scannerAlpha = (scannerAlpha + 1) % SCANNER_ALPHA.length;
int middle = frame.height() / 2 + frame.top;
mBox.set(frame.left + 2, middle - 1, frame.right - 1, middle + 2);
canvas.drawRect(mBox, mPaint);
box.set(frame.left + 2, middle - 1, frame.right - 1, middle + 2);
canvas.drawRect(box, paint);
// Request another update at the animation interval, but only repaint the laser line,
// not the entire viewfinder mask.
postInvalidateDelayed(ANIMATION_DELAY, mBox.left, mBox.top, mBox.right, mBox.bottom);
postInvalidateDelayed(ANIMATION_DELAY, box.left, box.top, box.right, box.bottom);
}
}
public void drawViewfinder() {
mResultBitmap = null;
resultBitmap = null;
invalidate();
}
@ -116,8 +117,7 @@ public final class ViewfinderView extends View {
* @param barcode An image of the decoded barcode.
*/
public void drawResultBitmap(Bitmap barcode) {
mResultBitmap = barcode;
resultBitmap = barcode;
invalidate();
}
}

View file

@ -16,34 +16,39 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import android.telephony.PhoneNumberUtils;
import android.text.SpannableString;
import android.text.Spannable;
import android.text.style.StyleSpan;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.AddressBookParsedResult;
import com.google.zxing.client.result.ParsedResult;
import android.app.Activity;
import android.telephony.PhoneNumberUtils;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* Handles address book entries.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class AddressBookResultHandler extends ResultHandler {
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
private final boolean[] mFields;
private int mButtonCount;
private final boolean[] fields;
private int buttonCount;
// This takes all the work out of figuring out which buttons/actions should be in which
// positions, based on which fields are present in this barcode.
private int mapIndexToAction(int index) {
if (index < mButtonCount) {
if (index < buttonCount) {
int count = -1;
for (int x = 0; x < MAX_BUTTON_COUNT; x++) {
if (mFields[x]) {
if (fields[x]) {
count++;
}
if (count == index) {
@ -64,23 +69,23 @@ public final class AddressBookResultHandler extends ResultHandler {
String[] emails = addressResult.getEmails();
boolean hasEmailAddress = emails != null && emails.length > 0;
mFields = new boolean[MAX_BUTTON_COUNT];
mFields[0] = true; // Add contact is always available
mFields[1] = hasAddress;
mFields[2] = hasPhoneNumber;
mFields[3] = hasEmailAddress;
fields = new boolean[MAX_BUTTON_COUNT];
fields[0] = true; // Add contact is always available
fields[1] = hasAddress;
fields[2] = hasPhoneNumber;
fields[3] = hasEmailAddress;
mButtonCount = 0;
buttonCount = 0;
for (int x = 0; x < MAX_BUTTON_COUNT; x++) {
if (mFields[x]) {
mButtonCount++;
if (fields[x]) {
buttonCount++;
}
}
}
@Override
public int getButtonCount() {
return mButtonCount;
return buttonCount;
}
@Override
@ -102,7 +107,7 @@ public final class AddressBookResultHandler extends ResultHandler {
@Override
public void handleButtonPress(int index) {
AddressBookParsedResult addressResult = (AddressBookParsedResult) mResult;
AddressBookParsedResult addressResult = (AddressBookParsedResult) result;
int action = mapIndexToAction(index);
switch (action) {
case 0:
@ -130,7 +135,7 @@ public final class AddressBookResultHandler extends ResultHandler {
// Overriden so we can hyphenate phone numbers, format birthdays, and bold the name.
@Override
public CharSequence getDisplayContents() {
AddressBookParsedResult result = (AddressBookParsedResult) mResult;
AddressBookParsedResult result = (AddressBookParsedResult) this.result;
StringBuffer contents = new StringBuffer();
ParsedResult.maybeAppend(result.getNames(), contents);
int namesLength = contents.length();
@ -178,5 +183,4 @@ public final class AddressBookResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_address_book;
}
}

View file

@ -1,57 +0,0 @@
/*
* 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 com.google.zxing.client.android.result;
import android.content.Intent;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ParsedResultType;
import java.net.URISyntaxException;
/**
* A {@link com.google.zxing.client.result.ParsedResult} derived from a URI that encodes an Android
* {@link Intent}, and which should presumably trigger that intent on Android.
*/
public final class AndroidIntentParsedResult extends ParsedResult {
private final Intent mIntent;
private AndroidIntentParsedResult(Intent intent) {
super(ParsedResultType.ANDROID_INTENT);
mIntent = intent;
}
public static AndroidIntentParsedResult parse(String rawText) {
try {
return new AndroidIntentParsedResult(Intent.getIntent(rawText));
} catch (URISyntaxException urise) {
return null;
} catch (IllegalArgumentException iae) {
return null;
}
}
public Intent getIntent() {
return mIntent;
}
@Override
public String getDisplayResult() {
return mIntent.toString();
}
}

View file

@ -16,11 +16,12 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.CalendarParsedResult;
import com.google.zxing.client.result.ParsedResult;
import android.app.Activity;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
@ -28,12 +29,16 @@ import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* Handles calendar entries encoded in QR Codes.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class CalendarResultHandler extends ResultHandler {
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
private static final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
private static final int[] mButtons = {
private static final int[] buttons = {
R.string.button_add_calendar
};
@ -43,17 +48,17 @@ public final class CalendarResultHandler extends ResultHandler {
@Override
public int getButtonCount() {
return mButtons.length;
return buttons.length;
}
@Override
public int getButtonText(int index) {
return mButtons[index];
return buttons[index];
}
@Override
public void handleButtonPress(int index) {
CalendarParsedResult calendarResult = (CalendarParsedResult) mResult;
CalendarParsedResult calendarResult = (CalendarParsedResult) result;
switch (index) {
case 0:
addCalendarEvent(calendarResult.getSummary(), calendarResult.getStart(),
@ -64,7 +69,7 @@ public final class CalendarResultHandler extends ResultHandler {
@Override
public CharSequence getDisplayContents() {
CalendarParsedResult calResult = (CalendarParsedResult) mResult;
CalendarParsedResult calResult = (CalendarParsedResult) result;
StringBuffer result = new StringBuffer();
ParsedResult.maybeAppend(calResult.getSummary(), result);
appendTime(calResult.getStart(), result);
@ -110,5 +115,4 @@ public final class CalendarResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_calendar;
}
}

View file

@ -16,14 +16,19 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.EmailAddressParsedResult;
import com.google.zxing.client.result.ParsedResult;
public final class EmailAddressResultHandler extends ResultHandler {
import android.app.Activity;
private static final int[] mButtons = {
/**
* Handles email addresses.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class EmailAddressResultHandler extends ResultHandler {
private static final int[] buttons = {
R.string.button_email,
R.string.button_add_contact
};
@ -34,17 +39,17 @@ public final class EmailAddressResultHandler extends ResultHandler {
@Override
public int getButtonCount() {
return mButtons.length;
return buttons.length;
}
@Override
public int getButtonText(int index) {
return mButtons[index];
return buttons[index];
}
@Override
public void handleButtonPress(int index) {
EmailAddressParsedResult emailResult = (EmailAddressParsedResult) mResult;
EmailAddressParsedResult emailResult = (EmailAddressParsedResult) result;
switch (index) {
case 0:
sendEmailFromUri(emailResult.getMailtoURI(), null, null);
@ -61,5 +66,4 @@ public final class EmailAddressResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_email_address;
}
}

View file

@ -16,14 +16,19 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.GeoParsedResult;
import com.google.zxing.client.result.ParsedResult;
public final class GeoResultHandler extends ResultHandler {
import android.app.Activity;
private static final int[] mButtons = {
/**
* Handles geographic coordinates (typically encoded as geo: URLs).
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class GeoResultHandler extends ResultHandler {
private static final int[] buttons = {
R.string.button_show_map,
R.string.button_get_directions
};
@ -34,17 +39,17 @@ public final class GeoResultHandler extends ResultHandler {
@Override
public int getButtonCount() {
return mButtons.length;
return buttons.length;
}
@Override
public int getButtonText(int index) {
return mButtons[index];
return buttons[index];
}
@Override
public void handleButtonPress(int index) {
GeoParsedResult geoResult = (GeoParsedResult) mResult;
GeoParsedResult geoResult = (GeoParsedResult) result;
switch (index) {
case 0:
openMap(geoResult.getGeoURI());
@ -59,5 +64,4 @@ public final class GeoResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_geo;
}
}

View file

@ -16,44 +16,50 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import com.google.zxing.client.android.R;
import com.google.zxing.client.android.PreferencesActivity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ISBNParsedResult;
import com.google.zxing.client.result.ParsedResult;
public final class ISBNResultHandler extends ResultHandler {
import android.app.Activity;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
private static final int[] mButtons = {
/**
* Handles books encoded by their ISBN values.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ISBNResultHandler extends ResultHandler {
private static final int[] buttons = {
R.string.button_product_search,
R.string.button_book_search,
R.string.button_search_book_contents,
R.string.button_custom_product_search,
};
private final String mCustomProductSearch;
private final String customProductSearch;
public ISBNResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
mCustomProductSearch = prefs.getString(PreferencesActivity.KEY_CUSTOM_PRODUCT_SEARCH, null);
customProductSearch = prefs.getString(PreferencesActivity.KEY_CUSTOM_PRODUCT_SEARCH, null);
}
@Override
public int getButtonCount() {
return mCustomProductSearch != null && mCustomProductSearch.length() > 0 ? mButtons.length : mButtons.length - 1;
return customProductSearch != null && customProductSearch.length() > 0 ? buttons.length : buttons
.length - 1;
}
@Override
public int getButtonText(int index) {
return mButtons[index];
return buttons[index];
}
@Override
public void handleButtonPress(int index) {
ISBNParsedResult isbnResult = (ISBNParsedResult) mResult;
ISBNParsedResult isbnResult = (ISBNParsedResult) result;
switch (index) {
case 0:
openProductSearch(isbnResult.getISBN());
@ -65,7 +71,7 @@ public final class ISBNResultHandler extends ResultHandler {
searchBookContents(isbnResult.getISBN());
break;
case 3:
String url = mCustomProductSearch.replace("%s", isbnResult.getISBN());
String url = customProductSearch.replace("%s", isbnResult.getISBN());
openURL(url);
break;
}
@ -75,5 +81,4 @@ public final class ISBNResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_isbn;
}
}

View file

@ -16,43 +16,48 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
import com.google.zxing.client.android.R;
import com.google.zxing.client.android.PreferencesActivity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ProductParsedResult;
public final class ProductResultHandler extends ResultHandler {
import android.app.Activity;
import android.content.SharedPreferences;
import android.preference.PreferenceManager;
private static final int[] mButtons = {
/**
* Handles generic products which are not books.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ProductResultHandler extends ResultHandler {
private static final int[] buttons = {
R.string.button_product_search,
R.string.button_web_search,
R.string.button_custom_product_search,
};
private final String mCustomProductSearch;
private final String customProductSearch;
public ProductResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
mCustomProductSearch = prefs.getString(PreferencesActivity.KEY_CUSTOM_PRODUCT_SEARCH, null);
customProductSearch = prefs.getString(PreferencesActivity.KEY_CUSTOM_PRODUCT_SEARCH, null);
}
@Override
public int getButtonCount() {
return mCustomProductSearch != null ? mButtons.length : mButtons.length - 1;
return customProductSearch != null ? buttons.length : buttons.length - 1;
}
@Override
public int getButtonText(int index) {
return mButtons[index];
return buttons[index];
}
@Override
public void handleButtonPress(int index) {
ProductParsedResult productResult = (ProductParsedResult) mResult;
ProductParsedResult productResult = (ProductParsedResult) result;
switch (index) {
case 0:
openProductSearch(productResult.getNormalizedProductID());
@ -61,7 +66,7 @@ public final class ProductResultHandler extends ResultHandler {
webSearch(productResult.getNormalizedProductID());
break;
case 2:
String url = mCustomProductSearch.replace("%s", productResult.getNormalizedProductID());
String url = customProductSearch.replace("%s", productResult.getNormalizedProductID());
openURL(url);
break;
}
@ -71,5 +76,4 @@ public final class ProductResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_product;
}
}

View file

@ -22,19 +22,19 @@ import android.widget.Button;
/**
* Handles the result of barcode decoding in the context of the Android platform, by dispatching the
* proper intents to open other activities like GMail, Maps, etc.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ResultButtonListener implements Button.OnClickListener {
final ResultHandler mResultHandler;
final int mIndex;
final ResultHandler resultHandler;
final int index;
public ResultButtonListener(ResultHandler resultHandler, int index) {
mResultHandler = resultHandler;
mIndex = index;
this.resultHandler = resultHandler;
this.index = index;
}
public void onClick(View view) {
mResultHandler.handleButtonPress(mIndex);
resultHandler.handleButtonPress(index);
}
}

View file

@ -16,40 +16,50 @@
package com.google.zxing.client.android.result;
import com.google.zxing.client.android.Contents;
import com.google.zxing.client.android.Intents;
import com.google.zxing.client.android.LocaleManager;
import com.google.zxing.client.android.R;
import com.google.zxing.client.android.SearchBookContentsActivity;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ParsedResultType;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.ActivityNotFoundException;
import android.content.Intent;
import android.net.Uri;
import android.provider.Contacts;
import com.google.zxing.client.android.Intents;
import com.google.zxing.client.android.R;
import com.google.zxing.client.android.SearchBookContentsActivity;
import com.google.zxing.client.android.LocaleManager;
import com.google.zxing.client.android.Contents;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ParsedResultType;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.text.DateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
/**
* 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 abstract class ResultHandler {
private static final DateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
private static final DateFormat DATE_TIME_FORMAT = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
public static final int MAX_BUTTON_COUNT = 4;
protected final ParsedResult mResult;
private final Activity mActivity;
protected final ParsedResult result;
private final Activity activity;
protected ResultHandler(Activity activity, ParsedResult result) {
mResult = result;
mActivity = activity;
this.result = result;
this.activity = activity;
}
/**
@ -81,7 +91,7 @@ public abstract class ResultHandler {
* @return The text to be displayed.
*/
public CharSequence getDisplayContents() {
String contents = mResult.getDisplayResult();
String contents = result.getDisplayResult();
return contents.replace("\r", "");
}
@ -98,7 +108,7 @@ public abstract class ResultHandler {
* @return The parsed type, e.g. URI or ISBN
*/
public final ParsedResultType getType() {
return mResult.getType();
return result.getType();
}
/**
@ -171,7 +181,7 @@ public abstract class ResultHandler {
}
public final void shareByEmail(String contents) {
sendEmailFromUri("mailto:", mActivity.getString(R.string.msg_share_subject_line), contents);
sendEmailFromUri("mailto:", activity.getString(R.string.msg_share_subject_line), contents);
}
public final void sendEmail(String address, String subject, String body) {
@ -188,7 +198,7 @@ public abstract class ResultHandler {
}
public final void shareBySMS(String contents) {
sendSMSFromUri("smsto:", mActivity.getString(R.string.msg_share_subject_line) + ":\n" +
sendSMSFromUri("smsto:", activity.getString(R.string.msg_share_subject_line) + ":\n" +
contents);
}
@ -212,7 +222,7 @@ public abstract class ResultHandler {
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse(uri));
// The Messaging app needs to see a valid subject or else it will treat this an an SMS.
if (subject == null || subject.length() == 0) {
putExtra(intent, "subject", mActivity.getString(R.string.msg_default_mms_subject));
putExtra(intent, "subject", activity.getString(R.string.msg_default_mms_subject));
} else {
putExtra(intent, "subject", subject);
}
@ -267,7 +277,7 @@ public abstract class ResultHandler {
public final void searchBookContents(String isbn) {
Intent intent = new Intent(Intents.SearchBookContents.ACTION);
intent.setClassName(mActivity, SearchBookContentsActivity.class.getName());
intent.setClassName(activity, SearchBookContentsActivity.class.getName());
putExtra(intent, Intents.SearchBookContents.ISBN, isbn);
launchIntent(intent);
}
@ -285,11 +295,11 @@ public abstract class ResultHandler {
private void launchIntent(Intent intent) {
if (intent != null) {
try {
mActivity.startActivity(intent);
activity.startActivity(intent);
} catch (ActivityNotFoundException e) {
AlertDialog.Builder builder = new AlertDialog.Builder(mActivity);
builder.setTitle(mActivity.getString(R.string.app_name));
builder.setMessage(mActivity.getString(R.string.msg_intent_failed));
AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(activity.getString(R.string.app_name));
builder.setMessage(activity.getString(R.string.msg_intent_failed));
builder.setPositiveButton(R.string.button_ok, null);
builder.show();
}
@ -301,5 +311,4 @@ public abstract class ResultHandler {
intent.putExtra(key, value);
}
}
}

View file

@ -16,14 +16,19 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import com.google.zxing.Result;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ParsedResultType;
import com.google.zxing.client.result.ResultParser;
public final class ResultHandlerFactory {
import android.app.Activity;
/**
* Manufactures Android-specific handlers based on the barcode content's type.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class ResultHandlerFactory {
private ResultHandlerFactory() {
}
@ -57,22 +62,6 @@ public final class ResultHandlerFactory {
}
private static ParsedResult parseResult(Result rawResult) {
ParsedResult result = ResultParser.parseResult(rawResult);
// Disabled for now. To reactivate, create an AndroidIntentResultHandler.
// if (result.getType().equals(ParsedResultType.TEXT)) {
// String rawText = rawResult.getText();
// AndroidIntentParsedResult androidResult = AndroidIntentParsedResult.parse(rawText);
// if (androidResult != null) {
// Intent intent = androidResult.getIntent();
// if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
// // For now, don't take anything that just parses as a View action. A lot
// // of things are accepted as a View action by default.
// result = androidResult;
// }
// }
// }
return result;
return ResultParser.parseResult(rawResult);
}
}

View file

@ -16,15 +16,20 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import android.telephony.PhoneNumberUtils;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.SMSParsedResult;
public final class SMSResultHandler extends ResultHandler {
import android.app.Activity;
import android.telephony.PhoneNumberUtils;
private static final int[] mButtons = {
/**
* Handles SMS addresses, offering a choice of composing a new SMS or MMS message.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class SMSResultHandler extends ResultHandler {
private static final int[] buttons = {
R.string.button_sms,
R.string.button_mms
};
@ -35,17 +40,17 @@ public final class SMSResultHandler extends ResultHandler {
@Override
public int getButtonCount() {
return mButtons.length;
return buttons.length;
}
@Override
public int getButtonText(int index) {
return mButtons[index];
return buttons[index];
}
@Override
public void handleButtonPress(int index) {
SMSParsedResult smsResult = (SMSParsedResult) mResult;
SMSParsedResult smsResult = (SMSParsedResult) result;
switch (index) {
case 0:
sendSMS(smsResult.getNumber(), smsResult.getBody());
@ -58,7 +63,7 @@ public final class SMSResultHandler extends ResultHandler {
@Override
public CharSequence getDisplayContents() {
SMSParsedResult smsResult = (SMSParsedResult) mResult;
SMSParsedResult smsResult = (SMSParsedResult) result;
StringBuffer contents = new StringBuffer();
ParsedResult.maybeAppend(PhoneNumberUtils.formatNumber(smsResult.getNumber()), contents);
ParsedResult.maybeAppend(smsResult.getVia(), contents);
@ -72,5 +77,4 @@ public final class SMSResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_sms;
}
}

View file

@ -16,15 +16,20 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import android.telephony.PhoneNumberUtils;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.TelParsedResult;
public final class TelResultHandler extends ResultHandler {
import android.app.Activity;
import android.telephony.PhoneNumberUtils;
private static final int[] mButtons = {
/**
* Offers relevant actions for telephone numbers.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class TelResultHandler extends ResultHandler {
private static final int[] buttons = {
R.string.button_dial,
R.string.button_add_contact
};
@ -35,17 +40,17 @@ public final class TelResultHandler extends ResultHandler {
@Override
public int getButtonCount() {
return mButtons.length;
return buttons.length;
}
@Override
public int getButtonText(int index) {
return mButtons[index];
return buttons[index];
}
@Override
public void handleButtonPress(int index) {
TelParsedResult telResult = (TelParsedResult) mResult;
TelParsedResult telResult = (TelParsedResult) result;
switch (index) {
case 0:
dialPhoneFromUri(telResult.getTelURI());
@ -61,7 +66,7 @@ public final class TelResultHandler extends ResultHandler {
// Overriden so we can take advantage of Android's phone number hyphenation routines.
@Override
public CharSequence getDisplayContents() {
String contents = mResult.getDisplayResult();
String contents = result.getDisplayResult();
contents = contents.replace("\r", "");
return PhoneNumberUtils.formatNumber(contents);
}
@ -70,5 +75,4 @@ public final class TelResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_tel;
}
}

View file

@ -16,16 +16,18 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
import android.app.Activity;
/**
* This class handles TextParsedResult as well as unknown formats.
* This class handles TextParsedResult as well as unknown formats. It's the fallback handler.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class TextResultHandler extends ResultHandler {
private static final int[] mButtons = {
private static final int[] buttons = {
R.string.button_web_search,
R.string.button_share_by_email,
R.string.button_share_by_sms
@ -37,25 +39,25 @@ public final class TextResultHandler extends ResultHandler {
@Override
public int getButtonCount() {
return mButtons.length;
return buttons.length;
}
@Override
public int getButtonText(int index) {
return mButtons[index];
return buttons[index];
}
@Override
public void handleButtonPress(int index) {
switch (index) {
case 0:
webSearch(mResult.getDisplayResult());
webSearch(result.getDisplayResult());
break;
case 1:
shareByEmail(mResult.getDisplayResult());
shareByEmail(result.getDisplayResult());
break;
case 2:
shareBySMS(mResult.getDisplayResult());
shareBySMS(result.getDisplayResult());
break;
}
}
@ -64,5 +66,4 @@ public final class TextResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_text;
}
}

View file

@ -16,14 +16,19 @@
package com.google.zxing.client.android.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.URIParsedResult;
public final class URIResultHandler extends ResultHandler {
import android.app.Activity;
private static final int[] mButtons = {
/**
* Offers appropriate actions for URLS.
*
* @author dswitkin@google.com (Daniel Switkin)
*/
public final class URIResultHandler extends ResultHandler {
private static final int[] buttons = {
R.string.button_open_browser,
R.string.button_share_by_email,
R.string.button_share_by_sms
@ -35,17 +40,17 @@ public final class URIResultHandler extends ResultHandler {
@Override
public int getButtonCount() {
return mButtons.length;
return buttons.length;
}
@Override
public int getButtonText(int index) {
return mButtons[index];
return buttons[index];
}
@Override
public void handleButtonPress(int index) {
URIParsedResult uriResult = (URIParsedResult) mResult;
URIParsedResult uriResult = (URIParsedResult) result;
switch (index) {
case 0:
openURL(uriResult.getURI());
@ -63,5 +68,4 @@ public final class URIResultHandler extends ResultHandler {
public int getDisplayTitle() {
return R.string.result_uri;
}
}