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. --> versionName is 2.31, 2.4, or 3.0. -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.zxing.client.android" package="com.google.zxing.client.android"
android:versionName="2.6" android:versionName="2.7"
android:versionCode="13"> android:versionCode="18">
<uses-sdk android:minSdkVersion="1"/>
<application android:icon="@drawable/launcher_icon" <application android:icon="@drawable/launcher_icon"
android:label="@string/app_name"> android:label="@string/app_name">
<activity android:name=".CaptureActivity" <activity android:name=".CaptureActivity"
@ -48,6 +49,20 @@ versionName is 2.31, 2.4, or 3.0. -->
<category android:name="android.intent.category.BROWSABLE"/> <category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http" android:host="zxing.appspot.com" android:path="/scan" /> <data android:scheme="http" android:host="zxing.appspot.com" android:path="/scan" />
</intent-filter> </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>
<activity android:name="PreferencesActivity" <activity android:name="PreferencesActivity"
android:label="@string/preferences_name"> android:label="@string/preferences_name">

View file

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

View file

@ -22,6 +22,7 @@
<item type="id" name="decode_succeeded"/> <item type="id" name="decode_succeeded"/>
<item type="id" name="encode_failed"/> <item type="id" name="encode_failed"/>
<item type="id" name="encode_succeeded"/> <item type="id" name="encode_succeeded"/>
<item type="id" name="launch_product_query"/>
<item type="id" name="quit"/> <item type="id" name="quit"/>
<item type="id" name="restart_preview"/> <item type="id" name="restart_preview"/>
<item type="id" name="return_scan_result"/> <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="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_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_1D_title">Decode 1D barcodes</string>
<string name="preferences_decode_QR_title">Decode QR Codes</string> <string name="preferences_decode_QR_title">Decode QR Codes</string>
<string name="preferences_general_title">General settings</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.Message;
import android.os.Vibrator; import android.os.Vibrator;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.text.SpannableStringBuilder;
import android.text.ClipboardManager; import android.text.ClipboardManager;
import android.text.SpannableStringBuilder;
import android.text.style.UnderlineSpan; import android.text.style.UnderlineSpan;
import android.util.Log;
import android.view.Gravity; import android.view.Gravity;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.Menu; import android.view.Menu;
@ -53,7 +54,6 @@ import android.view.WindowManager;
import android.widget.Button; import android.widget.Button;
import android.widget.ImageView; import android.widget.ImageView;
import android.widget.TextView; import android.widget.TextView;
import android.util.Log;
import com.google.zxing.Result; import com.google.zxing.Result;
import com.google.zxing.ResultPoint; import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.result.ResultButtonListener; 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 long VIBRATE_DURATION = 200;
private static final String PACKAGE_NAME = "com.google.zxing.client.android"; 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; public CaptureActivityHandler mHandler;
@ -93,9 +103,10 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
private boolean mPlayBeep; private boolean mPlayBeep;
private boolean mVibrate; private boolean mVibrate;
private boolean mCopyToClipboard; private boolean mCopyToClipboard;
private boolean mScanIntent; private Source mSource;
private String mSourceUrl;
private String mDecodeMode; private String mDecodeMode;
private String versionName; private String mVersionName;
private final OnCompletionListener mBeepListener = new BeepListener(); private final OnCompletionListener mBeepListener = new BeepListener();
@ -137,13 +148,35 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
Intent intent = getIntent(); Intent intent = getIntent();
String action = intent.getAction(); String action = intent.getAction();
if (intent != null && action != null && (action.equals(Intents.Scan.ACTION) || String dataString = intent.getDataString();
action.equals(Intents.Scan.DEPRECATED_ACTION))) { if (intent != null && action != null) {
mScanIntent = true; if (action.equals(Intents.Scan.ACTION) || action.equals(Intents.Scan.DEPRECATED_ACTION)) {
mDecodeMode = intent.getStringExtra(Intents.Scan.MODE); // Scan the formats the intent requested, and return the result to the calling activity.
resetStatusView(); 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 { } else {
mScanIntent = false; mSource = Source.NONE;
mDecodeMode = null; mDecodeMode = null;
if (mLastResult == null) { if (mLastResult == null) {
resetStatusView(); resetStatusView();
@ -170,11 +203,11 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
@Override @Override
public boolean onKeyDown(int keyCode, KeyEvent event) { public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) { if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mScanIntent) { if (mSource == Source.NATIVE_APP_INTENT) {
setResult(RESULT_CANCELED); setResult(RESULT_CANCELED);
finish(); finish();
return true; return true;
} else if (mLastResult != null) { } else if ((mSource == Source.NONE || mSource == Source.ZXING_LINK) && mLastResult != null) {
resetStatusView(); resetStatusView();
mHandler.sendEmptyMessage(R.id.restart_preview); mHandler.sendEmptyMessage(R.id.restart_preview);
return true; return true;
@ -230,7 +263,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
} }
case ABOUT_ID: case ABOUT_ID:
AlertDialog.Builder builder = new AlertDialog.Builder(this); 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.setMessage(getString(R.string.msg_about) + "\n\n" + getString(R.string.zxing_url));
builder.setIcon(R.drawable.zxing_icon); builder.setIcon(R.drawable.zxing_icon);
builder.setPositiveButton(R.string.button_open_browser, mAboutListener); 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 rawResult The contents of the barcode.
* @param barcode A greyscale bitmap of the camera data which was decoded. * @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; mLastResult = rawResult;
playBeepSoundAndVibrate(); playBeepSoundAndVibrate();
drawResultPoints(barcode, rawResult); drawResultPoints(barcode, rawResult);
if (mScanIntent) { switch (mSource) {
handleDecodeForScanIntent(rawResult, barcode, duration); case NATIVE_APP_INTENT:
} else { case PRODUCT_SEARCH_LINK:
mStatusView.setVisibility(View.GONE); handleDecodeExternally(rawResult, barcode);
mViewfinderView.setVisibility(View.GONE); break;
mResultView.setVisibility(View.VISIBLE); case ZXING_LINK:
case NONE:
ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view); handleDecodeInternally(rawResult, barcode);
barcodeImageView.setMaxWidth(MAX_RESULT_IMAGE_SIZE); break;
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);
}
} }
} }
@ -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); mViewfinderView.drawResultBitmap(barcode);
// Since this message will only be shown for a second, just tell the user what kind of // 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()); clipboard.setText(resultHandler.getDisplayContents());
} }
// Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when if (mSource == Source.NATIVE_APP_INTENT) {
// the deprecated intent is retired. // Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
Intent intent = new Intent(getIntent().getAction()); // the deprecated intent is retired.
intent.putExtra(Intents.Scan.RESULT, rawResult.toString()); Intent intent = new Intent(getIntent().getAction());
intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString()); intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
Message message = Message.obtain(mHandler, R.id.return_scan_result); intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
message.obj = intent; Message message = Message.obtain(mHandler, R.id.return_scan_result);
mHandler.sendMessageDelayed(message, INTENT_RESULT_DURATION); 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; int currentVersion = info.versionCode;
// Since we're paying to talk to the PackageManager anyway, it makes sense to cache the app // 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. // version name here for display in the about box later.
this.versionName = info.versionName; this.mVersionName = info.versionName;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
int lastVersion = prefs.getInt(PreferencesActivity.KEY_HELP_VERSION_SHOWN, 0); int lastVersion = prefs.getInt(PreferencesActivity.KEY_HELP_VERSION_SHOWN, 0);
if (currentVersion > lastVersion) { if (currentVersion > lastVersion) {

View file

@ -19,6 +19,7 @@ package com.google.zxing.client.android;
import android.app.Activity; import android.app.Activity;
import android.content.Intent; import android.content.Intent;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.net.Uri;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
@ -70,8 +71,7 @@ public final class CaptureActivityHandler extends Handler {
mState = State.SUCCESS; mState = State.SUCCESS;
Bundle bundle = message.getData(); Bundle bundle = message.getData();
Bitmap barcode = bundle.getParcelable(DecodeThread.BARCODE_BITMAP); Bitmap barcode = bundle.getParcelable(DecodeThread.BARCODE_BITMAP);
int duration = message.arg1; mActivity.handleDecode((Result) message.obj, barcode);
mActivity.handleDecode((Result) message.obj, barcode, duration);
break; break;
case R.id.decode_failed: case R.id.decode_failed:
// We're decoding as fast as possible, so when one decode fails, start another. // 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.setResult(Activity.RESULT_OK, (Intent) message.obj);
mActivity.finish(); mActivity.finish();
break; 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_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() {} private LocaleManager() {}
/** /**
@ -58,4 +65,20 @@ public final class LocaleManager {
return tld; 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))); 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) { public final void openProductSearch(String upc) {
Uri uri = Uri.parse("http://www.google." + LocaleManager.getCountryTLD() + "/products?q=" + Uri uri = Uri.parse("http://www.google." + LocaleManager.getProductSearchCountryTLD() +
upc); "/m/products?q=" + upc + "&source=zxing");
launchIntent(new Intent(Intent.ACTION_VIEW, uri)); launchIntent(new Intent(Intent.ACTION_VIEW, uri));
} }