From: srowen Date: Mon, 21 Sep 2009 18:03:29 +0000 (+0000) Subject: Add history feature; group some functionality into subpackages X-Git-Url: http://git.rot13.org/?a=commitdiff_plain;h=2bb349fdc9b83b812c888174844dbcab27897005;hp=72e94a715994a3e001eb85d6db8d82f2e7e29084;p=zxing.git Add history feature; group some functionality into subpackages git-svn-id: http://zxing.googlecode.com/svn/trunk@1058 59b500cc-1b3d-0410-9834-0bbf25fbcc57 --- diff --git a/android/res/values-ja-rJP/strings.xml b/android/res/values-ja-rJP/strings.xml index 46a61262..0daf9f78 100644 --- a/android/res/values-ja-rJP/strings.xml +++ b/android/res/values-ja-rJP/strings.xml @@ -53,6 +53,7 @@ 詳細 ヘルプ 設定 + 歴史 共有 オープンソースのバーコード ライブラリ、ZXing を使用しています @@ -102,6 +103,9 @@ テキストデータがヒットしました URL がヒットしました + 歴史 + 削除履歴 + Google ブックス バーコードで共有する diff --git a/android/res/values-zh-rCN/strings.xml b/android/res/values-zh-rCN/strings.xml index afae40f9..acd4cb78 100644 --- a/android/res/values-zh-rCN/strings.xml +++ b/android/res/values-zh-rCN/strings.xml @@ -49,6 +49,7 @@ 关于 帮助 设置 + 历史 分享 以ZXing的开源条码库为基础 内容 @@ -87,6 +88,8 @@ 找到电话号码 找到纯文本 找到URL + 历史 + 删除历史 Google图书搜索 通过条码分享 条码扫描器 diff --git a/android/res/values-zh-rTW/strings.xml b/android/res/values-zh-rTW/strings.xml index d9985303..ff28850e 100644 --- a/android/res/values-zh-rTW/strings.xml +++ b/android/res/values-zh-rTW/strings.xml @@ -49,6 +49,7 @@ 關于 幫助 設置 + 歷史 分享 以ZXing的開源條碼庫為基礎 內容 @@ -87,6 +88,8 @@ 找到電話號碼 找到純文本 找到URL + 歷史 + 刪除歷史 Google圖書搜索 通過條碼分享 條碼掃描器 diff --git a/android/res/values/strings.xml b/android/res/values/strings.xml index 740857b3..af76d6c2 100755 --- a/android/res/values/strings.xml +++ b/android/res/values/strings.xml @@ -53,6 +53,7 @@ About Help Settings + History Share Based on the open source ZXing Barcode Library @@ -103,6 +104,9 @@ Found plain text Found URL + History + Clear history + Google Book Search Share via barcode diff --git a/android/src/com/google/zxing/client/android/BaseLuminanceSource.java b/android/src/com/google/zxing/client/android/BaseLuminanceSource.java index 523c59af..fda6a84d 100644 --- a/android/src/com/google/zxing/client/android/BaseLuminanceSource.java +++ b/android/src/com/google/zxing/client/android/BaseLuminanceSource.java @@ -26,7 +26,8 @@ import android.graphics.Bitmap; * @author dswitkin@google.com (Daniel Switkin) */ public abstract class BaseLuminanceSource extends LuminanceSource { - public BaseLuminanceSource(int width, int height) { + + BaseLuminanceSource(int width, int height) { super(width, height); } diff --git a/android/src/com/google/zxing/client/android/BookmarkPickerActivity.java b/android/src/com/google/zxing/client/android/BookmarkPickerActivity.java deleted file mode 100644 index 537ed7f4..00000000 --- a/android/src/com/google/zxing/client/android/BookmarkPickerActivity.java +++ /dev/null @@ -1,79 +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; - -import android.app.ListActivity; -import android.content.Intent; -import android.database.Cursor; -import android.os.Bundle; -import android.provider.Browser; -import android.view.View; -import android.widget.ListAdapter; -import android.widget.ListView; -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 - }; - - private static final int[] TWO_LINE_VIEW_IDS = { - R.id.bookmark_title, - R.id.bookmark_url - }; - - private static final int TITLE_COLUMN = 0; - private static final int URL_COLUMN = 1; - - // Without this selection, we'd get all the history entries too - private static final String BOOKMARK_SELECTION = "bookmark = 1"; - - private Cursor cursor; - - @Override - protected void onCreate(Bundle icicle) { - super.onCreate(icicle); - - cursor = getContentResolver().query(Browser.BOOKMARKS_URI, BOOKMARK_PROJECTION, - BOOKMARK_SELECTION, null, null); - startManagingCursor(cursor); - - ListAdapter adapter = new SimpleCursorAdapter(this, R.layout.bookmark_picker_list_item, - cursor, BOOKMARK_PROJECTION, TWO_LINE_VIEW_IDS); - setListAdapter(adapter); - } - - @Override - protected void onListItemClick(ListView l, View view, int position, long id) { - if (cursor.moveToPosition(position)) { - Intent intent = new Intent(); - 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(); - } -} diff --git a/android/src/com/google/zxing/client/android/CaptureActivity.java b/android/src/com/google/zxing/client/android/CaptureActivity.java index 336ed400..bc56bd80 100755 --- a/android/src/com/google/zxing/client/android/CaptureActivity.java +++ b/android/src/com/google/zxing/client/android/CaptureActivity.java @@ -21,6 +21,8 @@ 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 com.google.zxing.client.android.history.HistoryManager; +import com.google.zxing.client.android.share.ShareActivity; import android.app.Activity; import android.app.AlertDialog; @@ -42,6 +44,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Message; import android.os.Vibrator; +import android.os.Handler; import android.preference.PreferenceManager; import android.text.ClipboardManager; import android.text.SpannableStringBuilder; @@ -73,9 +76,10 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal private static final String TAG = "CaptureActivity"; private static final int SHARE_ID = Menu.FIRST; - private static final int SETTINGS_ID = Menu.FIRST + 1; - private static final int HELP_ID = Menu.FIRST + 2; - private static final int ABOUT_ID = Menu.FIRST + 3; + private static final int HISTORY_ID = Menu.FIRST + 1; + private static final int SETTINGS_ID = Menu.FIRST + 2; + private static final int HELP_ID = Menu.FIRST + 3; + private static final int ABOUT_ID = Menu.FIRST + 4; private static final int MAX_RESULT_IMAGE_SIZE = 150; private static final long INTENT_RESULT_DURATION = 1500L; @@ -94,7 +98,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal NONE } - public CaptureActivityHandler handler; + private CaptureActivityHandler handler; private ViewfinderView viewfinderView; private View statusView; @@ -109,6 +113,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal private String sourceUrl; private String decodeMode; private String versionName; + private HistoryManager historyManager; private final OnCompletionListener beepListener = new BeepListener(); @@ -120,6 +125,10 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal } }; + public Handler getHandler() { + return handler; + } + @Override public void onCreate(Bundle icicle) { Log.i(TAG, "Creating CaptureActivity"); @@ -136,6 +145,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal handler = null; lastResult = null; hasSurface = false; + historyManager = new HistoryManager(this); showHelpOnFirstLaunch(); } @@ -233,6 +243,7 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(0, SHARE_ID, 0, R.string.menu_share).setIcon(R.drawable.share_menu_item); + menu.add(0, HISTORY_ID, 0, R.string.menu_history).setIcon(android.R.drawable.ic_menu_recent_history); menu.add(0, SETTINGS_ID, 0, R.string.menu_settings) .setIcon(android.R.drawable.ic_menu_preferences); menu.add(0, HELP_ID, 0, R.string.menu_help) @@ -259,6 +270,11 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal startActivity(intent); break; } + case HISTORY_ID: { + AlertDialog historyAlert = historyManager.buildAlert(); + historyAlert.show(); + break; + } case SETTINGS_ID: { Intent intent = new Intent(Intent.ACTION_VIEW); intent.setClassName(this, PreferencesActivity.class.getName()); @@ -313,18 +329,22 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal */ public void handleDecode(Result rawResult, Bitmap barcode) { lastResult = rawResult; - playBeepSoundAndVibrate(); - drawResultPoints(barcode, rawResult); - - switch (source) { - case NATIVE_APP_INTENT: - case PRODUCT_SEARCH_LINK: - handleDecodeExternally(rawResult, barcode); - break; - case ZXING_LINK: - case NONE: - handleDecodeInternally(rawResult, barcode); - break; + historyManager.addHistoryItem(rawResult.getText()); + if (barcode != null) { + playBeepSoundAndVibrate(); + drawResultPoints(barcode, rawResult); + switch (source) { + case NATIVE_APP_INTENT: + case PRODUCT_SEARCH_LINK: + handleDecodeExternally(rawResult, barcode); + break; + case ZXING_LINK: + case NONE: + handleDecodeInternally(rawResult, barcode); + break; + } + } else { + handleDecodeInternally(rawResult, null); } } @@ -366,13 +386,23 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal resultView.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); + if (barcode == null) { + barcodeImageView.setVisibility(View.GONE); + } else { + barcodeImageView.setVisibility(View.VISIBLE); + 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()); + if (rawResult.getBarcodeFormat() == null) { + formatTextView.setVisibility(View.GONE); + } else { + formatTextView.setVisibility(View.VISIBLE); + 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); diff --git a/android/src/com/google/zxing/client/android/CaptureActivityHandler.java b/android/src/com/google/zxing/client/android/CaptureActivityHandler.java index 1552f169..ab241c78 100755 --- a/android/src/com/google/zxing/client/android/CaptureActivityHandler.java +++ b/android/src/com/google/zxing/client/android/CaptureActivityHandler.java @@ -72,13 +72,13 @@ public final class CaptureActivityHandler extends Handler { case R.id.decode_succeeded: state = State.SUCCESS; Bundle bundle = message.getData(); - Bitmap barcode = bundle.getParcelable(DecodeThread.BARCODE_BITMAP); + Bitmap barcode = bundle == null ? null : (Bitmap) bundle.getParcelable(DecodeThread.BARCODE_BITMAP); 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. state = State.PREVIEW; - CameraManager.get().requestPreviewFrame(decodeThread.handler, R.id.decode); + CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode); break; case R.id.return_scan_result: activity.setResult(Activity.RESULT_OK, (Intent) message.obj); @@ -94,7 +94,7 @@ public final class CaptureActivityHandler extends Handler { public void quitSynchronously() { state = State.DONE; CameraManager.get().stopPreview(); - Message quit = Message.obtain(decodeThread.handler, R.id.quit); + Message quit = Message.obtain(decodeThread.getHandler(), R.id.quit); quit.sendToTarget(); try { decodeThread.join(); @@ -109,7 +109,7 @@ public final class CaptureActivityHandler extends Handler { private void restartPreviewAndDecode() { if (state == State.SUCCESS) { state = State.PREVIEW; - CameraManager.get().requestPreviewFrame(decodeThread.handler, R.id.decode); + CameraManager.get().requestPreviewFrame(decodeThread.getHandler(), R.id.decode); CameraManager.get().requestAutoFocus(this, R.id.auto_focus); activity.drawViewfinder(); } diff --git a/android/src/com/google/zxing/client/android/DecodeThread.java b/android/src/com/google/zxing/client/android/DecodeThread.java index bd2ade6d..1f8de723 100755 --- a/android/src/com/google/zxing/client/android/DecodeThread.java +++ b/android/src/com/google/zxing/client/android/DecodeThread.java @@ -44,7 +44,7 @@ final class DecodeThread extends Thread { public static final String BARCODE_BITMAP = "barcode_bitmap"; private static final String TAG = "DecodeThread"; - public Handler handler; + private Handler handler; private final CaptureActivity activity; private final MultiFormatReader multiFormatReader; @@ -77,6 +77,10 @@ final class DecodeThread extends Thread { } } + Handler getHandler() { + return handler; + } + @Override public void run() { Looper.prepare(); @@ -175,13 +179,13 @@ final class DecodeThread extends Thread { if (success) { Log.v(TAG, "Found barcode (" + (end - start) + " ms):\n" + rawResult.toString()); - Message message = Message.obtain(activity.handler, R.id.decode_succeeded, rawResult); + Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded, rawResult); Bundle bundle = new Bundle(); bundle.putParcelable(BARCODE_BITMAP, source.renderCroppedGreyscaleBitmap()); message.setData(bundle); message.sendToTarget(); } else { - Message message = Message.obtain(activity.handler, R.id.decode_failed); + Message message = Message.obtain(activity.getHandler(), R.id.decode_failed); message.sendToTarget(); } } diff --git a/android/src/com/google/zxing/client/android/EncodeActivity.java b/android/src/com/google/zxing/client/android/EncodeActivity.java deleted file mode 100755 index 0584697c..00000000 --- a/android/src/com/google/zxing/client/android/EncodeActivity.java +++ /dev/null @@ -1,139 +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; - -import android.app.Activity; -import android.app.AlertDialog; -import android.app.ProgressDialog; -import android.content.DialogInterface; -import android.content.DialogInterface.OnCancelListener; -import android.content.DialogInterface.OnClickListener; -import android.content.Intent; -import android.graphics.Bitmap; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.view.View; -import android.view.ViewTreeObserver.OnGlobalLayoutListener; -import android.widget.ImageView; -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; - - /** - * 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))) { - setContentView(R.layout.encode); - } else { - finish(); - } - } - - @Override - protected void onResume() { - super.onResume(); - - View layout = findViewById(R.id.encode_view); - layout.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener); - firstLayout = true; - } - - private void showErrorMessage(int message) { - if (progressDialog != null) { - progressDialog.dismiss(); - progressDialog = null; - } - AlertDialog.Builder builder = new AlertDialog.Builder(this); - builder.setMessage(message); - builder.setPositiveButton(R.string.button_ok, clickListener); - builder.show(); - } -} diff --git a/android/src/com/google/zxing/client/android/QRCodeEncoder.java b/android/src/com/google/zxing/client/android/QRCodeEncoder.java deleted file mode 100755 index 4a58a589..00000000 --- a/android/src/com/google/zxing/client/android/QRCodeEncoder.java +++ /dev/null @@ -1,250 +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; - -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; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.provider.Contacts; -import android.telephony.PhoneNumberUtils; -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 activity; - private String contents; - private String displayContents; - private String title; - private BarcodeFormat format; - - public QRCodeEncoder(Activity activity, Intent intent) { - 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(contents, handler, pixelResolution, - format); - encodeThread.start(); - } - - public String getContents() { - return contents; - } - - public String getDisplayContents() { - return displayContents; - } - - public String getTitle() { - return title; - } - - public String getFormat() { - return format.toString(); - } - - // It would be nice if the string encoding lived in the core ZXing library, - // but we use platform specific code like PhoneNumberUtils, so it can't. - private boolean encodeContents(Intent intent) { - if (intent == null) { - return false; - } - - // default to QR_CODE if no format given - String format = intent.getStringExtra(Intents.Encode.FORMAT); - if (format == null || format.length() == 0 || - format.equals(Contents.Format.QR_CODE)) { - String type = intent.getStringExtra(Intents.Encode.TYPE); - if (type == null || type.length() == 0) { - return false; - } - this.format = BarcodeFormat.QR_CODE; - encodeQRCodeContents(intent, type); - } else { - String data = intent.getStringExtra(Intents.Encode.DATA); - if (data != null && data.length() != 0) { - contents = data; - displayContents = data; - title = activity.getString(R.string.contents_text); - if (format.equals(Contents.Format.CODE_128)) - this.format = BarcodeFormat.CODE_128; - else if (format.equals(Contents.Format.CODE_39)) - this.format = BarcodeFormat.CODE_39; - else if (format.equals(Contents.Format.EAN_8)) - this.format = BarcodeFormat.EAN_8; - else if (format.equals(Contents.Format.EAN_13)) - this.format = BarcodeFormat.EAN_13; - else if (format.equals(Contents.Format.UPC_A)) - this.format = BarcodeFormat.UPC_A; - else if (format.equals(Contents.Format.UPC_E)) - this.format = BarcodeFormat.UPC_E; - } - } - 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) { - 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) { - 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) { - 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) { - 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); - if (bundle != null) { - StringBuilder newContents = new StringBuilder(); - StringBuilder newDisplayContents = new StringBuilder(); - newContents.append("MECARD:"); - String name = bundle.getString(Contacts.Intents.Insert.NAME); - if (name != null && name.length() > 0) { - newContents.append("N:").append(name).append(';'); - newDisplayContents.append(name); - } - String address = bundle.getString(Contacts.Intents.Insert.POSTAL); - if (address != null && address.length() > 0) { - newContents.append("ADR:").append(address).append(';'); - newDisplayContents.append('\n').append(address); - } - for (int x = 0; x < Contents.PHONE_KEYS.length; x++) { - String phone = bundle.getString(Contents.PHONE_KEYS[x]); - if (phone != null && phone.length() > 0) { - newContents.append("TEL:").append(phone).append(';'); - newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone)); - } - } - for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) { - String email = bundle.getString(Contents.EMAIL_KEYS[x]); - if (email != null && email.length() > 0) { - newContents.append("EMAIL:").append(email).append(';'); - newDisplayContents.append('\n').append(email); - } - } - // Make sure we've encoded at least one field. - if (newDisplayContents.length() > 0) { - newContents.append(';'); - contents = newContents.toString(); - displayContents = newDisplayContents.toString(); - title = activity.getString(R.string.contents_contact); - } else { - contents = null; - displayContents = null; - } - } - } else if (type.equals(Contents.Type.LOCATION)) { - Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA); - if (bundle != null) { - // These must use Bundle.getFloat(), not getDouble(), it's part of the API. - 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) { - 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 contents; - private final Handler handler; - private final int pixelResolution; - private final BarcodeFormat format; - - EncodeThread(String contents, Handler handler, int pixelResolution, - BarcodeFormat format) { - this.contents = contents; - this.handler = handler; - this.pixelResolution = pixelResolution; - this.format = format; - } - - @Override - public void run() { - try { - ByteMatrix result = new MultiFormatWriter().encode(contents, format, - pixelResolution, pixelResolution); - int width = result.getWidth(); - int height = result.getHeight(); - byte[][] array = result.getArray(); - int[] pixels = new int[width * height]; - for (int y = 0; y < height; y++) { - for (int x = 0; x < width; x++) { - int grey = array[y][x] & 0xff; - // pixels[y * width + x] = (0xff << 24) | (grey << 16) | (grey << 8) | grey; - pixels[y * width + x] = 0xff000000 | (0x00010101 * grey); - } - } - - Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - bitmap.setPixels(pixels, 0, width, 0, 0, width, height); - 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(handler, R.id.encode_failed); - message.sendToTarget(); - } catch (IllegalArgumentException e) { - Log.e(TAG, e.toString()); - Message message = Message.obtain(handler, R.id.encode_failed); - message.sendToTarget(); - } - } - } -} diff --git a/android/src/com/google/zxing/client/android/SearchBookContentsActivity.java b/android/src/com/google/zxing/client/android/SearchBookContentsActivity.java deleted file mode 100644 index b84c73d4..00000000 --- a/android/src/com/google/zxing/client/android/SearchBookContentsActivity.java +++ /dev/null @@ -1,321 +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; - -import android.app.Activity; -import android.content.Intent; -import android.content.res.Configuration; -import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.util.Log; -import android.view.KeyEvent; -import android.view.LayoutInflater; -import android.view.View; -import android.webkit.CookieManager; -import android.webkit.CookieSyncManager; -import android.widget.Button; -import android.widget.EditText; -import android.widget.ListView; -import android.widget.TextView; -import org.apache.http.Header; -import org.apache.http.HttpEntity; -import org.apache.http.HttpResponse; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpHead; -import org.apache.http.client.methods.HttpUriRequest; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -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 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) { - super.onCreate(icicle); - - // Make sure that expired cookies are removed on launch. - CookieSyncManager.createInstance(this); - CookieManager.getInstance().removeExpiredCookie(); - - Intent intent = getIntent(); - if (intent == null || (!intent.getAction().equals(Intents.SearchBookContents.ACTION))) { - finish(); - return; - } - - isbn = intent.getStringExtra(Intents.SearchBookContents.ISBN); - setTitle(getString(R.string.sbc_name) + ": ISBN " + isbn); - - setContentView(R.layout.search_book_contents); - 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 - queryTextView.setText(initialQuery); - } - queryTextView.setOnKeyListener(keyListener); - - queryButton = (Button) findViewById(R.id.query_button); - queryButton.setOnClickListener(buttonListener); - - resultListView = (ListView) findViewById(R.id.result_list_view); - LayoutInflater factory = LayoutInflater.from(this); - headerView = (TextView) factory.inflate(R.layout.search_book_contents_header, - resultListView, false); - resultListView.addHeaderView(headerView); - } - - @Override - protected void onResume() { - super.onResume(); - queryTextView.selectAll(); - } - - @Override - public void onConfigurationChanged(Configuration config) { - // Do nothing, this is to prevent the activity from being restarted when the keyboard opens. - super.onConfigurationChanged(config); - } - - private void resetForNewQuery() { - networkThread = null; - queryTextView.setEnabled(true); - queryTextView.selectAll(); - queryButton.setEnabled(true); - } - - private void launchSearch() { - if (networkThread == null) { - String query = queryTextView.getText().toString(); - if (query != null && query.length() > 0) { - 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); - } - } - } - - // Currently there is no way to distinguish between a query which had no results and a book - // which is not searchable - both return zero results. - private void handleSearchResults(JSONObject json) { - try { - int count = json.getInt("number_of_results"); - headerView.setText("Found " + (count == 1 ? "1 result" : count + " results")); - if (count > 0) { - JSONArray results = json.getJSONArray("search_results"); - SearchBookContentsResult.setQuery(queryTextView.getText().toString()); - List items = new ArrayList(count); - for (int x = 0; x < count; x++) { - items.add(parseResult(results.getJSONObject(x))); - } - resultListView.setAdapter(new SearchBookContentsAdapter(this, items)); - } else { - String searchable = json.optString("searchable"); - if ("false".equals(searchable)) { - headerView.setText(R.string.msg_sbc_book_not_searchable); - } - resultListView.setAdapter(null); - } - } catch (JSONException e) { - Log.e(TAG, e.toString()); - resultListView.setAdapter(null); - headerView.setText(R.string.msg_sbc_failed); - } - } - - // Available fields: page_number, page_id, page_url, snippet_text - private SearchBookContentsResult parseResult(JSONObject json) { - try { - String pageNumber = json.getString("page_number"); - if (pageNumber.length() > 0) { - pageNumber = getString(R.string.msg_sbc_page) + ' ' + pageNumber; - } else { - // This can happen for text on the jacket, and possibly other reasons. - pageNumber = getString(R.string.msg_sbc_unknown_page); - } - - // Remove all HTML tags and encoded characters. Ideally the server would do this. - String snippet = json.optString("snippet_text"); - boolean valid = true; - if (snippet.length() > 0) { - snippet = snippet.replaceAll("\\<.*?\\>", ""); - snippet = snippet.replaceAll("<", "<"); - snippet = snippet.replaceAll(">", ">"); - snippet = snippet.replaceAll("'", "'"); - snippet = snippet.replaceAll(""", "\""); - } else { - snippet = '(' + getString(R.string.msg_sbc_snippet_unavailable) + ')'; - valid = false; - } - return new SearchBookContentsResult(pageNumber, snippet, valid); - } catch (JSONException e) { - // Never seen in the wild, just being complete. - return new SearchBookContentsResult(getString(R.string.msg_sbc_no_page_returned), "", false); - } - } - - private static final class NetworkThread extends Thread { - private final String isbn; - private final String query; - private final Handler handler; - - NetworkThread(String isbn, String query, Handler handler) { - this.isbn = isbn; - this.query = query; - this.handler = handler; - } - - @Override - public void run() { - AndroidHttpClient client = null; - try { - // 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" + isbn + - "&jscmd=SearchWithinVolume2&q=" + query, null); - HttpUriRequest get = new HttpGet(uri); - get.setHeader("cookie", getCookie(uri.toString())); - client = AndroidHttpClient.newInstance(USER_AGENT); - HttpResponse response = client.execute(get); - if (response.getStatusLine().getStatusCode() == 200) { - HttpEntity entity = response.getEntity(); - ByteArrayOutputStream jsonHolder = new ByteArrayOutputStream(); - entity.writeTo(jsonHolder); - jsonHolder.flush(); - JSONObject json = new JSONObject(jsonHolder.toString(getEncoding(entity))); - jsonHolder.close(); - - 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(handler, R.id.search_book_contents_failed); - message.sendToTarget(); - } - } catch (Exception e) { - Log.e(TAG, e.toString()); - Message message = Message.obtain(handler, R.id.search_book_contents_failed); - message.sendToTarget(); - } finally { - if (client != null) { - client.close(); - } - } - } - - // Book Search requires a cookie to work, which we store persistently. If the cookie does - // not exist, this could be the first search or it has expired. Either way, do a quick HEAD - // request to fetch it, save it via the CookieSyncManager to flash, then return it. - private String getCookie(String url) { - String cookie = CookieManager.getInstance().getCookie(url); - if (cookie == null || cookie.length() == 0) { - Log.v(TAG, "Book Search cookie was missing or expired"); - HttpUriRequest head = new HttpHead(url); - AndroidHttpClient client = AndroidHttpClient.newInstance(USER_AGENT); - try { - HttpResponse response = client.execute(head); - if (response.getStatusLine().getStatusCode() == 200) { - Header[] cookies = response.getHeaders("set-cookie"); - for (Header theCookie : cookies) { - CookieManager.getInstance().setCookie(url, theCookie.getValue()); - } - CookieSyncManager.getInstance().sync(); - cookie = CookieManager.getInstance().getCookie(url); - } - } catch (IOException e) { - Log.e(TAG, e.toString()); - } - client.close(); - } - return cookie; - } - - private static String getEncoding(HttpEntity entity) { - // FIXME: The server is returning ISO-8859-1 but the content is actually windows-1252. - // Once Jeff fixes the HTTP response, remove this hardcoded value and go back to getting - // the encoding dynamically. - return "windows-1252"; -// HeaderElement[] elements = entity.getContentType().getElements(); -// if (elements != null && elements.length > 0) { -// String encoding = elements[0].getParameterByName("charset").getValue(); -// if (encoding != null && encoding.length() > 0) { -// return encoding; -// } -// } -// return "UTF-8"; - } - } -} diff --git a/android/src/com/google/zxing/client/android/SearchBookContentsAdapter.java b/android/src/com/google/zxing/client/android/SearchBookContentsAdapter.java deleted file mode 100644 index 4f9e4c3f..00000000 --- a/android/src/com/google/zxing/client/android/SearchBookContentsAdapter.java +++ /dev/null @@ -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; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -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 { - public SearchBookContentsAdapter(Context context, List items) { - super(context, R.layout.search_book_contents_list_item, 0, items); - } - - @Override - public View getView(int position, View view, ViewGroup viewGroup) { - SearchBookContentsListItem listItem; - - if (view == null) { - LayoutInflater factory = LayoutInflater.from(getContext()); - listItem = (SearchBookContentsListItem) factory.inflate( - R.layout.search_book_contents_list_item, viewGroup, false); - } else { - if (view instanceof SearchBookContentsListItem) { - listItem = (SearchBookContentsListItem) view; - } else { - return view; - } - } - - SearchBookContentsResult result = getItem(position); - listItem.set(result); - return listItem; - } -} diff --git a/android/src/com/google/zxing/client/android/SearchBookContentsListItem.java b/android/src/com/google/zxing/client/android/SearchBookContentsListItem.java deleted file mode 100644 index 80d6cecb..00000000 --- a/android/src/com/google/zxing/client/android/SearchBookContentsListItem.java +++ /dev/null @@ -1,80 +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; - -import android.content.Context; -import android.graphics.Typeface; -import android.text.SpannableString; -import android.text.Spannable; -import android.text.style.StyleSpan; -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 pageNumberView; - private TextView snippetView; - - SearchBookContentsListItem(Context context) { - super(context); - } - - public SearchBookContentsListItem(Context context, AttributeSet attrs) { - super(context, attrs); - } - - @Override - protected void onFinishInflate() { - super.onFinishInflate(); - pageNumberView = (TextView) findViewById(R.id.page_number_view); - snippetView = (TextView) findViewById(R.id.snippet_view); - } - - public void set(SearchBookContentsResult result) { - pageNumberView.setText(result.getPageNumber()); - String snippet = result.getSnippet(); - if (snippet.length() > 0) { - if (result.getValidSnippet()) { - String lowerQuery = SearchBookContentsResult.getQuery().toLowerCase(); - String lowerSnippet = snippet.toLowerCase(); - Spannable styledSnippet = new SpannableString(snippet); - StyleSpan boldSpan = new StyleSpan(Typeface.BOLD); - int queryLength = lowerQuery.length(); - int offset = 0; - while (true) { - int pos = lowerSnippet.indexOf(lowerQuery, offset); - if (pos < 0) { - break; - } - styledSnippet.setSpan(boldSpan, pos, pos + queryLength, 0); - offset = pos + queryLength; - } - snippetView.setText(styledSnippet); - } else { - // This may be an error message, so don't try to bold the query terms within it - snippetView.setText(snippet); - } - } else { - snippetView.setText(""); - } - } -} diff --git a/android/src/com/google/zxing/client/android/SearchBookContentsResult.java b/android/src/com/google/zxing/client/android/SearchBookContentsResult.java deleted file mode 100644 index de561a76..00000000 --- a/android/src/com/google/zxing/client/android/SearchBookContentsResult.java +++ /dev/null @@ -1,56 +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; - -/** - * The underlying data for a SBC result. - * - * @author dswitkin@google.com (Daniel Switkin) - */ -public final class SearchBookContentsResult { - private static String query; - - private final String pageNumber; - private final String snippet; - private final boolean validSnippet; - - public SearchBookContentsResult(String pageNumber, String snippet, boolean validSnippet) { - this.pageNumber = pageNumber; - this.snippet = snippet; - this.validSnippet = validSnippet; - } - - public static void setQuery(String query) { - SearchBookContentsResult.query = query; - } - - public String getPageNumber() { - return pageNumber; - } - - public String getSnippet() { - return snippet; - } - - public boolean getValidSnippet() { - return validSnippet; - } - - public static String getQuery() { - return query; - } -} diff --git a/android/src/com/google/zxing/client/android/ShareActivity.java b/android/src/com/google/zxing/client/android/ShareActivity.java deleted file mode 100755 index db302dd7..00000000 --- a/android/src/com/google/zxing/client/android/ShareActivity.java +++ /dev/null @@ -1,221 +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; - -import android.app.Activity; -import android.content.ContentResolver; -import android.content.Intent; -import android.database.Cursor; -import android.net.Uri; -import android.os.Bundle; -import android.provider.Browser; -import android.provider.Contacts; -import android.provider.BaseColumns; -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; - - //private static final int METHODS_ID_COLUMN = 0; - private static final int METHODS_KIND_COLUMN = 1; - private static final int METHODS_DATA_COLUMN = 2; - - private static final String[] METHODS_PROJECTION = { - BaseColumns._ID, // 0 - Contacts.ContactMethodsColumns.KIND, // 1 - Contacts.ContactMethodsColumns.DATA, // 2 - }; - - private static final int PHONES_NUMBER_COLUMN = 1; - - private static final String[] PHONES_PROJECTION = { - BaseColumns._ID, // 0 - Contacts.PhonesColumns.NUMBER // 1 - }; - - private Button clipboardButton; - - 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 bookmarkListener = new Button.OnClickListener() { - public void onClick(View v) { - Intent intent = new Intent(Intent.ACTION_PICK); - intent.setClassName(ShareActivity.this, BookmarkPickerActivity.class.getName()); - startActivityForResult(intent, PICK_BOOKMARK); - } - }; - - 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 - if (clipboard.hasText()) { - Intent intent = new Intent(Intents.Encode.ACTION); - intent.putExtra(Intents.Encode.TYPE, Contents.Type.TEXT); - intent.putExtra(Intents.Encode.DATA, clipboard.getText()); - intent.putExtra(Intents.Encode.FORMAT, Contents.Format.QR_CODE); - startActivity(intent); - } - } - }; - - @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) { - switch (requestCode) { - case PICK_BOOKMARK: - showTextAsBarcode(intent.getStringExtra(Browser.BookmarkColumns.URL)); - break; - case PICK_CONTACT: - // Data field is content://contacts/people/984 - showContactAsBarcode(intent.getData()); - break; - } - } - } - - private void showTextAsBarcode(String text) { - Intent intent = new Intent(Intents.Encode.ACTION); - intent.putExtra(Intents.Encode.TYPE, Contents.Type.TEXT); - intent.putExtra(Intents.Encode.DATA, text); - intent.putExtra(Intents.Encode.FORMAT, Contents.Format.QR_CODE); - startActivity(intent); - } - - /** - * Takes a contact Uri and does the necessary database lookups to retrieve that person's info, - * then sends an Encode intent to render it as a QR Code. - * - * @param contactUri A Uri of the form content://contacts/people/17 - */ - private void showContactAsBarcode(Uri contactUri) { - ContentResolver resolver = getContentResolver(); - Cursor contactCursor = resolver.query(contactUri, null, null, null, null); - Bundle bundle = new Bundle(); - if (contactCursor != null && contactCursor.moveToFirst()) { - int nameColumn = contactCursor.getColumnIndex(Contacts.PeopleColumns.NAME); - String name = contactCursor.getString(nameColumn); - - // Don't require a name to be present, this contact might be just a phone number. - if (name != null && name.length() > 0) { - bundle.putString(Contacts.Intents.Insert.NAME, massageContactData(name)); - } - contactCursor.close(); - - Uri phonesUri = Uri.withAppendedPath(contactUri, Contacts.People.Phones.CONTENT_DIRECTORY); - Cursor phonesCursor = resolver.query(phonesUri, PHONES_PROJECTION, null, null, null); - if (phonesCursor != null) { - int foundPhone = 0; - while (phonesCursor.moveToNext()) { - String number = phonesCursor.getString(PHONES_NUMBER_COLUMN); - if (foundPhone < Contents.PHONE_KEYS.length) { - bundle.putString(Contents.PHONE_KEYS[foundPhone], massageContactData(number)); - foundPhone++; - } - } - phonesCursor.close(); - } - - Uri methodsUri = Uri.withAppendedPath(contactUri, - Contacts.People.ContactMethods.CONTENT_DIRECTORY); - Cursor methodsCursor = resolver.query(methodsUri, METHODS_PROJECTION, null, null, null); - if (methodsCursor != null) { - int foundEmail = 0; - boolean foundPostal = false; - while (methodsCursor.moveToNext()) { - int kind = methodsCursor.getInt(METHODS_KIND_COLUMN); - String data = methodsCursor.getString(METHODS_DATA_COLUMN); - switch (kind) { - case Contacts.KIND_EMAIL: - if (foundEmail < Contents.EMAIL_KEYS.length) { - bundle.putString(Contents.EMAIL_KEYS[foundEmail], massageContactData(data)); - foundEmail++; - } - break; - case Contacts.KIND_POSTAL: - if (!foundPostal) { - bundle.putString(Contacts.Intents.Insert.POSTAL, massageContactData(data)); - foundPostal = true; - } - break; - } - } - methodsCursor.close(); - } - - Intent intent = new Intent(Intents.Encode.ACTION); - intent.putExtra(Intents.Encode.TYPE, Contents.Type.CONTACT); - intent.putExtra(Intents.Encode.DATA, bundle); - intent.putExtra(Intents.Encode.FORMAT, Contents.Format.QR_CODE); - - startActivity(intent); - } - } - - private static String massageContactData(String data) { - // For now -- make sure we don't put newlines in shared contact data. It messes up - // any known encoding of contact data. Replace with space. - if (data.indexOf('\n') >= 0) { - data = data.replace("\n", " "); - } - if (data.indexOf('\r') >= 0) { - data = data.replace("\r", " "); - } - return data; - } -} diff --git a/android/src/com/google/zxing/client/android/book/SearchBookContentsActivity.java b/android/src/com/google/zxing/client/android/book/SearchBookContentsActivity.java new file mode 100644 index 00000000..a0de09c3 --- /dev/null +++ b/android/src/com/google/zxing/client/android/book/SearchBookContentsActivity.java @@ -0,0 +1,331 @@ +/* + * 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.book; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.webkit.CookieManager; +import android.webkit.CookieSyncManager; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; +import org.apache.http.Header; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpHead; +import org.apache.http.client.methods.HttpUriRequest; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Pattern; + +import com.google.zxing.client.android.R; +import com.google.zxing.client.android.Intents; +import com.google.zxing.client.android.AndroidHttpClient; + +/** + * 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 networkThread; + private String isbn; + private EditText queryTextView; + private Button queryButton; + private ListView resultListView; + private TextView headerView; + + private 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; + } + }; + private static final Pattern TAG_PATTERN = Pattern.compile("\\<.*?\\>"); + private static final Pattern LT_ENTITY_PATTERN = Pattern.compile("<"); + private static final Pattern GT_ENTITY_PATTERN = Pattern.compile(">"); + private static final Pattern QUOTE_ENTITY_PATTERN = Pattern.compile("'"); + private static final Pattern QUOT_ENTITY_PATTERN = Pattern.compile("""); + + @Override + public void onCreate(Bundle icicle) { + super.onCreate(icicle); + + // Make sure that expired cookies are removed on launch. + CookieSyncManager.createInstance(this); + CookieManager.getInstance().removeExpiredCookie(); + + Intent intent = getIntent(); + if (intent == null || (!intent.getAction().equals(Intents.SearchBookContents.ACTION))) { + finish(); + return; + } + + isbn = intent.getStringExtra(Intents.SearchBookContents.ISBN); + setTitle(getString(R.string.sbc_name) + ": ISBN " + isbn); + + setContentView(R.layout.search_book_contents); + 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 + queryTextView.setText(initialQuery); + } + queryTextView.setOnKeyListener(keyListener); + + queryButton = (Button) findViewById(R.id.query_button); + queryButton.setOnClickListener(buttonListener); + + resultListView = (ListView) findViewById(R.id.result_list_view); + LayoutInflater factory = LayoutInflater.from(this); + headerView = (TextView) factory.inflate(R.layout.search_book_contents_header, + resultListView, false); + resultListView.addHeaderView(headerView); + } + + @Override + protected void onResume() { + super.onResume(); + queryTextView.selectAll(); + } + + @Override + public void onConfigurationChanged(Configuration config) { + // Do nothing, this is to prevent the activity from being restarted when the keyboard opens. + super.onConfigurationChanged(config); + } + + private void resetForNewQuery() { + networkThread = null; + queryTextView.setEnabled(true); + queryTextView.selectAll(); + queryButton.setEnabled(true); + } + + private void launchSearch() { + if (networkThread == null) { + String query = queryTextView.getText().toString(); + if (query != null && query.length() > 0) { + 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); + } + } + } + + // Currently there is no way to distinguish between a query which had no results and a book + // which is not searchable - both return zero results. + private void handleSearchResults(JSONObject json) { + try { + int count = json.getInt("number_of_results"); + headerView.setText("Found " + (count == 1 ? "1 result" : count + " results")); + if (count > 0) { + JSONArray results = json.getJSONArray("search_results"); + SearchBookContentsResult.setQuery(queryTextView.getText().toString()); + List items = new ArrayList(count); + for (int x = 0; x < count; x++) { + items.add(parseResult(results.getJSONObject(x))); + } + resultListView.setAdapter(new SearchBookContentsAdapter(this, items)); + } else { + String searchable = json.optString("searchable"); + if ("false".equals(searchable)) { + headerView.setText(R.string.msg_sbc_book_not_searchable); + } + resultListView.setAdapter(null); + } + } catch (JSONException e) { + Log.e(TAG, e.toString()); + resultListView.setAdapter(null); + headerView.setText(R.string.msg_sbc_failed); + } + } + + // Available fields: page_number, page_id, page_url, snippet_text + private SearchBookContentsResult parseResult(JSONObject json) { + try { + String pageNumber = json.getString("page_number"); + if (pageNumber.length() > 0) { + pageNumber = getString(R.string.msg_sbc_page) + ' ' + pageNumber; + } else { + // This can happen for text on the jacket, and possibly other reasons. + pageNumber = getString(R.string.msg_sbc_unknown_page); + } + + // Remove all HTML tags and encoded characters. Ideally the server would do this. + String snippet = json.optString("snippet_text"); + boolean valid = true; + if (snippet.length() > 0) { + snippet = TAG_PATTERN.matcher(snippet).replaceAll(""); + snippet = LT_ENTITY_PATTERN.matcher(snippet).replaceAll("<"); + snippet = GT_ENTITY_PATTERN.matcher(snippet).replaceAll(">"); + snippet = QUOTE_ENTITY_PATTERN.matcher(snippet).replaceAll("'"); + snippet = QUOT_ENTITY_PATTERN.matcher(snippet).replaceAll("\""); + } else { + snippet = '(' + getString(R.string.msg_sbc_snippet_unavailable) + ')'; + valid = false; + } + return new SearchBookContentsResult(pageNumber, snippet, valid); + } catch (JSONException e) { + // Never seen in the wild, just being complete. + return new SearchBookContentsResult(getString(R.string.msg_sbc_no_page_returned), "", false); + } + } + + private static final class NetworkThread extends Thread { + private final String isbn; + private final String query; + private final Handler handler; + + NetworkThread(String isbn, String query, Handler handler) { + this.isbn = isbn; + this.query = query; + this.handler = handler; + } + + @Override + public void run() { + AndroidHttpClient client = null; + try { + // 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" + isbn + + "&jscmd=SearchWithinVolume2&q=" + query, null); + HttpUriRequest get = new HttpGet(uri); + get.setHeader("cookie", getCookie(uri.toString())); + client = AndroidHttpClient.newInstance(USER_AGENT); + HttpResponse response = client.execute(get); + if (response.getStatusLine().getStatusCode() == 200) { + HttpEntity entity = response.getEntity(); + ByteArrayOutputStream jsonHolder = new ByteArrayOutputStream(); + entity.writeTo(jsonHolder); + jsonHolder.flush(); + JSONObject json = new JSONObject(jsonHolder.toString(getEncoding(entity))); + jsonHolder.close(); + + 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(handler, R.id.search_book_contents_failed); + message.sendToTarget(); + } + } catch (Exception e) { + Log.e(TAG, e.toString()); + Message message = Message.obtain(handler, R.id.search_book_contents_failed); + message.sendToTarget(); + } finally { + if (client != null) { + client.close(); + } + } + } + + // Book Search requires a cookie to work, which we store persistently. If the cookie does + // not exist, this could be the first search or it has expired. Either way, do a quick HEAD + // request to fetch it, save it via the CookieSyncManager to flash, then return it. + private static String getCookie(String url) { + String cookie = CookieManager.getInstance().getCookie(url); + if (cookie == null || cookie.length() == 0) { + Log.v(TAG, "Book Search cookie was missing or expired"); + HttpUriRequest head = new HttpHead(url); + AndroidHttpClient client = AndroidHttpClient.newInstance(USER_AGENT); + try { + HttpResponse response = client.execute(head); + if (response.getStatusLine().getStatusCode() == 200) { + Header[] cookies = response.getHeaders("set-cookie"); + for (Header theCookie : cookies) { + CookieManager.getInstance().setCookie(url, theCookie.getValue()); + } + CookieSyncManager.getInstance().sync(); + cookie = CookieManager.getInstance().getCookie(url); + } + } catch (IOException e) { + Log.e(TAG, e.toString()); + } + client.close(); + } + return cookie; + } + + private static String getEncoding(HttpEntity entity) { + // FIXME: The server is returning ISO-8859-1 but the content is actually windows-1252. + // Once Jeff fixes the HTTP response, remove this hardcoded value and go back to getting + // the encoding dynamically. + return "windows-1252"; +// HeaderElement[] elements = entity.getContentType().getElements(); +// if (elements != null && elements.length > 0) { +// String encoding = elements[0].getParameterByName("charset").getValue(); +// if (encoding != null && encoding.length() > 0) { +// return encoding; +// } +// } +// return "UTF-8"; + } + } +} diff --git a/android/src/com/google/zxing/client/android/book/SearchBookContentsAdapter.java b/android/src/com/google/zxing/client/android/book/SearchBookContentsAdapter.java new file mode 100644 index 00000000..32fbb100 --- /dev/null +++ b/android/src/com/google/zxing/client/android/book/SearchBookContentsAdapter.java @@ -0,0 +1,59 @@ +/* + * 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.book; + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; + +import java.util.List; + +import com.google.zxing.client.android.R; + +/** + * Manufactures list items which represent SBC results. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +final class SearchBookContentsAdapter extends ArrayAdapter { + public SearchBookContentsAdapter(Context context, List items) { + super(context, R.layout.search_book_contents_list_item, 0, items); + } + + @Override + public View getView(int position, View view, ViewGroup viewGroup) { + SearchBookContentsListItem listItem; + + if (view == null) { + LayoutInflater factory = LayoutInflater.from(getContext()); + listItem = (SearchBookContentsListItem) factory.inflate( + R.layout.search_book_contents_list_item, viewGroup, false); + } else { + if (view instanceof SearchBookContentsListItem) { + listItem = (SearchBookContentsListItem) view; + } else { + return view; + } + } + + SearchBookContentsResult result = getItem(position); + listItem.set(result); + return listItem; + } +} diff --git a/android/src/com/google/zxing/client/android/book/SearchBookContentsListItem.java b/android/src/com/google/zxing/client/android/book/SearchBookContentsListItem.java new file mode 100644 index 00000000..0700b015 --- /dev/null +++ b/android/src/com/google/zxing/client/android/book/SearchBookContentsListItem.java @@ -0,0 +1,81 @@ +/* + * 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.book; + +import android.content.Context; +import android.graphics.Typeface; +import android.text.SpannableString; +import android.text.Spannable; +import android.text.style.StyleSpan; +import android.util.AttributeSet; +import android.widget.LinearLayout; +import android.widget.TextView; +import com.google.zxing.client.android.R; + +/** + * A list item which displays the page number and snippet of this search result. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +final class SearchBookContentsListItem extends LinearLayout { + private TextView pageNumberView; + private TextView snippetView; + + SearchBookContentsListItem(Context context) { + super(context); + } + + public SearchBookContentsListItem(Context context, AttributeSet attrs) { + super(context, attrs); + } + + @Override + protected void onFinishInflate() { + super.onFinishInflate(); + pageNumberView = (TextView) findViewById(R.id.page_number_view); + snippetView = (TextView) findViewById(R.id.snippet_view); + } + + public void set(SearchBookContentsResult result) { + pageNumberView.setText(result.getPageNumber()); + String snippet = result.getSnippet(); + if (snippet.length() > 0) { + if (result.getValidSnippet()) { + String lowerQuery = SearchBookContentsResult.getQuery().toLowerCase(); + String lowerSnippet = snippet.toLowerCase(); + Spannable styledSnippet = new SpannableString(snippet); + StyleSpan boldSpan = new StyleSpan(Typeface.BOLD); + int queryLength = lowerQuery.length(); + int offset = 0; + while (true) { + int pos = lowerSnippet.indexOf(lowerQuery, offset); + if (pos < 0) { + break; + } + styledSnippet.setSpan(boldSpan, pos, pos + queryLength, 0); + offset = pos + queryLength; + } + snippetView.setText(styledSnippet); + } else { + // This may be an error message, so don't try to bold the query terms within it + snippetView.setText(snippet); + } + } else { + snippetView.setText(""); + } + } +} diff --git a/android/src/com/google/zxing/client/android/book/SearchBookContentsResult.java b/android/src/com/google/zxing/client/android/book/SearchBookContentsResult.java new file mode 100644 index 00000000..8893739e --- /dev/null +++ b/android/src/com/google/zxing/client/android/book/SearchBookContentsResult.java @@ -0,0 +1,56 @@ +/* + * 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.book; + +/** + * The underlying data for a SBC result. + * + * @author dswitkin@google.com (Daniel Switkin) + */ +final class SearchBookContentsResult { + private static String query; + + private final String pageNumber; + private final String snippet; + private final boolean validSnippet; + + SearchBookContentsResult(String pageNumber, String snippet, boolean validSnippet) { + this.pageNumber = pageNumber; + this.snippet = snippet; + this.validSnippet = validSnippet; + } + + public static void setQuery(String query) { + SearchBookContentsResult.query = query; + } + + public String getPageNumber() { + return pageNumber; + } + + public String getSnippet() { + return snippet; + } + + public boolean getValidSnippet() { + return validSnippet; + } + + public static String getQuery() { + return query; + } +} diff --git a/android/src/com/google/zxing/client/android/encode/EncodeActivity.java b/android/src/com/google/zxing/client/android/encode/EncodeActivity.java new file mode 100755 index 00000000..9f50e471 --- /dev/null +++ b/android/src/com/google/zxing/client/android/encode/EncodeActivity.java @@ -0,0 +1,141 @@ +/* + * 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.encode; + +import android.app.Activity; +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.content.DialogInterface.OnCancelListener; +import android.content.DialogInterface.OnClickListener; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.view.View; +import android.view.ViewTreeObserver.OnGlobalLayoutListener; +import android.widget.ImageView; +import android.widget.TextView; +import com.google.zxing.client.android.R; +import com.google.zxing.client.android.Intents; + +/** + * 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) + */ +final class EncodeActivity extends Activity { + private QRCodeEncoder qrCodeEncoder; + private ProgressDialog progressDialog; + private boolean firstLayout; + + /** + * This needs to be delayed until after the first layout so that the view dimensions will be + * available. + */ + private 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; + } + } + }; + + private 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))) { + setContentView(R.layout.encode); + } else { + finish(); + } + } + + @Override + protected void onResume() { + super.onResume(); + + View layout = findViewById(R.id.encode_view); + layout.getViewTreeObserver().addOnGlobalLayoutListener(layoutListener); + firstLayout = true; + } + + private void showErrorMessage(int message) { + if (progressDialog != null) { + progressDialog.dismiss(); + progressDialog = null; + } + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setMessage(message); + builder.setPositiveButton(R.string.button_ok, clickListener); + builder.show(); + } +} diff --git a/android/src/com/google/zxing/client/android/encode/QRCodeEncoder.java b/android/src/com/google/zxing/client/android/encode/QRCodeEncoder.java new file mode 100755 index 00000000..2f4a573c --- /dev/null +++ b/android/src/com/google/zxing/client/android/encode/QRCodeEncoder.java @@ -0,0 +1,254 @@ +/* + * 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.encode; + +import com.google.zxing.BarcodeFormat; +import com.google.zxing.MultiFormatWriter; +import com.google.zxing.WriterException; +import com.google.zxing.client.android.Intents; +import com.google.zxing.client.android.Contents; +import com.google.zxing.client.android.R; +import com.google.zxing.common.ByteMatrix; + +import android.app.Activity; +import android.content.Intent; +import android.graphics.Bitmap; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.provider.Contacts; +import android.telephony.PhoneNumberUtils; +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) + */ +final class QRCodeEncoder { + private final Activity activity; + private String contents; + private String displayContents; + private String title; + private BarcodeFormat format; + + public QRCodeEncoder(Activity activity, Intent intent) { + 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(contents, handler, pixelResolution, + format); + encodeThread.start(); + } + + public String getContents() { + return contents; + } + + public String getDisplayContents() { + return displayContents; + } + + public String getTitle() { + return title; + } + + public String getFormat() { + return format.toString(); + } + + // It would be nice if the string encoding lived in the core ZXing library, + // but we use platform specific code like PhoneNumberUtils, so it can't. + private boolean encodeContents(Intent intent) { + if (intent == null) { + return false; + } + + // default to QR_CODE if no format given + String format = intent.getStringExtra(Intents.Encode.FORMAT); + if (format == null || format.length() == 0 || + format.equals(Contents.Format.QR_CODE)) { + String type = intent.getStringExtra(Intents.Encode.TYPE); + if (type == null || type.length() == 0) { + return false; + } + this.format = BarcodeFormat.QR_CODE; + encodeQRCodeContents(intent, type); + } else { + String data = intent.getStringExtra(Intents.Encode.DATA); + if (data != null && data.length() != 0) { + contents = data; + displayContents = data; + title = activity.getString(R.string.contents_text); + if (format.equals(Contents.Format.CODE_128)) { + this.format = BarcodeFormat.CODE_128; + } else if (format.equals(Contents.Format.CODE_39)) { + this.format = BarcodeFormat.CODE_39; + } else if (format.equals(Contents.Format.EAN_8)) { + this.format = BarcodeFormat.EAN_8; + } else if (format.equals(Contents.Format.EAN_13)) { + this.format = BarcodeFormat.EAN_13; + } else if (format.equals(Contents.Format.UPC_A)) { + this.format = BarcodeFormat.UPC_A; + } else if (format.equals(Contents.Format.UPC_E)) { + this.format = BarcodeFormat.UPC_E; + } + } + } + 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) { + 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) { + 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) { + 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) { + 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); + if (bundle != null) { + StringBuilder newContents = new StringBuilder(); + StringBuilder newDisplayContents = new StringBuilder(); + newContents.append("MECARD:"); + String name = bundle.getString(Contacts.Intents.Insert.NAME); + if (name != null && name.length() > 0) { + newContents.append("N:").append(name).append(';'); + newDisplayContents.append(name); + } + String address = bundle.getString(Contacts.Intents.Insert.POSTAL); + if (address != null && address.length() > 0) { + newContents.append("ADR:").append(address).append(';'); + newDisplayContents.append('\n').append(address); + } + for (int x = 0; x < Contents.PHONE_KEYS.length; x++) { + String phone = bundle.getString(Contents.PHONE_KEYS[x]); + if (phone != null && phone.length() > 0) { + newContents.append("TEL:").append(phone).append(';'); + newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone)); + } + } + for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) { + String email = bundle.getString(Contents.EMAIL_KEYS[x]); + if (email != null && email.length() > 0) { + newContents.append("EMAIL:").append(email).append(';'); + newDisplayContents.append('\n').append(email); + } + } + // Make sure we've encoded at least one field. + if (newDisplayContents.length() > 0) { + newContents.append(';'); + contents = newContents.toString(); + displayContents = newDisplayContents.toString(); + title = activity.getString(R.string.contents_contact); + } else { + contents = null; + displayContents = null; + } + } + } else if (type.equals(Contents.Type.LOCATION)) { + Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA); + if (bundle != null) { + // These must use Bundle.getFloat(), not getDouble(), it's part of the API. + 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) { + 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 contents; + private final Handler handler; + private final int pixelResolution; + private final BarcodeFormat format; + + EncodeThread(String contents, Handler handler, int pixelResolution, + BarcodeFormat format) { + this.contents = contents; + this.handler = handler; + this.pixelResolution = pixelResolution; + this.format = format; + } + + @Override + public void run() { + try { + ByteMatrix result = new MultiFormatWriter().encode(contents, format, + pixelResolution, pixelResolution); + int width = result.getWidth(); + int height = result.getHeight(); + byte[][] array = result.getArray(); + int[] pixels = new int[width * height]; + for (int y = 0; y < height; y++) { + for (int x = 0; x < width; x++) { + int grey = array[y][x] & 0xff; + // pixels[y * width + x] = (0xff << 24) | (grey << 16) | (grey << 8) | grey; + pixels[y * width + x] = 0xff000000 | (0x00010101 * grey); + } + } + + Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + bitmap.setPixels(pixels, 0, width, 0, 0, width, height); + 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(handler, R.id.encode_failed); + message.sendToTarget(); + } catch (IllegalArgumentException e) { + Log.e(TAG, e.toString()); + Message message = Message.obtain(handler, R.id.encode_failed); + message.sendToTarget(); + } + } + } +} diff --git a/android/src/com/google/zxing/client/android/history/DBHelper.java b/android/src/com/google/zxing/client/android/history/DBHelper.java new file mode 100644 index 00000000..4568e85c --- /dev/null +++ b/android/src/com/google/zxing/client/android/history/DBHelper.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.client.android.history; + +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteDatabase; +import android.content.Context; + +/** + * @author Sean Owen + */ +final class DBHelper extends SQLiteOpenHelper { + + private static final int DB_VERSION = 1; + private static final String DB_NAME = "barcode_scanner_history.db"; + static final String TABLE_NAME = "history"; + private static final String ID_COL = "id"; + static final String TEXT_COL = "text"; + static final String TIMESTAMP_COL = "timestamp"; + + DBHelper(Context context) { + super(context, DB_NAME, null, DB_VERSION); + } + + @Override + public void onCreate(SQLiteDatabase sqLiteDatabase) { + sqLiteDatabase.execSQL( + "CREATE TABLE " + TABLE_NAME + " (" + + ID_COL + " INTEGER PRIMARY KEY, " + + TEXT_COL + " TEXT, " + + TIMESTAMP_COL + " INTEGER" + + ");"); + } + + @Override + public void onUpgrade(SQLiteDatabase sqLiteDatabase, int oldVersion, int newVersion) { + } + +} diff --git a/android/src/com/google/zxing/client/android/history/HistoryManager.java b/android/src/com/google/zxing/client/android/history/HistoryManager.java new file mode 100644 index 00000000..5b8c76fa --- /dev/null +++ b/android/src/com/google/zxing/client/android/history/HistoryManager.java @@ -0,0 +1,116 @@ +/* + * Copyright (C) 2009 ZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.google.zxing.client.android.history; + +import android.app.AlertDialog; +import android.content.ContentValues; +import android.content.DialogInterface; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.Cursor; +import android.os.Message; + +import java.util.List; +import java.util.ArrayList; + +import com.google.zxing.client.android.R; +import com.google.zxing.client.android.CaptureActivity; +import com.google.zxing.Result; + +/** + * @author Sean Owen + */ +public final class HistoryManager { + + private final CaptureActivity activity; + + public HistoryManager(CaptureActivity activity) { + this.activity = activity; + } + + List getHistoryItems() { + + SQLiteOpenHelper helper = new DBHelper(activity); + SQLiteDatabase db = helper.getReadableDatabase(); + List items = new ArrayList(); + try { + Cursor cursor = db.query(DBHelper.TABLE_NAME, + new String[] {DBHelper.TEXT_COL}, + null, null, null, null, + DBHelper.TIMESTAMP_COL + " DESC"); + while (cursor.moveToNext()) { + items.add(cursor.getString(0)); + } + } finally { + db.close(); + } + return items; + } + + public AlertDialog buildAlert() { + List items = getHistoryItems(); + final String[] dialogItems = new String[items.size() + 1]; + for (int i = 0; i < items.size(); i++) { + dialogItems[i] = items.get(i); + } + dialogItems[dialogItems.length - 1] = activity.getResources().getString(R.string.history_clear_text); + DialogInterface.OnClickListener clickListener = new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialogInterface, int i) { + if (i == dialogItems.length - 1) { + clearHistory(); + } else { + Result result = new Result(dialogItems[i], null, null, null); + Message message = Message.obtain(activity.getHandler(), R.id.decode_succeeded, result); + message.sendToTarget(); + } + } + }; + AlertDialog.Builder builder = new AlertDialog.Builder(activity); + builder.setTitle(R.string.history_title); + builder.setItems(dialogItems, clickListener); + return builder.create(); + } + + public void addHistoryItem(String text) { + + if (getHistoryItems().contains(text)) { + return; + } + + SQLiteOpenHelper helper = new DBHelper(activity); + SQLiteDatabase db = helper.getWritableDatabase(); + try { + ContentValues values = new ContentValues(); + values.put(DBHelper.TEXT_COL, text); + values.put(DBHelper.TIMESTAMP_COL, System.currentTimeMillis()); + db.insert(DBHelper.TABLE_NAME, DBHelper.TIMESTAMP_COL, values); + } finally { + db.close(); + } + } + + void clearHistory() { + SQLiteOpenHelper helper = new DBHelper(activity); + SQLiteDatabase db = helper.getWritableDatabase(); + try { + db.delete(DBHelper.TABLE_NAME, null, null); + } finally { + db.close(); + } + } + +} diff --git a/android/src/com/google/zxing/client/android/result/AddressBookResultHandler.java b/android/src/com/google/zxing/client/android/result/AddressBookResultHandler.java index 59228add..573adb4c 100644 --- a/android/src/com/google/zxing/client/android/result/AddressBookResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/AddressBookResultHandler.java @@ -107,7 +107,7 @@ public final class AddressBookResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - AddressBookParsedResult addressResult = (AddressBookParsedResult) result; + AddressBookParsedResult addressResult = (AddressBookParsedResult) getResult(); int action = mapIndexToAction(index); switch (action) { case 0: @@ -135,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) this.result; + AddressBookParsedResult result = (AddressBookParsedResult) getResult(); StringBuffer contents = new StringBuffer(); ParsedResult.maybeAppend(result.getNames(), contents); int namesLength = contents.length(); diff --git a/android/src/com/google/zxing/client/android/result/CalendarResultHandler.java b/android/src/com/google/zxing/client/android/result/CalendarResultHandler.java index c58500b6..b0e75f11 100644 --- a/android/src/com/google/zxing/client/android/result/CalendarResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/CalendarResultHandler.java @@ -58,7 +58,7 @@ public final class CalendarResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - CalendarParsedResult calendarResult = (CalendarParsedResult) result; + CalendarParsedResult calendarResult = (CalendarParsedResult) getResult(); switch (index) { case 0: addCalendarEvent(calendarResult.getSummary(), calendarResult.getStart(), @@ -69,7 +69,7 @@ public final class CalendarResultHandler extends ResultHandler { @Override public CharSequence getDisplayContents() { - CalendarParsedResult calResult = (CalendarParsedResult) result; + CalendarParsedResult calResult = (CalendarParsedResult) getResult(); StringBuffer result = new StringBuffer(); ParsedResult.maybeAppend(calResult.getSummary(), result); appendTime(calResult.getStart(), result); diff --git a/android/src/com/google/zxing/client/android/result/EmailAddressResultHandler.java b/android/src/com/google/zxing/client/android/result/EmailAddressResultHandler.java index 35c4fe58..55cc9f3d 100644 --- a/android/src/com/google/zxing/client/android/result/EmailAddressResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/EmailAddressResultHandler.java @@ -49,7 +49,7 @@ public final class EmailAddressResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - EmailAddressParsedResult emailResult = (EmailAddressParsedResult) result; + EmailAddressParsedResult emailResult = (EmailAddressParsedResult) getResult(); switch (index) { case 0: sendEmailFromUri(emailResult.getMailtoURI(), null, null); diff --git a/android/src/com/google/zxing/client/android/result/GeoResultHandler.java b/android/src/com/google/zxing/client/android/result/GeoResultHandler.java index a569ef13..059f2cf0 100644 --- a/android/src/com/google/zxing/client/android/result/GeoResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/GeoResultHandler.java @@ -49,7 +49,7 @@ public final class GeoResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - GeoParsedResult geoResult = (GeoParsedResult) result; + GeoParsedResult geoResult = (GeoParsedResult) getResult(); switch (index) { case 0: openMap(geoResult.getGeoURI()); diff --git a/android/src/com/google/zxing/client/android/result/ISBNResultHandler.java b/android/src/com/google/zxing/client/android/result/ISBNResultHandler.java index d304fa9b..9b058428 100644 --- a/android/src/com/google/zxing/client/android/result/ISBNResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/ISBNResultHandler.java @@ -59,7 +59,7 @@ public final class ISBNResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - ISBNParsedResult isbnResult = (ISBNParsedResult) result; + ISBNParsedResult isbnResult = (ISBNParsedResult) getResult(); switch (index) { case 0: openProductSearch(isbnResult.getISBN()); diff --git a/android/src/com/google/zxing/client/android/result/ProductResultHandler.java b/android/src/com/google/zxing/client/android/result/ProductResultHandler.java index f497d708..0f1405f0 100644 --- a/android/src/com/google/zxing/client/android/result/ProductResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/ProductResultHandler.java @@ -57,7 +57,7 @@ public final class ProductResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - ProductParsedResult productResult = (ProductParsedResult) result; + ProductParsedResult productResult = (ProductParsedResult) getResult(); switch (index) { case 0: openProductSearch(productResult.getNormalizedProductID()); diff --git a/android/src/com/google/zxing/client/android/result/ResultButtonListener.java b/android/src/com/google/zxing/client/android/result/ResultButtonListener.java index 584a3d5a..723c0e6d 100644 --- a/android/src/com/google/zxing/client/android/result/ResultButtonListener.java +++ b/android/src/com/google/zxing/client/android/result/ResultButtonListener.java @@ -26,8 +26,8 @@ import android.widget.Button; * @author dswitkin@google.com (Daniel Switkin) */ public final class ResultButtonListener implements Button.OnClickListener { - final ResultHandler resultHandler; - final int index; + private final ResultHandler resultHandler; + private final int index; public ResultButtonListener(ResultHandler resultHandler, int index) { this.resultHandler = resultHandler; 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 c2a367d3..d7bd354f 100644 --- a/android/src/com/google/zxing/client/android/result/ResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/ResultHandler.java @@ -20,7 +20,7 @@ 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.android.book.SearchBookContentsActivity; import com.google.zxing.client.result.ParsedResult; import com.google.zxing.client.result.ParsedResultType; @@ -54,14 +54,18 @@ public abstract class ResultHandler { public static final int MAX_BUTTON_COUNT = 4; - protected final ParsedResult result; + private final ParsedResult result; private final Activity activity; - protected ResultHandler(Activity activity, ParsedResult result) { + ResultHandler(Activity activity, ParsedResult result) { this.result = result; this.activity = activity; } + ParsedResult getResult() { + return result; + } + /** * Indicates how many buttons the derived class wants shown. * @@ -119,7 +123,7 @@ public abstract class ResultHandler { * @param start The start time as yyyyMMdd or yyyyMMdd'T'HHmmss or yyyyMMdd'T'HHmmss'Z' * @param end The end time as yyyyMMdd or yyyyMMdd'T'HHmmss or yyyyMMdd'T'HHmmss'Z' */ - public final void addCalendarEvent(String summary, String start, String end) { + final void addCalendarEvent(String summary, String start, String end) { Intent intent = new Intent(Intent.ACTION_EDIT); intent.setType("vnd.android.cursor.item/event"); intent.putExtra("beginTime", calculateMilliseconds(start)); @@ -155,7 +159,7 @@ public abstract class ResultHandler { } } - public final void addContact(String[] names, String[] phoneNumbers, String[] emails, String note, + final void addContact(String[] names, String[] phoneNumbers, String[] emails, String note, String address, String org, String title) { // Only use the first name in the array, if present. @@ -180,16 +184,16 @@ public abstract class ResultHandler { launchIntent(intent); } - public final void shareByEmail(String contents) { + final void shareByEmail(String contents) { sendEmailFromUri("mailto:", activity.getString(R.string.msg_share_subject_line), contents); } - public final void sendEmail(String address, String subject, String body) { + final void sendEmail(String address, String subject, String body) { sendEmailFromUri("mailto:" + address, subject, body); } // Use public Intent fields rather than private GMail app fields to specify subject and body. - public final void sendEmailFromUri(String uri, String subject, String body) { + final void sendEmailFromUri(String uri, String subject, String body) { Intent intent = new Intent(Intent.ACTION_SEND, Uri.parse(uri)); putExtra(intent, Intent.EXTRA_SUBJECT, subject); putExtra(intent, Intent.EXTRA_TEXT, body); @@ -197,16 +201,16 @@ public abstract class ResultHandler { launchIntent(intent); } - public final void shareBySMS(String contents) { + final void shareBySMS(String contents) { sendSMSFromUri("smsto:", activity.getString(R.string.msg_share_subject_line) + ":\n" + contents); } - public final void sendSMS(String phoneNumber, String body) { + final void sendSMS(String phoneNumber, String body) { sendSMSFromUri("smsto:" + phoneNumber, body); } - public final void sendSMSFromUri(String uri, String body) { + final void sendSMSFromUri(String uri, String body) { Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse(uri)); putExtra(intent, "sms_body", body); // Exit the app once the SMS is sent @@ -214,11 +218,11 @@ public abstract class ResultHandler { launchIntent(intent); } - public final void sendMMS(String phoneNumber, String subject, String body) { + final void sendMMS(String phoneNumber, String subject, String body) { sendMMSFromUri("mmsto:" + phoneNumber, subject, body); } - public final void sendMMSFromUri(String uri, String subject, String body) { + final void sendMMSFromUri(String uri, String subject, String body) { 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) { @@ -231,15 +235,15 @@ public abstract class ResultHandler { launchIntent(intent); } - public final void dialPhone(String phoneNumber) { + final void dialPhone(String phoneNumber) { launchIntent(new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber))); } - public final void dialPhoneFromUri(String uri) { + final void dialPhoneFromUri(String uri) { launchIntent(new Intent(Intent.ACTION_DIAL, Uri.parse(uri))); } - public final void openMap(String geoURI) { + final void openMap(String geoURI) { launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(geoURI))); } @@ -249,7 +253,7 @@ public abstract class ResultHandler { * @param address The address to find * @param title An optional title, e.g. the name of the business at this address */ - public final void searchMap(String address, String title) { + final void searchMap(String address, String title) { String query = address; if (title != null && title.length() > 0) { query = query + " (" + title + ')'; @@ -257,42 +261,42 @@ public abstract class ResultHandler { launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=" + Uri.encode(query)))); } - public final void getDirections(double latitude, double longitude) { + final void getDirections(double latitude, double longitude) { launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("http://maps.google." + 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) { + final void openProductSearch(String upc) { Uri uri = Uri.parse("http://www.google." + LocaleManager.getProductSearchCountryTLD() + "/m/products?q=" + upc + "&source=zxing"); launchIntent(new Intent(Intent.ACTION_VIEW, uri)); } - public final void openBookSearch(String isbn) { + final void openBookSearch(String isbn) { Uri uri = Uri.parse("http://books.google." + LocaleManager.getBookSearchCountryTLD() + "/books?vid=isbn" + isbn); launchIntent(new Intent(Intent.ACTION_VIEW, uri)); } - public final void searchBookContents(String isbn) { + final void searchBookContents(String isbn) { Intent intent = new Intent(Intents.SearchBookContents.ACTION); intent.setClassName(activity, SearchBookContentsActivity.class.getName()); putExtra(intent, Intents.SearchBookContents.ISBN, isbn); launchIntent(intent); } - public final void openURL(String url) { + final void openURL(String url) { launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(url))); } - public final void webSearch(String query) { + final void webSearch(String query) { Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); intent.putExtra("query", query); launchIntent(intent); } - private void launchIntent(Intent intent) { + void launchIntent(Intent intent) { if (intent != null) { try { activity.startActivity(intent); diff --git a/android/src/com/google/zxing/client/android/result/SMSResultHandler.java b/android/src/com/google/zxing/client/android/result/SMSResultHandler.java index 56868bea..06da2927 100644 --- a/android/src/com/google/zxing/client/android/result/SMSResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/SMSResultHandler.java @@ -50,7 +50,7 @@ public final class SMSResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - SMSParsedResult smsResult = (SMSParsedResult) result; + SMSParsedResult smsResult = (SMSParsedResult) getResult(); switch (index) { case 0: sendSMS(smsResult.getNumber(), smsResult.getBody()); @@ -63,7 +63,7 @@ public final class SMSResultHandler extends ResultHandler { @Override public CharSequence getDisplayContents() { - SMSParsedResult smsResult = (SMSParsedResult) result; + SMSParsedResult smsResult = (SMSParsedResult) getResult(); StringBuffer contents = new StringBuffer(); ParsedResult.maybeAppend(PhoneNumberUtils.formatNumber(smsResult.getNumber()), contents); ParsedResult.maybeAppend(smsResult.getVia(), contents); diff --git a/android/src/com/google/zxing/client/android/result/TelResultHandler.java b/android/src/com/google/zxing/client/android/result/TelResultHandler.java index d957d5c1..85e028ea 100644 --- a/android/src/com/google/zxing/client/android/result/TelResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/TelResultHandler.java @@ -50,7 +50,7 @@ public final class TelResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - TelParsedResult telResult = (TelParsedResult) result; + TelParsedResult telResult = (TelParsedResult) getResult(); switch (index) { case 0: dialPhoneFromUri(telResult.getTelURI()); @@ -66,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 = result.getDisplayResult(); + String contents = getResult().getDisplayResult(); contents = contents.replace("\r", ""); return PhoneNumberUtils.formatNumber(contents); } diff --git a/android/src/com/google/zxing/client/android/result/TextResultHandler.java b/android/src/com/google/zxing/client/android/result/TextResultHandler.java index c40f3567..c60bd029 100644 --- a/android/src/com/google/zxing/client/android/result/TextResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/TextResultHandler.java @@ -59,7 +59,7 @@ public final class TextResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - String text = result.getDisplayResult(); + String text = getResult().getDisplayResult(); switch (index) { case 0: webSearch(text); diff --git a/android/src/com/google/zxing/client/android/result/URIResultHandler.java b/android/src/com/google/zxing/client/android/result/URIResultHandler.java index cd4e11eb..3088f7f8 100644 --- a/android/src/com/google/zxing/client/android/result/URIResultHandler.java +++ b/android/src/com/google/zxing/client/android/result/URIResultHandler.java @@ -50,7 +50,7 @@ public final class URIResultHandler extends ResultHandler { @Override public void handleButtonPress(int index) { - URIParsedResult uriResult = (URIParsedResult) result; + URIParsedResult uriResult = (URIParsedResult) getResult(); switch (index) { case 0: openURL(uriResult.getURI()); diff --git a/android/src/com/google/zxing/client/android/share/BookmarkPickerActivity.java b/android/src/com/google/zxing/client/android/share/BookmarkPickerActivity.java new file mode 100644 index 00000000..026a4ba8 --- /dev/null +++ b/android/src/com/google/zxing/client/android/share/BookmarkPickerActivity.java @@ -0,0 +1,80 @@ +/* + * 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.share; + +import android.app.ListActivity; +import android.content.Intent; +import android.database.Cursor; +import android.os.Bundle; +import android.provider.Browser; +import android.view.View; +import android.widget.ListAdapter; +import android.widget.ListView; +import android.widget.SimpleCursorAdapter; +import com.google.zxing.client.android.R; + +/** + * 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) + */ +final class BookmarkPickerActivity extends ListActivity { + private static final String[] BOOKMARK_PROJECTION = { + Browser.BookmarkColumns.TITLE, + Browser.BookmarkColumns.URL + }; + + private static final int[] TWO_LINE_VIEW_IDS = { + R.id.bookmark_title, + R.id.bookmark_url + }; + + private static final int TITLE_COLUMN = 0; + private static final int URL_COLUMN = 1; + + // Without this selection, we'd get all the history entries too + private static final String BOOKMARK_SELECTION = "bookmark = 1"; + + private Cursor cursor; + + @Override + protected void onCreate(Bundle icicle) { + super.onCreate(icicle); + + cursor = getContentResolver().query(Browser.BOOKMARKS_URI, BOOKMARK_PROJECTION, + BOOKMARK_SELECTION, null, null); + startManagingCursor(cursor); + + ListAdapter adapter = new SimpleCursorAdapter(this, R.layout.bookmark_picker_list_item, + cursor, BOOKMARK_PROJECTION, TWO_LINE_VIEW_IDS); + setListAdapter(adapter); + } + + @Override + protected void onListItemClick(ListView l, View view, int position, long id) { + if (cursor.moveToPosition(position)) { + Intent intent = new Intent(); + 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(); + } +} diff --git a/android/src/com/google/zxing/client/android/share/ShareActivity.java b/android/src/com/google/zxing/client/android/share/ShareActivity.java new file mode 100755 index 00000000..3578b1ab --- /dev/null +++ b/android/src/com/google/zxing/client/android/share/ShareActivity.java @@ -0,0 +1,224 @@ +/* + * 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.share; + +import android.app.Activity; +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.Browser; +import android.provider.Contacts; +import android.provider.BaseColumns; +import android.text.ClipboardManager; +import android.view.View; +import android.widget.Button; +import com.google.zxing.client.android.Intents; +import com.google.zxing.client.android.Contents; +import com.google.zxing.client.android.R; + +/** + * 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; + + //private static final int METHODS_ID_COLUMN = 0; + private static final int METHODS_KIND_COLUMN = 1; + private static final int METHODS_DATA_COLUMN = 2; + + private static final String[] METHODS_PROJECTION = { + BaseColumns._ID, // 0 + Contacts.ContactMethodsColumns.KIND, // 1 + Contacts.ContactMethodsColumns.DATA, // 2 + }; + + private static final int PHONES_NUMBER_COLUMN = 1; + + private static final String[] PHONES_PROJECTION = { + BaseColumns._ID, // 0 + Contacts.PhonesColumns.NUMBER // 1 + }; + + private Button clipboardButton; + + 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 bookmarkListener = new Button.OnClickListener() { + public void onClick(View v) { + Intent intent = new Intent(Intent.ACTION_PICK); + intent.setClassName(ShareActivity.this, BookmarkPickerActivity.class.getName()); + startActivityForResult(intent, PICK_BOOKMARK); + } + }; + + 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 + if (clipboard.hasText()) { + Intent intent = new Intent(Intents.Encode.ACTION); + intent.putExtra(Intents.Encode.TYPE, Contents.Type.TEXT); + intent.putExtra(Intents.Encode.DATA, clipboard.getText()); + intent.putExtra(Intents.Encode.FORMAT, Contents.Format.QR_CODE); + startActivity(intent); + } + } + }; + + @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) { + switch (requestCode) { + case PICK_BOOKMARK: + showTextAsBarcode(intent.getStringExtra(Browser.BookmarkColumns.URL)); + break; + case PICK_CONTACT: + // Data field is content://contacts/people/984 + showContactAsBarcode(intent.getData()); + break; + } + } + } + + private void showTextAsBarcode(String text) { + Intent intent = new Intent(Intents.Encode.ACTION); + intent.putExtra(Intents.Encode.TYPE, Contents.Type.TEXT); + intent.putExtra(Intents.Encode.DATA, text); + intent.putExtra(Intents.Encode.FORMAT, Contents.Format.QR_CODE); + startActivity(intent); + } + + /** + * Takes a contact Uri and does the necessary database lookups to retrieve that person's info, + * then sends an Encode intent to render it as a QR Code. + * + * @param contactUri A Uri of the form content://contacts/people/17 + */ + private void showContactAsBarcode(Uri contactUri) { + ContentResolver resolver = getContentResolver(); + Cursor contactCursor = resolver.query(contactUri, null, null, null, null); + Bundle bundle = new Bundle(); + if (contactCursor != null && contactCursor.moveToFirst()) { + int nameColumn = contactCursor.getColumnIndex(Contacts.PeopleColumns.NAME); + String name = contactCursor.getString(nameColumn); + + // Don't require a name to be present, this contact might be just a phone number. + if (name != null && name.length() > 0) { + bundle.putString(Contacts.Intents.Insert.NAME, massageContactData(name)); + } + contactCursor.close(); + + Uri phonesUri = Uri.withAppendedPath(contactUri, Contacts.People.Phones.CONTENT_DIRECTORY); + Cursor phonesCursor = resolver.query(phonesUri, PHONES_PROJECTION, null, null, null); + if (phonesCursor != null) { + int foundPhone = 0; + while (phonesCursor.moveToNext()) { + String number = phonesCursor.getString(PHONES_NUMBER_COLUMN); + if (foundPhone < Contents.PHONE_KEYS.length) { + bundle.putString(Contents.PHONE_KEYS[foundPhone], massageContactData(number)); + foundPhone++; + } + } + phonesCursor.close(); + } + + Uri methodsUri = Uri.withAppendedPath(contactUri, + Contacts.People.ContactMethods.CONTENT_DIRECTORY); + Cursor methodsCursor = resolver.query(methodsUri, METHODS_PROJECTION, null, null, null); + if (methodsCursor != null) { + int foundEmail = 0; + boolean foundPostal = false; + while (methodsCursor.moveToNext()) { + int kind = methodsCursor.getInt(METHODS_KIND_COLUMN); + String data = methodsCursor.getString(METHODS_DATA_COLUMN); + switch (kind) { + case Contacts.KIND_EMAIL: + if (foundEmail < Contents.EMAIL_KEYS.length) { + bundle.putString(Contents.EMAIL_KEYS[foundEmail], massageContactData(data)); + foundEmail++; + } + break; + case Contacts.KIND_POSTAL: + if (!foundPostal) { + bundle.putString(Contacts.Intents.Insert.POSTAL, massageContactData(data)); + foundPostal = true; + } + break; + } + } + methodsCursor.close(); + } + + Intent intent = new Intent(Intents.Encode.ACTION); + intent.putExtra(Intents.Encode.TYPE, Contents.Type.CONTACT); + intent.putExtra(Intents.Encode.DATA, bundle); + intent.putExtra(Intents.Encode.FORMAT, Contents.Format.QR_CODE); + + startActivity(intent); + } + } + + private static String massageContactData(String data) { + // For now -- make sure we don't put newlines in shared contact data. It messes up + // any known encoding of contact data. Replace with space. + if (data.indexOf('\n') >= 0) { + data = data.replace("\n", " "); + } + if (data.indexOf('\r') >= 0) { + data = data.replace("\r", " "); + } + return data; + } +}