Add history feature; group some functionality into subpackages
authorsrowen <srowen@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 21 Sep 2009 18:03:29 +0000 (18:03 +0000)
committersrowen <srowen@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 21 Sep 2009 18:03:29 +0000 (18:03 +0000)
git-svn-id: http://zxing.googlecode.com/svn/trunk@1058 59b500cc-1b3d-0410-9834-0bbf25fbcc57

38 files changed:
android/res/values-ja-rJP/strings.xml
android/res/values-zh-rCN/strings.xml
android/res/values-zh-rTW/strings.xml
android/res/values/strings.xml
android/src/com/google/zxing/client/android/BaseLuminanceSource.java
android/src/com/google/zxing/client/android/BookmarkPickerActivity.java [deleted file]
android/src/com/google/zxing/client/android/CaptureActivity.java
android/src/com/google/zxing/client/android/CaptureActivityHandler.java
android/src/com/google/zxing/client/android/DecodeThread.java
android/src/com/google/zxing/client/android/EncodeActivity.java [deleted file]
android/src/com/google/zxing/client/android/QRCodeEncoder.java [deleted file]
android/src/com/google/zxing/client/android/SearchBookContentsActivity.java [deleted file]
android/src/com/google/zxing/client/android/SearchBookContentsAdapter.java [deleted file]
android/src/com/google/zxing/client/android/SearchBookContentsListItem.java [deleted file]
android/src/com/google/zxing/client/android/SearchBookContentsResult.java [deleted file]
android/src/com/google/zxing/client/android/ShareActivity.java [deleted file]
android/src/com/google/zxing/client/android/book/SearchBookContentsActivity.java [new file with mode: 0644]
android/src/com/google/zxing/client/android/book/SearchBookContentsAdapter.java [new file with mode: 0644]
android/src/com/google/zxing/client/android/book/SearchBookContentsListItem.java [new file with mode: 0644]
android/src/com/google/zxing/client/android/book/SearchBookContentsResult.java [new file with mode: 0644]
android/src/com/google/zxing/client/android/encode/EncodeActivity.java [new file with mode: 0755]
android/src/com/google/zxing/client/android/encode/QRCodeEncoder.java [new file with mode: 0755]
android/src/com/google/zxing/client/android/history/DBHelper.java [new file with mode: 0644]
android/src/com/google/zxing/client/android/history/HistoryManager.java [new file with mode: 0644]
android/src/com/google/zxing/client/android/result/AddressBookResultHandler.java
android/src/com/google/zxing/client/android/result/CalendarResultHandler.java
android/src/com/google/zxing/client/android/result/EmailAddressResultHandler.java
android/src/com/google/zxing/client/android/result/GeoResultHandler.java
android/src/com/google/zxing/client/android/result/ISBNResultHandler.java
android/src/com/google/zxing/client/android/result/ProductResultHandler.java
android/src/com/google/zxing/client/android/result/ResultButtonListener.java
android/src/com/google/zxing/client/android/result/ResultHandler.java
android/src/com/google/zxing/client/android/result/SMSResultHandler.java
android/src/com/google/zxing/client/android/result/TelResultHandler.java
android/src/com/google/zxing/client/android/result/TextResultHandler.java
android/src/com/google/zxing/client/android/result/URIResultHandler.java
android/src/com/google/zxing/client/android/share/BookmarkPickerActivity.java [new file with mode: 0644]
android/src/com/google/zxing/client/android/share/ShareActivity.java [new file with mode: 0755]

index 46a6126..0daf9f7 100644 (file)
@@ -53,6 +53,7 @@
   <string name="menu_about">詳細</string>
   <string name="menu_help">ヘルプ</string>
   <string name="menu_settings">設定</string>
+  <string name="menu_history">歴史</string>    
   <string name="menu_share">共有</string>
 
   <string name="msg_about">オープンソースのバーコード ライブラリ、ZXing を使用しています</string>
   <string name="result_text">テキストデータがヒットしました</string>
   <string name="result_uri">URL がヒットしました</string>
 
+  <string name="history_title">歴史</string>
+  <string name="history_clear_text">削除履歴</string>
+
   <string name="sbc_name">Google ブックス</string>
   <string name="share_name">バーコードで共有する</string>
 
index afae40f..acd4cb7 100644 (file)
@@ -49,6 +49,7 @@
   <string name="menu_about">关于</string>\r
   <string name="menu_help">帮助</string>\r
   <string name="menu_settings">设置</string>\r
+  <string name="menu_history">历史</string>      \r
   <string name="menu_share">分享</string>\r
   <string name="msg_about">以ZXing的开源条码库为基础</string>\r
   <string name="msg_default_contents">内容</string>\r
@@ -87,6 +88,8 @@
   <string name="result_tel">找到电话号码</string>\r
   <string name="result_text">找到纯文本</string>\r
   <string name="result_uri">找到URL</string>\r
+  <string name="history_title">历史</string>\r
+  <string name="history_clear_text">删除历史</string>\r
   <string name="sbc_name">Google图书搜索</string>\r
   <string name="share_name">通过条码分享</string>\r
   <string name="title_about">条码扫描器</string>\r
