Final changes for v2.7 of Barcode Scanner, including sending product lookups to the mobile version of Google Product Search.

git-svn-id: https://zxing.googlecode.com/svn/trunk@923 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
dswitkin 2009-05-01 14:36:57 +00:00
parent 0de1fbc623
commit ae1241880b
8 changed files with 183 additions and 86 deletions

View file

@ -20,8 +20,9 @@ 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.6"
android:versionCode="13">
android:versionName="2.7"
android:versionCode="18">
<uses-sdk android:minSdkVersion="1"/>
<application android:icon="@drawable/launcher_icon"
android:label="@string/app_name">
<activity android:name=".CaptureActivity"
@ -48,6 +49,20 @@ versionName is 2.31, 2.4, or 3.0. -->
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:host="zxing.appspot.com" android:path="/scan" />
</intent-filter>
<!-- We also support a Google Product Search URL. -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:host="www.google.com" android:path="/m/products/scan" />
</intent-filter>
<!-- And the UK version. -->
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:host="www.google.co.uk" android:path="/m/products/scan" />
</intent-filter>
</activity>
<activity android:name="PreferencesActivity"
android:label="@string/preferences_name">

View file

@ -3,14 +3,14 @@
<body>
<link rel="StyleSheet" href="style.css" type="text/css">
<h3><center>What's new in this version</center></h3>
<p>New in version 2.6:</p>
<p>New in version 2.7:</p>
<ul>
<li>Added support for ITF format barcodes.</li>
<li>Fixed a bug where some URLs would be misinterpreted as email addresses.</li>
<li>Added support for uppercase URIs like TEL: and GEO:.</li>
<li>Prevented Barcode Scanner from crashing when launching an unhandled intent.</li>
<li>Web apps can now launch Barcode Scanner by creating a hyperlink to <i>http://zxing.appspot.com/scan</i>.</li>
<li>Added the version to the about box.</li>
<li>Searching for a product online now uses the new mobile version of Google Product Search, which
is formatted for small screens and loads quicker.</li>
<li>Two fixes in QR Code version detection.</li>
<li>Fixed encoding QR Codes of geo locations.</li>
<li>You can now encode a contact as a QR Code even if it doesn't have a name, i.e. just a phone
number.</li>
</ul>
</body>
</html>

View file

@ -22,6 +22,7 @@
<item type="id" name="decode_succeeded"/>
<item type="id" name="encode_failed"/>
<item type="id" name="encode_succeeded"/>
<item type="id" name="launch_product_query"/>
<item type="id" name="quit"/>
<item type="id" name="restart_preview"/>
<item type="id" name="return_scan_result"/>

View file

@ -79,7 +79,7 @@
<string name="msg_share_subject_line">Here\'s the contents of a barcode I scanned</string>
<string name="preferences_actions_title">When a barcode is found\u2026</string>
<string name="preferences_copy_to_clipboard_title">Copy contents to clipboard</string>
<string name="preferences_copy_to_clipboard_title">Copy to clipboard</string>
<string name="preferences_decode_1D_title">Decode 1D barcodes</string>
<string name="preferences_decode_QR_title">Decode QR Codes</string>
<string name="preferences_general_title">General settings</string>

View file

