diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index 18449af2f..ca5106f27 100755 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -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. --> + android:versionName="2.7" + android:versionCode="18"> + + + + + + + + + + + + + + + diff --git a/android/assets/html/whatsnew.html b/android/assets/html/whatsnew.html index 0b39b2f23..9b55eae1e 100644 --- a/android/assets/html/whatsnew.html +++ b/android/assets/html/whatsnew.html @@ -3,14 +3,14 @@

What's new in this version

-

New in version 2.6:

+

New in version 2.7:

    -
  • Added support for ITF format barcodes.
  • -
  • Fixed a bug where some URLs would be misinterpreted as email addresses.
  • -
  • Added support for uppercase URIs like TEL: and GEO:.
  • -
  • Prevented Barcode Scanner from crashing when launching an unhandled intent.
  • -
  • Web apps can now launch Barcode Scanner by creating a hyperlink to http://zxing.appspot.com/scan.
  • -
  • Added the version to the about box.
  • +
  • Searching for a product online now uses the new mobile version of Google Product Search, which + is formatted for small screens and loads quicker.
  • +
  • Two fixes in QR Code version detection.
  • +
  • Fixed encoding QR Codes of geo locations.
  • +
  • You can now encode a contact as a QR Code even if it doesn't have a name, i.e. just a phone + number.
\ No newline at end of file diff --git a/android/res/values/ids.xml b/android/res/values/ids.xml index 434d93e10..ea373de94 100755 --- a/android/res/values/ids.xml +++ b/android/res/values/ids.xml @@ -22,6 +22,7 @@ + diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index ac193b5bb..c6d06f86c 100755 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -79,7 +79,7 @@ Here\'s the contents of a barcode I scanned When a barcode is found\u2026 - Copy contents to clipboard + Copy to clipboard Decode 1D barcodes Decode QR Codes General settings diff --git a/android/src/com/google/zxing/client/android/CaptureActivity.java b/android/src/com/google/zxing/client/android/CaptureActivity.java index 34d8d2d0d..dc220e832 100755 --- a/android/src/com/google/zxing/client/android/CaptureActivity.java +++ b/android/src/com/google/zxing/client/android/CaptureActivity.java @@ -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) { diff --git a/android/src/com/google/zxing/client/android/CaptureActivityHandler.java b/android/src/com/google/zxing/client/android/CaptureActivityHandler.java index 34780f202..0736842ec 100755 --- a/android/src/com/google/zxing/client/android/CaptureActivityHandler.java +++ b/android/src/com/google/zxing/client/android/CaptureActivityHandler.java @@ -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; } } diff --git a/android/src/com/google/zxing/client/android/LocaleManager.java b/android/src/com/google/zxing/client/android/LocaleManager.java index c504be700..d30701b5a 100644 --- a/android/src/com/google/zxing/client/android/LocaleManager.java +++ b/android/src/com/google/zxing/client/android/LocaleManager.java @@ -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 GOOGLE_PRODUCT_SEARCH_COUNTRY_TLD; + static { + GOOGLE_PRODUCT_SEARCH_COUNTRY_TLD = new HashMap(); + 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; + } + } diff --git a/android/src/com/google/zxing/client/android/result/ResultHandler.java b/android/src/com/google/zxing/client/android/result/ResultHandler.java index 63f380c88..5c150feae 100644 --- a/android/src/com/google/zxing/client/android/result/ResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/ResultHandler.java @@ -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)); }