index d998530..ff28850 100644 (file)
@@ -49,6 +49,7 @@
   <string name="menu_about">關于</string>\r
   <string name="menu_help">幫助</string>\r
   <string name="menu_settings">設置</string>\r
+  <string name="menu_history">歷史</string>  \r
   <string name="menu_share">分享</string>\r
   <string name="msg_about">以ZXing的開源條碼庫為基礎</string>\r
   <string name="msg_default_contents">內容</string>\r
@@ -87,6 +88,8 @@
   <string name="result_tel">找到電話號碼</string>\r
   <string name="result_text">找到純文本</string>\r
   <string name="result_uri">找到URL</string>\r
+  <string name="history_title">歷史</string>\r
+  <string name="history_clear_text">刪除歷史</string>\r
   <string name="sbc_name">Google圖書搜索</string>\r
   <string name="share_name">通過條碼分享</string>\r
   <string name="title_about">條碼掃描器</string>\r
index 740857b..af76d6c 100755 (executable)
@@ -53,6 +53,7 @@
   <string name="menu_about">About</string>
   <string name="menu_help">Help</string>
   <string name="menu_settings">Settings</string>
+  <string name="menu_history">History</string>
   <string name="menu_share">Share</string>
 
   <string name="msg_about">Based on the open source ZXing Barcode Library</string>
   <string name="result_text">Found plain text</string>
   <string name="result_uri">Found URL</string>
 
+  <string name="history_title">History</string>
+  <string name="history_clear_text">Clear history</string>
+
   <string name="sbc_name">Google Book Search</string>
   <string name="share_name">Share via barcode</string>
 
index 523c59a..fda6a84 100644 (file)
@@ -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 (file)
index 537ed7f..0000000
+++ /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();
-  }
-}
index 336ed40..bc56bd8 100755 (executable)
@@ -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);
index 1552f16..ab241c7 100755 (executable)
@@ -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();
     }
index bd2ade6..1f8de72 100755 (executable)
@@ -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 (executable)
index 0584697..0000000
+++ /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 (executable)
index 4a58a58..0000000
+++ /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 (file)
index b84c73d..0000000
+++ /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<SearchBookContentsResult> items = new ArrayList<SearchBookContentsResult>(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("&lt;", "<");
-        snippet = snippet.replaceAll("&gt;", ">");
-        snippet = snippet.replaceAll("&#39;", "'");
-        snippet = snippet.replaceAll("&quot;", "\"");
-      } 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 (file)
index 4f9e4c3..0000000
+++ /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<SearchBookContentsResult> {
-  public SearchBookContentsAdapter(Context context, List<SearchBookContentsResult> 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 (file)
index 80d6cec..0000000
+++ /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 (file)
index de561a7..0000000
+++ /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 (executable)
index db302dd..0000000
+++ /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 (file)
index 0000000..a0de09c
--- /dev/null
@@ -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("&lt;");
+  private static final Pattern GT_ENTITY_PATTERN = Pattern.compile("&gt;");
+  private static final Pattern QUOTE_ENTITY_PATTERN = Pattern.compile("&#39;");
+  private static final Pattern QUOT_ENTITY_PATTERN = Pattern.compile("&quot;");
+
+  @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<SearchBookContentsResult> items = new ArrayList<SearchBookContentsResult>(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 (file)
index 0000000..32fbb10
--- /dev/null
@@ -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<SearchBookContentsResult> {
+  public SearchBookContentsAdapter(Context context, List<SearchBookContentsResult> 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 (file)
index 0000000..0700b01
--- /dev/null
@@ -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 (file)
index 0000000..8893739
--- /dev/null
@@ -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 (executable)
index 0000000..9f50e47
--- /dev/null
@@ -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 (executable)
index 0000000..2f4a573
--- /dev/null
@@ -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 (file)
index 0000000..4568e85
--- /dev/null
@@ -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 (file)
index 0000000..5b8c76f
--- /dev/null
@@ -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<String> getHistoryItems() {
+
+    SQLiteOpenHelper helper = new DBHelper(activity);
+    SQLiteDatabase db = helper.getReadableDatabase();
+    List<String> items = new ArrayList<String>();
+    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<String> 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();
+    }
+  }
+
+}
index 59228ad..573adb4 100644 (file)
@@ -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();
index c58500b..b0e75f1 100644 (file)
@@ -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);
index 35c4fe5..55cc9f3 100644 (file)
@@ -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);
index a569ef1..059f2cf 100644 (file)
@@ -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());
index d304fa9..9b05842 100644 (file)
@@ -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());
index f497d70..0f1405f 100644 (file)
@@ -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());
index 584a3d5..723c0e6 100644 (file)
@@ -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;
index c2a367d..d7bd354 100644 (file)
@@ -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);
index 56868be..06da292 100644 (file)
@@ -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);
index d957d5c..85e028e 100644 (file)
@@ -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);
   }
index c40f356..c60bd02 100644 (file)
@@ -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);
index cd4e11e..3088f7f 100644 (file)
@@ -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 (file)
index 0000000..026a4ba
--- /dev/null
@@ -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 (executable)
index 0000000..3578b1a
--- /dev/null
@@ -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;
+  }
+}