@ -37,9 +37,10 @@ import android.os.Bundle;
import android.os.Message;
import android.os.Vibrator;
import android.preference.PreferenceManager;
import android.text.SpannableStringBuilder;
import android.text.ClipboardManager;
import android.text.SpannableStringBuilder;
import android.text.style.UnderlineSpan;
import android.util.Log;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
@ -53,7 +54,6 @@ import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.util.Log;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.result.ResultButtonListener;
@ -81,6 +81,16 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
private static final long VIBRATE_DURATION = 200;
private static final String PACKAGE_NAME = "com.google.zxing.client.android";
private static final String PRODUCT_SEARCH_URL_PREFIX = "http://www.google";
private static final String PRODUCT_SEARCH_URL_SUFFIX = "/m/products/scan";
private static final String ZXING_URL = "http://zxing.appspot.com/scan";
private enum Source {
NATIVE_APP_INTENT,
PRODUCT_SEARCH_LINK,
ZXING_LINK,
NONE
}
public CaptureActivityHandler mHandler;
@ -93,9 +103,10 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
private boolean mPlayBeep;
private boolean mVibrate;
private boolean mCopyToClipboard;
private boolean mScanIntent;
private Source mSource;
private String mSourceUrl;
private String mDecodeMode;
private String versionName;
private String mVersionName;
private final OnCompletionListener mBeepListener = new BeepListener();
@ -137,13 +148,35 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
Intent intent = getIntent();
String action = intent.getAction();
if (intent != null && action != null && (action.equals(Intents.Scan.ACTION) ||
action.equals(Intents.Scan.DEPRECATED_ACTION))) {
mScanIntent = true;
mDecodeMode = intent.getStringExtra(Intents.Scan.MODE);
resetStatusView();
String dataString = intent.getDataString();
if (intent != null && action != null) {
if (action.equals(Intents.Scan.ACTION) || action.equals(Intents.Scan.DEPRECATED_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);
resetStatusView();
} else if (dataString != null && dataString.contains(PRODUCT_SEARCH_URL_PREFIX) &&
dataString.contains(PRODUCT_SEARCH_URL_PREFIX)) {
// Scan only products and send the result to mobile Product Search.
mSource = Source.PRODUCT_SEARCH_LINK;
mSourceUrl = dataString;
mDecodeMode = 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;
resetStatusView();
} else {
// Scan all formats and handle the results ourselves (launched from Home).
mSource = Source.NONE;
mDecodeMode = null;
resetStatusView();
}
} else {
mScanIntent = false;
mSource = Source.NONE;
mDecodeMode = null;
if (mLastResult == null) {
resetStatusView();
@ -170,11 +203,11 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mScanIntent) {
if (mSource == Source.NATIVE_APP_INTENT) {
setResult(RESULT_CANCELED);
finish();
return true;
} else if (mLastResult != null) {
} else if ((mSource == Source.NONE || mSource == Source.ZXING_LINK) && mLastResult != null) {
resetStatusView();
mHandler.sendEmptyMessage(R.id.restart_preview);
return true;
@ -230,7 +263,7 @@ 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) + versionName);
builder.setTitle(getString(R.string.title_about) + mVersionName);
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);
@ -274,60 +307,21 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
*
* @param rawResult The contents of the barcode.
* @param barcode A greyscale bitmap of the camera data which was decoded.
* @param duration How long the decoding took in milliseconds.
*/
public void handleDecode(Result rawResult, Bitmap barcode, int duration) {
public void handleDecode(Result rawResult, Bitmap barcode) {
mLastResult = rawResult;
playBeepSoundAndVibrate();
drawResultPoints(barcode, rawResult);
if (mScanIntent) {
handleDecodeForScanIntent(rawResult, barcode, duration);
} else {
mStatusView.setVisibility(View.GONE);
mViewfinderView.setVisibility(View.GONE);
mResultView.setVisibility(View.VISIBLE);
ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view);
barcodeImageView.setMaxWidth(MAX_RESULT_IMAGE_SIZE);
barcodeImageView.setMaxHeight(MAX_RESULT_IMAGE_SIZE);
barcodeImageView.setImageBitmap(barcode);
TextView formatTextView = (TextView) findViewById(R.id.format_text_view);
formatTextView.setText(getString(R.string.msg_default_format) + ": " +
rawResult.getBarcodeFormat().toString());
ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult);
TextView typeTextView = (TextView) findViewById(R.id.type_text_view);
typeTextView.setText(getString(R.string.msg_default_type) + ": " +
resultHandler.getType().toString());
TextView contentsTextView = (TextView) findViewById(R.id.contents_text_view);
CharSequence title = getString(resultHandler.getDisplayTitle());
SpannableStringBuilder styled = new SpannableStringBuilder(title + "\n\n");
styled.setSpan(new UnderlineSpan(), 0, title.length(), 0);
CharSequence displayContents = resultHandler.getDisplayContents();
styled.append(displayContents);
contentsTextView.setText(styled);
int buttonCount = resultHandler.getButtonCount();
ViewGroup buttonView = (ViewGroup) findViewById(R.id.result_button_view);
buttonView.requestFocus();
for (int x = 0; x < ResultHandler.MAX_BUTTON_COUNT; x++) {
Button button = (Button) buttonView.getChildAt(x);
if (x < buttonCount) {
button.setVisibility(View.VISIBLE);
button.setText(resultHandler.getButtonText(x));
button.setOnClickListener(new ResultButtonListener(resultHandler, x));
} else {
button.setVisibility(View.GONE);
}
}
if (mCopyToClipboard) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clipboard.setText(displayContents);
}
switch (mSource) {
case NATIVE_APP_INTENT:
case PRODUCT_SEARCH_LINK:
handleDecodeExternally(rawResult, barcode);
break;
case ZXING_LINK:
case NONE:
handleDecodeInternally(rawResult, barcode);
break;
}
}
@ -362,7 +356,56 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
}
}
private void handleDecodeForScanIntent(Result rawResult, Bitmap barcode, int duration) {
// 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);
ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view);
barcodeImageView.setMaxWidth(MAX_RESULT_IMAGE_SIZE);
barcodeImageView.setMaxHeight(MAX_RESULT_IMAGE_SIZE);
barcodeImageView.setImageBitmap(barcode);
TextView formatTextView = (TextView) findViewById(R.id.format_text_view);
formatTextView.setText(getString(R.string.msg_default_format) + ": " +
rawResult.getBarcodeFormat().toString());
ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult);
TextView typeTextView = (TextView) findViewById(R.id.type_text_view);
typeTextView.setText(getString(R.string.msg_default_type) + ": " +
resultHandler.getType().toString());
TextView contentsTextView = (TextView) findViewById(R.id.contents_text_view);
CharSequence title = getString(resultHandler.getDisplayTitle());
SpannableStringBuilder styled = new SpannableStringBuilder(title + "\n\n");
styled.setSpan(new UnderlineSpan(), 0, title.length(), 0);
CharSequence displayContents = resultHandler.getDisplayContents();
styled.append(displayContents);
contentsTextView.setText(styled);
int buttonCount = resultHandler.getButtonCount();
ViewGroup buttonView = (ViewGroup) findViewById(R.id.result_button_view);
buttonView.requestFocus();
for (int x = 0; x < ResultHandler.MAX_BUTTON_COUNT; x++) {
Button button = (Button) buttonView.getChildAt(x);
if (x < buttonCount) {
button.setVisibility(View.VISIBLE);
button.setText(resultHandler.getButtonText(x));
button.setOnClickListener(new ResultButtonListener(resultHandler, x));
} else {
button.setVisibility(View.GONE);
}
}
if (mCopyToClipboard) {
ClipboardManager clipboard = (ClipboardManager) getSystemService(CLIPBOARD_SERVICE);
clipboard.setText(displayContents);
}
}
// Briefly show the contents of the barcode, then handle the result outside Barcode Scanner.
private void handleDecodeExternally(Result rawResult, Bitmap barcode) {
mViewfinderView.drawResultBitmap(barcode);
// Since this message will only be shown for a second, just tell the user what kind of
@ -381,14 +424,24 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
clipboard.setText(resultHandler.getDisplayContents());
}
// 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.obj = intent;
mHandler.sendMessageDelayed(message, INTENT_RESULT_DURATION);
if (mSource == 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.obj = intent;
mHandler.sendMessageDelayed(message, INTENT_RESULT_DURATION);
} else if (mSource == 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=" +
resultHandler.getDisplayContents().toString() + "&source=zxing";
mHandler.sendMessageDelayed(message, INTENT_RESULT_DURATION);
}
}
/**
@ -402,7 +455,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.versionName = info.versionName;
this.mVersionName = info.versionName;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
int lastVersion = prefs.getInt(PreferencesActivity.KEY_HELP_VERSION_SHOWN, 0);
if (currentVersion > lastVersion) {

View file

@ -19,6 +19,7 @@ package com.google.zxing.client.android;
import android.app.Activity;
import android.content.Intent;
import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
@ -70,8 +71,7 @@ public final class CaptureActivityHandler extends Handler {
mState = State.SUCCESS;
Bundle bundle = message.getData();
Bitmap barcode = bundle.getParcelable(DecodeThread.BARCODE_BITMAP);
int duration = message.arg1;
mActivity.handleDecode((Result) message.obj, barcode, duration);
mActivity.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.
@ -82,6 +82,10 @@ public final class CaptureActivityHandler extends Handler {
mActivity.setResult(Activity.RESULT_OK, (Intent) message.obj);
mActivity.finish();
break;
case R.id.launch_product_query:
String url = (String) message.obj;
mActivity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
break;
}
}

View file

@ -40,6 +40,13 @@ public final class LocaleManager {
GOOGLE_COUNTRY_TLD.put(Locale.UK, "co.uk");
}
// Google Product Search for mobile is available in fewer countries than web search.
private static final Map<Locale,String> GOOGLE_PRODUCT_SEARCH_COUNTRY_TLD;
static {
GOOGLE_PRODUCT_SEARCH_COUNTRY_TLD = new HashMap<Locale,String>();
GOOGLE_PRODUCT_SEARCH_COUNTRY_TLD.put(Locale.UK, "co.uk");
}
private LocaleManager() {}
/**
@ -58,4 +65,20 @@ public final class LocaleManager {
return tld;
}
/**
* The same as above, but specifically for Google Product Search.
* @return The top-level domain to use.
*/
public static String getProductSearchCountryTLD() {
Locale locale = Locale.getDefault();
if (locale == null) {
return DEFAULT_TLD;
}
String tld = GOOGLE_PRODUCT_SEARCH_COUNTRY_TLD.get(locale);
if (tld == null) {
return DEFAULT_TLD;
}
return tld;
}
}

View file

@ -253,9 +253,10 @@ public abstract class ResultHandler {
LocaleManager.getCountryTLD() + "/maps?f=d&daddr=" + latitude + ',' + longitude)));
}
// Uses the mobile-specific version of Product Search, which is formatted for small screens.
public final void openProductSearch(String upc) {
Uri uri = Uri.parse("http://www.google." + LocaleManager.getCountryTLD() + "/products?q=" +
upc);
Uri uri = Uri.parse("http://www.google." + LocaleManager.getProductSearchCountryTLD() +
"/m/products?q=" + upc + "&source=zxing");
launchIntent(new Intent(Intent.ACTION_VIEW, uri));
}