The new Android client, featuring:

- a completely new result UI with multiple actions per barcode
- Search Book Contents, powered by Google Book Search
- support for calendar events
- support for URLs and addresses in contact info
- many other small improvements, like making URLs hyperlinked

git-svn-id: https://zxing.googlecode.com/svn/trunk@625 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
dswitkin 2008-10-18 16:23:24 +00:00
parent f3123f432b
commit 35115ce1f3
42 changed files with 4256 additions and 122 deletions

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2008 Google Inc.
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.
@ -15,34 +15,59 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.android.barcodes">
<application android:icon="@drawable/ic_launcher_barcodes"
android:label="@string/app_name">
package="com.google.zxing.client.android">
<application android:icon="@drawable/ic_launcher_barcodes"
android:label="@string/app_name">
<activity android:name=".BarcodesCaptureActivity"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden"
android:theme="@android:style/Theme.NoTitleBar.Fullscreen">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="com.android.barcodes.SCAN"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.zxing.client.android.SCAN"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<!-- For compatibility only - do not use in new code, this will go away! -->
<action android:name="com.android.barcodes.SCAN"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<activity android:name="BarcodesPreferenceActivity"
android:label="@string/preferences_name">
</activity>
<activity android:name="BarcodesEncodeActivity"
android:screenOrientation="portrait">
<intent-filter>
<action android:name="com.android.barcodes.ENCODE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<action android:name="com.google.zxing.client.android.ENCODE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<!-- For compatibility only - do not use in new code, this will go away! -->
<action android:name="com.android.barcodes.ENCODE"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.INTERNET"/>
<activity android:name="SearchBookContentsActivity"
android:label="@string/sbc_name"
android:screenOrientation="landscape"
android:configChanges="orientation|keyboardHidden">
<intent-filter>
<action android:name="com.google.zxing.client.android.SEARCH_BOOK_CONTENTS"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
<intent-filter>
<!-- For compatibility only - do not use in new code, this will go away! -->
<action android:name="com.android.barcodes.SEARCH_BOOK_CONTENTS"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
<uses-permission android:name="android.permission.INTERNET"/>
</manifest>

View file

@ -1,4 +1,19 @@
<?xml version="1.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.
-->
<project name="BarcodeScanner" default="debug">
<!-- SDK Locations -->
<property file="../build.properties"/>
@ -6,7 +21,7 @@
<property name="android-tools" value="${sdk-folder}/tools"/>
<!-- Application Package Name -->
<property name="application-package" value="com.android.barcodes" />
<property name="application-package" value="com.google.zxing.client.android" />
<!-- The intermediates directory -->
<!-- Eclipse uses "bin" for its own output, so we do the same. -->

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2008 Google Inc.
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.
@ -23,23 +23,115 @@
android:layout_height="fill_parent"
android:layout_centerInParent="true"/>
<com.android.barcodes.ViewfinderView
android:id="@+id/viewfinder_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/transparent"/>
<com.google.zxing.client.android.ViewfinderView
android:id="@+id/viewfinder_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/transparent"/>
<LinearLayout
<LinearLayout android:id="@+id/result_view"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/transparent">
android:background="@color/result_view"
android:visibility="gone"
android:padding="4px">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:gravity="top"
android:padding="12px">
<LinearLayout
android:orientation="vertical"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:gravity="left|top">
<ImageView android:id="@+id/barcode_image_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="4px"
android:adjustViewBounds="true"
android:scaleType="centerInside"/>
<TextView android:id="@+id/format_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="@string/msg_default_format"
android:textColor="@color/result_minor_text"
android:textSize="14sp"/>
<TextView android:id="@+id/type_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left"
android:text="@string/msg_default_type"
android:textColor="@color/result_minor_text"
android:textSize="14sp"/>
</LinearLayout>
<ScrollView
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1">
<TextView android:id="@+id/contents_text_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:gravity="left|top"
android:text="@string/msg_default_contents"
android:textColor="@color/result_text"
android:textSize="22sp"
android:paddingLeft="12px"
android:autoLink="web"/>
</ScrollView>
</LinearLayout>
<LinearLayout android:id="@+id/result_button_view"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:gravity="center">
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
<Button android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:visibility="gone"/>
</LinearLayout>
</LinearLayout>
<LinearLayout
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/transparent">
<FrameLayout
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@color/transparent"/>
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:layout_weight="1"
android:background="@color/transparent"/>
<LinearLayout android:id="@+id/status_view"
android:orientation="horizontal"
@ -56,13 +148,8 @@
android:layout_gravity="left|center_vertical"
android:layout_weight="1"
android:text="@string/msg_default_status"
android:textColor="@color/status_text"/>
<Button android:id="@+id/status_action_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center|fill_horizontal"
android:visibility="gone"/>
android:textColor="@color/status_text"
android.textSize="14sp"/>
</LinearLayout>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2008 Google Inc.
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.
@ -15,24 +15,24 @@
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/encode_view"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/encode_view"
android:orientation="vertical"
android:gravity="center">
<ImageView android:id="@+id/image_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:scaleType="center"/>
<TextView android:id="@+id/contents_text_view"
android:id="@+id/encode_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:textColor="@color/contents_text"
android:textSize="20.0sp"/>
android:layout_height="fill_parent"
android:background="@color/encode_view"
android:orientation="vertical"
android:gravity="center">
<ImageView android:id="@+id/image_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:scaleType="center"/>
<TextView android:id="@+id/contents_text_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_gravity="center_horizontal"
android:gravity="center"
android:textColor="@color/contents_text"
android:textSize="20.0sp"/>
</LinearLayout>

View file

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="@color/sbc_layout_view">
<LinearLayout
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="0">
<EditText android:id="@+id/query_text_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|center_vertical"
android:layout_weight="1"
android:singleLine="true"
android:selectAllOnFocus="true"/>
<Button android:id="@+id/query_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_weight="0"
android:text="@string/button_search_book_contents"/>
</LinearLayout>
<ListView android:id="@+id/result_list_view"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_weight="1"/>
</LinearLayout>

View file

@ -0,0 +1,25 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/sbc_header_view"
android:paddingLeft="8px"
android:enabled="false"
android:textColor="@color/sbc_header_text"
android:singleLine="true"
android:textSize="16sp"/>

View file

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
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.
-->
<com.google.zxing.client.android.SearchBookContentsListItem
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:background="@color/sbc_list_item"
android:padding="8px">
<TextView android:id="@+id/page_number_view"
android:layout_width="75px"
android:layout_height="wrap_content"
android:layout_gravity="left|top"
android:layout_marginRight="8px"
android:textColor="@color/sbc_page_number_text"
android:singleLine="false"
android:textSize="16sp"
android:textStyle="bold"/>
<TextView android:id="@+id/snippet_view"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="left|top"
android:textColor="@color/sbc_snippet_text"
android:singleLine="false"
android:textSize="16sp"
android:typeface="serif"/>
</com.google.zxing.client.android.SearchBookContentsListItem>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2008 Google Inc.
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.
@ -17,4 +17,4 @@
<resources>
<declare-styleable name="ViewfinderView">
</declare-styleable>
</resources>
</resources>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2008 Google Inc.
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.
@ -15,13 +15,23 @@
limitations under the License.
-->
<resources>
<color name="contents_text">#000000</color>
<color name="encode_view">#ffffff</color>
<color name="result_points">#c000ff00</color>
<color name="status_view">#50000000</color>
<color name="status_text">#ffffffff</color>
<color name="transparent">#00000000</color>
<color name="viewfinder_frame">#ff000000</color>
<color name="viewfinder_laser">#ff0000</color>
<color name="viewfinder_mask">#60000000</color>
</resources>
<color name="contents_text">#ff000000</color>
<color name="encode_view">#ffffffff</color>
<color name="result_image_border">#ffffffff</color>
<color name="result_minor_text">#ffc0c0c0</color>
<color name="result_points">#c000ff00</color>
<color name="result_text">#ffffffff</color>
<color name="result_view">#b0000000</color>
<color name="sbc_header_text">#ff808080</color>
<color name="sbc_header_view">#ffffffff</color>
<color name="sbc_list_item">#fffff0e0</color>
<color name="sbc_layout_view">#ffffffff</color>
<color name="sbc_page_number_text">#ff000000</color>
<color name="sbc_snippet_text">#ff4b4b4b</color>
<color name="status_view">#50000000</color>
<color name="status_text">#ffffffff</color>
<color name="transparent">#00000000</color>
<color name="viewfinder_frame">#ff000000</color>
<color name="viewfinder_laser">#ffff0000</color>
<color name="viewfinder_mask">#60000000</color>
</resources>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2008 Google Inc.
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.
@ -15,14 +15,16 @@
limitations under the License.
-->
<resources>
<!-- Messages IDs -->
<item type="id" name="auto_focus"/>
<item type="id" name="decode"/>
<item type="id" name="decode_failed"/>
<item type="id" name="decode_succeeded"/>
<item type="id" name="encode_failed"/>
<item type="id" name="encode_succeeded"/>
<item type="id" name="quit"/>
<item type="id" name="restart_preview"/>
<item type="id" name="return_scan_result"/>
<!-- Messages IDs -->
<item type="id" name="auto_focus"/>
<item type="id" name="decode"/>
<item type="id" name="decode_failed"/>
<item type="id" name="decode_succeeded"/>
<item type="id" name="encode_failed"/>
<item type="id" name="encode_succeeded"/>
<item type="id" name="quit"/>
<item type="id" name="restart_preview"/>
<item type="id" name="return_scan_result"/>
<item type="id" name="search_book_contents_failed"/>
<item type="id" name="search_book_contents_succeeded"/>
</resources>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (C) 2008 Google Inc.
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.
@ -15,46 +15,83 @@
limitations under the License.
-->
<resources>
<string name="app_name">Barcode Scanner</string>
<string name="app_name">Barcode Scanner</string>
<string name="button_add_contact">Add contact</string>
<string name="button_cancel">Cancel</string>
<string name="button_dial">Dial</string>
<string name="button_email">Email</string>
<string name="button_lookup_book">Look up book</string>
<string name="button_lookup_product">Look up product</string>
<string name="button_ok">OK</string>
<string name="button_open_browser">Open browser</string>
<string name="button_show_map">Show map</string>
<string name="button_sms">Send SMS</string>
<string name="button_add_calendar">Add event to calendar</string>
<string name="button_add_contact">Add contact</string>
<string name="button_book_search">Open Book Search</string>
<string name="button_cancel">Cancel</string>
<string name="button_dial">Dial number</string>
<string name="button_email">Send email</string>
<string name="button_get_directions">Get directions</string>
<string name="button_mms">Send MMS</string>
<string name="button_ok">OK</string>
<string name="button_open_browser">Open browser</string>
<string name="button_product_search">Open Product Search</string>
<string name="button_search_book_contents">Search book contents</string>
<string name="button_share_by_email">Share via email</string>
<string name="button_share_by_sms">Share via SMS</string>
<string name="button_show_map">Show map</string>
<string name="button_sms">Send SMS</string>
<string name="button_web_search">Web search</string>
<string name="contents_contact">Contact info</string>
<string name="contents_email">Email address</string>
<string name="contents_location">Geographic coordinates</string>
<string name="contents_phone">Phone number</string>
<string name="contents_sms">SMS address</string>
<string name="contents_text">Plain text</string>
<string name="contents_contact">Contact info</string>
<string name="contents_email">Email address</string>
<string name="contents_location">Geographic coordinates</string>
<string name="contents_phone">Phone number</string>
<string name="contents_sms">SMS address</string>
<string name="contents_text">Plain text</string>
<string name="menu_about">About</string>
<string name="menu_help">Help</string>
<string name="menu_settings">Settings</string>
<string name="menu_about">About</string>
<string name="menu_help">Help</string>
<string name="menu_settings">Settings</string>
<string name="msg_about">Based on the ZXing Barcode Library</string>
<string name="msg_encode_barcode_failed">Could not retrieve a barcode from the network.</string>
<string name="msg_encode_contents_failed">Could not encode a barcode from the data provided.</string>
<string name="msg_encode_in_progress">Generating a barcode\u2026</string>
<string name="msg_help">Barcode Scanner continuously scans the viewfinder rectangle, so there\'s no need to press the shutter button. If you\'re having trouble, make sure to hold the phone steady. If the camera is unable to focus, try moving further back from the barcode.</string>
<string name="msg_default_status">Place a barcode inside the viewfinder rectangle to read it.</string>
<string name="msg_about">Based on the open source ZXing Barcode Library</string>
<string name="msg_default_contents">Contents</string>
<string name="msg_default_format">Format</string>
<string name="msg_default_mms_subject">Hi</string>
<string name="msg_default_status">Place a barcode inside the viewfinder rectangle to read it.
</string>
<string name="msg_default_type">Type</string>
<string name="msg_encode_barcode_failed">Could not retrieve a barcode from the network.</string>
<string name="msg_encode_contents_failed">Could not encode a barcode from the data provided.
</string>
<string name="msg_encode_in_progress">Generating a barcode\u2026</string>
<string name="msg_help">Barcode Scanner continuously scans the viewfinder rectangle, so there\'s
no need to press the shutter button. If you\'re having trouble, make sure to hold the phone
steady. If the camera is unable to focus, try moving further back from the barcode.
</string>
<string name="msg_sbc_failed">Sorry, the search encountered a problem.</string>
<string name="msg_sbc_no_page_returned">No page returned</string>
<string name="msg_sbc_page">Page</string>
<string name="msg_sbc_snippet_unavailable">Snippets not available</string>
<string name="msg_sbc_unknown_page">Unknown page</string>
<string name="msg_searching_book">Searching book\u2026</string>
<string name="msg_share_barcode">Here\'s the contents of a barcode I scanned</string>
<string name="preferences_decode_1D_title">Decode 1D barcodes</string>
<string name="preferences_decode_QR_title">Decode QR Codes</string>
<string name="preferences_general_title">General settings</string>
<string name="preferences_name">Settings</string>
<string name="preferences_play_beep_title">Beep when a barcode is found</string>
<string name="preferences_sounds_title">Sounds</string>
<string name="preferences_decode_1D_title">Decode 1D barcodes</string>
<string name="preferences_decode_QR_title">Decode QR Codes</string>
<string name="preferences_general_title">General settings</string>
<string name="preferences_name">Settings</string>
<string name="preferences_play_beep_title">Beep when a barcode is found</string>
<string name="preferences_sounds_title">Sounds</string>
<string name="title_about">About Barcode Scanner</string>
<string name="title_help">Help</string>
<string name="result_address_book">Found contact info</string>
<string name="result_calendar">Found calendar event</string>
<string name="result_email_address">Found email address</string>
<string name="result_geo">Found geographic coordinates</string>
<string name="result_isbn">Found book</string>
<string name="result_sms">Found SMS address</string>
<string name="result_tel">Found phone number</string>
<string name="result_text">Found plain text</string>
<string name="result_upc">Found product</string>
<string name="result_uri">Found URL</string>
<string name="zxing_url">http://code.google.com/p/zxing</string>
<string name="sbc_name">Google Book Search</string>
<string name="title_about">About Barcode Scanner</string>
<string name="title_help">Help</string>
<string name="zxing_url">http://code.google.com/p/zxing</string>
<string name="zxing_user_agent">ZXing-Android/1.0</string>
</resources>

View file

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (C) 2008 Google Inc.
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.

View file

@ -0,0 +1,432 @@
/*
* 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.util.Log;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpEntityEnclosingRequest;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpMessage;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.client.params.HttpClientParams;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.conn.ClientConnectionManager;
import org.apache.http.conn.scheme.PlainSocketFactory;
import org.apache.http.conn.scheme.Scheme;
import org.apache.http.conn.scheme.SchemeRegistry;
import org.apache.http.conn.ssl.SSLSocketFactory;
import org.apache.http.entity.AbstractHttpEntity;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.impl.client.RequestWrapper;
import org.apache.http.impl.conn.tsccm.ThreadSafeClientConnManager;
import org.apache.http.params.BasicHttpParams;
import org.apache.http.params.HttpConnectionParams;
import org.apache.http.params.HttpParams;
import org.apache.http.params.HttpProtocolParams;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.BasicHttpProcessor;
import org.apache.http.protocol.HttpContext;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URI;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;
/**
* <p>Subclass of the Apache {@link DefaultHttpClient} that is configured with
* reasonable default settings and registered schemes for Android, and
* also lets the user add {@link HttpRequestInterceptor} classes.
* Don't create this directly, use the {@link #newInstance} factory method.</p>
* <p/>
* <p>This client processes cookies but does not retain them by default.
* To retain cookies, simply add a cookie store to the HttpContext:
* <pre>context.setAttribute(ClientContext.COOKIE_STORE, cookieStore);</pre>
* </p>
*/
public final class AndroidHttpClient implements HttpClient {
// Gzip of data shorter than this probably won't be worthwhile
public static final long DEFAULT_SYNC_MIN_GZIP_BYTES = 256;
private static final String TAG = "AndroidHttpClient";
/**
* Set if HTTP requests are blocked from being executed on this thread
*/
private static final ThreadLocal<Boolean> sThreadBlocked =
new ThreadLocal<Boolean>();
/**
* Interceptor throws an exception if the executing thread is blocked
*/
private static final HttpRequestInterceptor sThreadCheckInterceptor =
new HttpRequestInterceptor() {
public void process(HttpRequest request, HttpContext context) {
if (sThreadBlocked.get() != null && sThreadBlocked.get()) {
throw new RuntimeException("This thread forbids HTTP requests");
}
}
};
/**
* Create a new HttpClient with reasonable defaults (which you can update).
*
* @param userAgent to report in your HTTP requests.
* @return AndroidHttpClient for you to use for all your requests.
*/
public static AndroidHttpClient newInstance(String userAgent) {
HttpParams params = new BasicHttpParams();
// Turn off stale checking. Our connections break all the time anyway,
// and it's not worth it to pay the penalty of checking every time.
HttpConnectionParams.setStaleCheckingEnabled(params, false);
// Default connection and socket timeout of 20 seconds. Tweak to taste.
HttpConnectionParams.setConnectionTimeout(params, 20 * 1000);
HttpConnectionParams.setSoTimeout(params, 20 * 1000);
HttpConnectionParams.setSocketBufferSize(params, 8192);
// Don't handle redirects -- return them to the caller. Our code
// often wants to re-POST after a redirect, which we must do ourselves.
HttpClientParams.setRedirecting(params, false);
// Set the specified user agent and register standard protocols.
HttpProtocolParams.setUserAgent(params, userAgent);
SchemeRegistry schemeRegistry = new SchemeRegistry();
schemeRegistry.register(new Scheme("http",
PlainSocketFactory.getSocketFactory(), 80));
schemeRegistry.register(new Scheme("https",
SSLSocketFactory.getSocketFactory(), 443));
ClientConnectionManager manager =
new ThreadSafeClientConnManager(params, schemeRegistry);
// We use a factory method to modify superclass initialization
// parameters without the funny call-a-static-method dance.
return new AndroidHttpClient(manager, params);
}
private final HttpClient delegate;
private RuntimeException mLeakedException = new IllegalStateException(
"AndroidHttpClient created and never closed");
private AndroidHttpClient(ClientConnectionManager ccm, HttpParams params) {
this.delegate = new DefaultHttpClient(ccm, params) {
@Override
protected BasicHttpProcessor createHttpProcessor() {
// Add interceptor to prevent making requests from main thread.
BasicHttpProcessor processor = super.createHttpProcessor();
processor.addRequestInterceptor(sThreadCheckInterceptor);
processor.addRequestInterceptor(new CurlLogger());
return processor;
}
@Override
protected HttpContext createHttpContext() {
// Same as DefaultHttpClient.createHttpContext() minus the
// cookie store.
HttpContext context = new BasicHttpContext();
context.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, getAuthSchemes());
context.setAttribute(ClientContext.COOKIESPEC_REGISTRY, getCookieSpecs());
context.setAttribute(ClientContext.CREDS_PROVIDER, getCredentialsProvider());
return context;
}
};
}
@Override
protected void finalize() throws Throwable {
super.finalize();
if (mLeakedException != null) {
Log.e(TAG, "Leak found", mLeakedException);
mLeakedException = null;
}
}
/**
* Block this thread from executing HTTP requests.
* Used to guard against HTTP requests blocking the main application thread.
*
* @param blocked if HTTP requests run on this thread should be denied
*/
public static void setThreadBlocked(boolean blocked) {
sThreadBlocked.set(blocked);
}
/**
* Modifies a request to indicate to the server that we would like a
* gzipped response. (Uses the "Accept-Encoding" HTTP header.)
*
* @param request the request to modify
* @see #getUngzippedContent
*/
public static void modifyRequestToAcceptGzipResponse(HttpMessage request) {
request.addHeader("Accept-Encoding", "gzip");
}
/**
* Gets the input stream from a response entity. If the entity is gzipped
* then this will get a stream over the uncompressed data.
*
* @param entity the entity whose content should be read
* @return the input stream to read from
* @throws IOException
*/
public static InputStream getUngzippedContent(HttpEntity entity) throws IOException {
InputStream responseStream = entity.getContent();
if (responseStream == null) {
return responseStream;
}
Header header = entity.getContentEncoding();
if (header == null) {
return responseStream;
}
String contentEncoding = header.getValue();
if (contentEncoding == null) {
return responseStream;
}
if (contentEncoding.contains("gzip")) responseStream = new GZIPInputStream(responseStream);
return responseStream;
}
/**
* Release resources associated with this client. You must call this,
* or significant resources (sockets and memory) may be leaked.
*/
public void close() {
if (mLeakedException != null) {
getConnectionManager().shutdown();
mLeakedException = null;
}
}
public HttpParams getParams() {
return delegate.getParams();
}
public ClientConnectionManager getConnectionManager() {
return delegate.getConnectionManager();
}
public HttpResponse execute(HttpUriRequest request) throws IOException {
return delegate.execute(request);
}
public HttpResponse execute(HttpUriRequest request, HttpContext context) throws IOException {
return delegate.execute(request, context);
}
public HttpResponse execute(HttpHost target, HttpRequest request) throws IOException {
return delegate.execute(target, request);
}
public HttpResponse execute(HttpHost target, HttpRequest request,
HttpContext context) throws IOException {
return delegate.execute(target, request, context);
}
public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler) throws IOException {
return delegate.execute(request, responseHandler);
}
public <T> T execute(HttpUriRequest request, ResponseHandler<? extends T> responseHandler, HttpContext context)
throws IOException {
return delegate.execute(request, responseHandler, context);
}
public <T> T execute(HttpHost target, HttpRequest request, ResponseHandler<? extends T> responseHandler)
throws IOException {
return delegate.execute(target, request, responseHandler);
}
public <T> T execute(HttpHost target, HttpRequest request,
ResponseHandler<? extends T> responseHandler,
HttpContext context)
throws IOException {
return delegate.execute(target, request, responseHandler, context);
}
/**
* Compress data to send to server.
* Creates a Http Entity holding the gzipped data.
* The data will not be compressed if it is too short.
*
* @param data The bytes to compress
* @return Entity holding the data
*/
public static AbstractHttpEntity getCompressedEntity(byte data[]) throws IOException {
AbstractHttpEntity entity;
if (data.length < getMinGzipSize()) {
entity = new ByteArrayEntity(data);
} else {
ByteArrayOutputStream arr = new ByteArrayOutputStream();
OutputStream zipper = new GZIPOutputStream(arr);
zipper.write(data);
zipper.close();
entity = new ByteArrayEntity(arr.toByteArray());
entity.setContentEncoding("gzip");
}
return entity;
}
/**
* Retrieves the minimum size for compressing data.
* Shorter data will not be compressed.
*/
public static long getMinGzipSize() {
return DEFAULT_SYNC_MIN_GZIP_BYTES;
}
/* cURL logging support. */
/**
* Logging tag and level.
*/
private static class LoggingConfiguration {
private final String tag;
private final int level;
private LoggingConfiguration(String tag, int level) {
this.tag = tag;
this.level = level;
}
/**
* Returns true if logging is turned on for this configuration.
*/
private boolean isLoggable() {
return Log.isLoggable(tag, level);
}
/**
* Prints a message using this configuration.
*/
private void println(String message) {
Log.println(level, tag, message);
}
}
/**
* cURL logging configuration.
*/
private volatile LoggingConfiguration curlConfiguration;
/**
* Enables cURL request logging for this client.
*
* @param name to log messages with
* @param level at which to log messages (see {@link android.util.Log})
*/
public void enableCurlLogging(String name, int level) {
if (name == null) {
throw new NullPointerException("name");
}
if (level < Log.VERBOSE || level > Log.ASSERT) {
throw new IllegalArgumentException("Level is out of range ["
+ Log.VERBOSE + ".." + Log.ASSERT + "]");
}
curlConfiguration = new LoggingConfiguration(name, level);
}
/**
* Disables cURL logging for this client.
*/
public void disableCurlLogging() {
curlConfiguration = null;
}
/**
* Logs cURL commands equivalent to requests.
*/
private class CurlLogger implements HttpRequestInterceptor {
public void process(HttpRequest request, HttpContext context)
throws HttpException, IOException {
LoggingConfiguration configuration = curlConfiguration;
if (configuration != null
&& configuration.isLoggable()
&& request instanceof HttpUriRequest) {
configuration.println(toCurl((HttpUriRequest) request));
}
}
/**
* Generates a cURL command equivalent to the given request.
*/
private String toCurl(HttpUriRequest request) throws IOException {
StringBuilder builder = new StringBuilder();
builder.append("curl ");
for (Header header : request.getAllHeaders()) {
builder.append("--header \"");
builder.append(header.toString().trim());
builder.append("\" ");
}
URI uri = request.getURI();
// If this is a wrapped request, use the URI from the original
// request instead. getURI() on the wrapper seems to return a
// relative URI. We want an absolute URI.
if (request instanceof RequestWrapper) {
HttpRequest original = ((RequestWrapper) request).getOriginal();
if (original instanceof HttpUriRequest) {
uri = ((HttpUriRequest) original).getURI();
}
}
builder.append('"');
builder.append(uri);
builder.append('"');
if (request instanceof HttpEntityEnclosingRequest) {
HttpEntityEnclosingRequest entityRequest =
(HttpEntityEnclosingRequest) request;
HttpEntity entity = entityRequest.getEntity();
if (entity != null && entity.isRepeatable()) {
if (entity.getContentLength() < 1024) {
ByteArrayOutputStream stream = new ByteArrayOutputStream();
entity.writeTo(stream);
String entityString = stream.toString();
// TODO: Check the content type, too.
builder.append(" --data-ascii \"").append(entityString).append('"');
} else {
builder.append(" [TOO MUCH DATA TO INCLUDE]");
}
}
}
return builder.toString();
}
}
}

View file

@ -0,0 +1,412 @@
/*
* 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.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.AssetFileDescriptor;
import android.content.res.Configuration;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.media.AudioManager;
import android.media.MediaPlayer;
import android.media.MediaPlayer.OnCompletionListener;
import android.net.Uri;
import android.os.Bundle;
import android.os.Message;
import android.preference.PreferenceManager;
import android.text.SpannableStringBuilder;
import android.text.style.UnderlineSpan;
import android.view.Gravity;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import com.google.zxing.Result;
import com.google.zxing.ResultPoint;
import com.google.zxing.client.android.result.ResultHandler;
import com.google.zxing.client.android.result.ResultHandlerFactory;
import com.google.zxing.client.android.result.ResultButtonListener;
import java.io.IOException;
/**
* The barcode reader activity itself. This is loosely based on the CameraPreview
* example included in the Android SDK.
*/
public final class BarcodesCaptureActivity extends Activity implements SurfaceHolder.Callback {
private static final int SETTINGS_ID = Menu.FIRST;
private static final int HELP_ID = Menu.FIRST + 1;
private static final int ABOUT_ID = Menu.FIRST + 2;
private static final int MAX_RESULT_IMAGE_SIZE = 150;
private static final int INTENT_RESULT_DURATION = 1500;
private static final float BEEP_VOLUME = 0.15f;
public BarcodesCaptureActivityHandler mHandler;
private ViewfinderView mViewfinderView;
private View mStatusView;
private View mResultView;
private MediaPlayer mMediaPlayer;
private Result mLastResult;
private boolean mHasSurface;
private boolean mPlayBeep;
private boolean mScanIntent;
private String mDecodeMode;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Window window = getWindow();
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
setContentView(R.layout.capture);
CameraManager.init(getApplication());
mViewfinderView = (ViewfinderView) findViewById(R.id.viewfinder_view);
mResultView = findViewById(R.id.result_view);
mStatusView = findViewById(R.id.status_view);
mHandler = null;
mLastResult = null;
mHasSurface = false;
}
@Override
protected void onResume() {
super.onResume();
SurfaceView surfaceView = (SurfaceView) findViewById(R.id.preview_view);
SurfaceHolder surfaceHolder = surfaceView.getHolder();
if (mHasSurface) {
// The activity was paused but not stopped, so the surface still exists. Therefore
// surfaceCreated() won't be called, so init the camera here.
initCamera(surfaceHolder);
} else {
// Install the callback and wait for surfaceCreated() to init the camera.
surfaceHolder.addCallback(this);
surfaceHolder.setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
}
Intent intent = getIntent();
if (intent != null && (intent.getAction().equals(Intents.Scan.ACTION) ||
intent.getAction().equals(Intents.Scan.DEPRECATED_ACTION))) {
mScanIntent = true;
mDecodeMode = intent.getStringExtra(Intents.Scan.MODE);
resetStatusView();
} else {
mScanIntent = false;
mDecodeMode = null;
if (mLastResult == null) {
resetStatusView();
}
}
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this);
mPlayBeep = prefs.getBoolean(BarcodesPreferenceActivity.KEY_PLAY_BEEP, true);
initBeepSound();
}
@Override
protected void onPause() {
super.onPause();
if (mHandler != null) {
mHandler.quitSynchronously();
mHandler = null;
}
CameraManager.get().closeDriver();
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
if (mScanIntent) {
setResult(RESULT_CANCELED);
finish();
return true;
} else if (mLastResult != null) {
resetStatusView();
mHandler.sendEmptyMessage(R.id.restart_preview);
return true;
}
} else if (keyCode == KeyEvent.KEYCODE_FOCUS || keyCode == KeyEvent.KEYCODE_CAMERA) {
// Handle these events so they don't launch the Camera app
return true;
}
return super.onKeyDown(keyCode, event);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
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)
.setIcon(android.R.drawable.ic_menu_help);
menu.add(0, ABOUT_ID, 0, R.string.menu_about)
.setIcon(android.R.drawable.ic_menu_info_details);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case SETTINGS_ID: {
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClassName(this, BarcodesPreferenceActivity.class.getName());
startActivity(intent);
break;
}
case HELP_ID: {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.title_help);
builder.setMessage(R.string.msg_help);
builder.setPositiveButton(R.string.button_ok, null);
builder.show();
break;
}
case ABOUT_ID: {
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(R.string.title_about);
builder.setMessage(getString(R.string.msg_about) + "\n\n" + getString(R.string.zxing_url));
builder.setIcon(R.drawable.zxing_icon);
builder.setPositiveButton(R.string.button_open_browser, mAboutListener);
builder.setNegativeButton(R.string.button_cancel, null);
builder.show();
break;
}
}
return super.onOptionsItemSelected(item);
}
@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 final DialogInterface.OnClickListener mAboutListener = new DialogInterface.OnClickListener() {
public void onClick(android.content.DialogInterface dialogInterface, int i) {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(getString(R.string.zxing_url)));
startActivity(intent);
}
};
public void surfaceCreated(SurfaceHolder holder) {
if (!mHasSurface) {
mHasSurface = true;
initCamera(holder);
}
}
public void surfaceDestroyed(SurfaceHolder holder) {
mHasSurface = false;
}
public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
}
/**
* A valid barcode has been found, so give an indication of success and show the results.
*
* @param rawResult The contents of the barcode.
* @param barcode A greyscale bitmap of the camera data which was decoded.
* @param duration How long the decoding took in milliseconds.
*/
public void handleDecode(Result rawResult, Bitmap barcode, int duration) {
mLastResult = rawResult;
playBeepSound();
drawResultPoints(barcode, rawResult);
if (mScanIntent) {
handleDecodeForScanIntent(rawResult, barcode, duration);
} else {
mStatusView.setVisibility(View.GONE);
mViewfinderView.setVisibility(View.GONE);
mResultView.setVisibility(View.VISIBLE);
ImageView barcodeImageView = (ImageView) findViewById(R.id.barcode_image_view);
barcodeImageView.setMaxWidth(MAX_RESULT_IMAGE_SIZE);
barcodeImageView.setMaxHeight(MAX_RESULT_IMAGE_SIZE);
barcodeImageView.setImageBitmap(barcode);
TextView formatTextView = (TextView) findViewById(R.id.format_text_view);
formatTextView.setText(getString(R.string.msg_default_format) + ": " +
rawResult.getBarcodeFormat().toString());
ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult);
TextView typeTextView = (TextView) findViewById(R.id.type_text_view);
typeTextView.setText(getString(R.string.msg_default_type) + ": " +
resultHandler.getType().toString());
TextView contentsTextView = (TextView) findViewById(R.id.contents_text_view);
CharSequence title = getString(resultHandler.getDisplayTitle());
SpannableStringBuilder styled = new SpannableStringBuilder(title + "\n\n");
styled.setSpan(new UnderlineSpan(), 0, title.length(), 0);
styled.append(resultHandler.getDisplayContents());
contentsTextView.setText(styled);
int buttonCount = resultHandler.getButtonCount();
ViewGroup buttonView = (ViewGroup) findViewById(R.id.result_button_view);
buttonView.requestFocus();
for (int x = 0; x < ResultHandler.MAX_BUTTON_COUNT; x++) {
Button button = (Button) buttonView.getChildAt(x);
if (x < buttonCount) {
button.setVisibility(View.VISIBLE);
button.setText(resultHandler.getButtonText(x));
button.setOnClickListener(new ResultButtonListener(resultHandler, x));
} else {
button.setVisibility(View.GONE);
}
}
}
}
/**
* Superimpose a line for 1D or dots for 2D to highlight the key features of the barcode.
*
* @param barcode A bitmap of the captured image.
* @param rawResult The decoded results which contains the points to draw.
*/
private void drawResultPoints(Bitmap barcode, Result rawResult) {
ResultPoint[] points = rawResult.getResultPoints();
if (points != null && points.length > 0) {
Canvas canvas = new Canvas(barcode);
Paint paint = new Paint();
paint.setColor(getResources().getColor(R.color.result_image_border));
paint.setStrokeWidth(3);
paint.setStyle(Paint.Style.STROKE);
Rect border = new Rect(2, 2, barcode.getWidth() - 2, barcode.getHeight() - 2);
canvas.drawRect(border, paint);
paint.setColor(getResources().getColor(R.color.result_points));
if (points.length == 2) {
paint.setStrokeWidth(4);
canvas.drawLine(points[0].getX(), points[0].getY(), points[1].getX(),
points[1].getY(), paint);
} else {
paint.setStrokeWidth(10);
for (int x = 0; x < points.length; x++) {
canvas.drawPoint(points[x].getX(), points[x].getY(), paint);
}
}
}
}
private void handleDecodeForScanIntent(Result rawResult, Bitmap barcode, int duration) {
mViewfinderView.drawResultBitmap(barcode);
// Since this message will only be shown for a second, just tell the user what kind of
// barcode was found (e.g. contact info) rather than the full contents, which they won't
// have time to read.
ResultHandler resultHandler = ResultHandlerFactory.makeResultHandler(this, rawResult);
TextView textView = (TextView) findViewById(R.id.status_text_view);
textView.setGravity(Gravity.CENTER);
textView.setTextSize(18.0f);
textView.setText(getString(resultHandler.getDisplayTitle()));
mStatusView.setBackgroundColor(getResources().getColor(R.color.transparent));
// Hand back whatever action they requested - this can be changed to Intents.Scan.ACTION when
// the deprecated intent is retired.
Intent intent = new Intent(getIntent().getAction());
intent.putExtra(Intents.Scan.RESULT, rawResult.toString());
intent.putExtra(Intents.Scan.RESULT_FORMAT, rawResult.getBarcodeFormat().toString());
Message message = Message.obtain(mHandler, R.id.return_scan_result);
message.obj = intent;
mHandler.sendMessageDelayed(message, INTENT_RESULT_DURATION);
}
/**
* Creates the beep MediaPlayer in advance so that the sound can be triggered with the least
* latency possible.
*/
private void initBeepSound() {
if (mPlayBeep && mMediaPlayer == null) {
mMediaPlayer = new MediaPlayer();
mMediaPlayer.setAudioStreamType(AudioManager.STREAM_SYSTEM);
mMediaPlayer.setOnCompletionListener(mBeepListener);
AssetFileDescriptor file = getResources().openRawResourceFd(R.raw.beep);
try {
mMediaPlayer.setDataSource(file.getFileDescriptor(), file.getStartOffset(),
file.getLength());
file.close();
mMediaPlayer.setVolume(BEEP_VOLUME, BEEP_VOLUME);
mMediaPlayer.prepare();
} catch (IOException e) {
mMediaPlayer = null;
}
}
}
private void playBeepSound() {
if (mPlayBeep && mMediaPlayer != null) {
mMediaPlayer.start();
}
}
private void initCamera(SurfaceHolder surfaceHolder) {
CameraManager.get().openDriver(surfaceHolder);
if (mHandler == null) {
boolean beginScanning = mLastResult == null;
mHandler = new BarcodesCaptureActivityHandler(this, mDecodeMode, beginScanning);
}
}
/**
* When the beep has finished playing, rewind to queue up another one.
*/
private final OnCompletionListener mBeepListener = new OnCompletionListener() {
public void onCompletion(MediaPlayer mediaPlayer) {
mediaPlayer.seekTo(0);
}
};
private void resetStatusView() {
mResultView.setVisibility(View.GONE);
mStatusView.setVisibility(View.VISIBLE);
mStatusView.setBackgroundColor(getResources().getColor(R.color.status_view));
mViewfinderView.setVisibility(View.VISIBLE);
TextView textView = (TextView) findViewById(R.id.status_text_view);
textView.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
textView.setTextSize(14.0f);
textView.setText(R.string.msg_default_status);
mLastResult = null;
}
public void drawViewfinder() {
mViewfinderView.drawViewfinder();
}
}

View file

@ -0,0 +1,111 @@
/*
* 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.graphics.Bitmap;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import com.google.zxing.Result;
/**
* This class handles all the messaging which comprises the state machine for capture.
*/
public class BarcodesCaptureActivityHandler extends Handler {
private final BarcodesCaptureActivity mActivity;
private final DecodeThread mDecodeThread;
private State mState;
private enum State {
PREVIEW,
SUCCESS,
DONE
}
BarcodesCaptureActivityHandler(BarcodesCaptureActivity activity, String decodeMode,
boolean beginScanning) {
mActivity = activity;
mDecodeThread = new DecodeThread(activity, decodeMode);
mDecodeThread.start();
mState = State.SUCCESS;
// Start ourselves capturing previews and decoding.
CameraManager.get().startPreview();
if (beginScanning) {
restartPreviewAndDecode();
}
}
public void handleMessage(Message message) {
switch (message.what) {
case R.id.auto_focus:
// When one auto focus pass finishes, start another. This is the closest thing to
// continuous AF. It does seem to hunt a bit, but I'm not sure what else to do.
if (mState == State.PREVIEW) {
CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
}
break;
case R.id.restart_preview:
restartPreviewAndDecode();
break;
case R.id.decode_succeeded:
mState = State.SUCCESS;
Bundle bundle = message.getData();
Bitmap barcode = bundle.getParcelable(DecodeThread.BARCODE_BITMAP);
int duration = message.arg1;
mActivity.handleDecode((Result) message.obj, barcode, duration);
break;
case R.id.decode_failed:
// We're decoding as fast as possible, so when one decode fails, start another.
mState = State.PREVIEW;
CameraManager.get().requestPreviewFrame(mDecodeThread.mHandler, R.id.decode);
break;
case R.id.return_scan_result:
mActivity.setResult(Activity.RESULT_OK, (Intent) message.obj);
mActivity.finish();
break;
}
}
public void quitSynchronously() {
mState = State.DONE;
CameraManager.get().stopPreview();
Message quit = Message.obtain(mDecodeThread.mHandler, R.id.quit);
quit.sendToTarget();
try {
mDecodeThread.join();
} catch (InterruptedException e) {
}
// Be absolutely sure we don't send any queued up messages
removeMessages(R.id.decode_succeeded);
removeMessages(R.id.decode_failed);
}
private void restartPreviewAndDecode() {
if (mState == State.SUCCESS) {
mState = State.PREVIEW;
CameraManager.get().requestPreviewFrame(mDecodeThread.mHandler, R.id.decode);
CameraManager.get().requestAutoFocus(this, R.id.auto_focus);
mActivity.drawViewfinder();
}
}
}

View file

@ -0,0 +1,138 @@
/*
* 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.ViewTreeObserver.OnGlobalLayoutListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
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.
*/
public class BarcodesEncodeActivity extends Activity {
private QRCodeEncoder mQRCodeEncoder;
private ProgressDialog mProgressDialog;
private boolean mFirstLayout;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Intent intent = getIntent();
if (intent != null && (intent.getAction().equals(Intents.Encode.ACTION) ||
intent.getAction().equals(Intents.Encode.DEPRECATED_ACTION))) {
setContentView(R.layout.encode);
} else {
finish();
}
}
@Override
protected void onResume() {
super.onResume();
LinearLayout layout = (LinearLayout) findViewById(R.id.encode_view);
layout.getViewTreeObserver().addOnGlobalLayoutListener(mLayoutListener);
mFirstLayout = true;
}
/**
* This needs to be delayed until after the first layout so that the view dimensions will be
* available.
*/
public OnGlobalLayoutListener mLayoutListener = new OnGlobalLayoutListener() {
public void onGlobalLayout() {
if (mFirstLayout) {
LinearLayout layout = (LinearLayout) findViewById(R.id.encode_view);
int width = layout.getWidth();
int height = layout.getHeight();
int smallerDimension = (width < height) ? width : height;
Intent intent = getIntent();
try {
mQRCodeEncoder = new QRCodeEncoder(BarcodesEncodeActivity.this, intent);
setTitle(getString(R.string.app_name) + " - " + mQRCodeEncoder.getTitle());
mQRCodeEncoder.requestBarcode(mHandler, smallerDimension);
mProgressDialog = ProgressDialog.show(BarcodesEncodeActivity.this, null,
getString(R.string.msg_encode_in_progress), true, true, mCancelListener);
} catch (IllegalArgumentException e) {
showErrorMessage(R.string.msg_encode_contents_failed);
}
mFirstLayout = false;
}
}
};
public Handler mHandler = new Handler() {
public void handleMessage(Message message) {
switch (message.what) {
case R.id.encode_succeeded:
mProgressDialog.dismiss();
mProgressDialog = 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(mQRCodeEncoder.getDisplayContents());
mQRCodeEncoder = null;
break;
case R.id.encode_failed:
showErrorMessage(R.string.msg_encode_barcode_failed);
mQRCodeEncoder = null;
break;
}
}
};
private void showErrorMessage(int message) {
if (mProgressDialog != null) {
mProgressDialog.dismiss();
mProgressDialog = null;
}
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(message);
builder.setPositiveButton(R.string.button_ok, mClickListener);
builder.show();
}
private OnClickListener mClickListener = new OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
finish();
}
};
private OnCancelListener mCancelListener = new OnCancelListener() {
public void onCancel(DialogInterface dialog) {
finish();
}
};
}

View file

@ -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;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.PreferenceScreen;
public class BarcodesPreferenceActivity extends android.preference.PreferenceActivity
implements OnSharedPreferenceChangeListener {
static final String KEY_DECODE_1D = "preferences_decode_1D";
static final String KEY_DECODE_QR = "preferences_decode_QR";
static final String KEY_PLAY_BEEP = "preferences_play_beep";
CheckBoxPreference mDecode1D;
CheckBoxPreference mDecodeQR;
CheckBoxPreference mPlayBeep;
@Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(R.xml.preferences);
PreferenceScreen preferences = getPreferenceScreen();
preferences.getSharedPreferences().registerOnSharedPreferenceChangeListener(this);
mDecode1D = (CheckBoxPreference) preferences.findPreference(KEY_DECODE_1D);
mDecodeQR = (CheckBoxPreference) preferences.findPreference(KEY_DECODE_QR);
mPlayBeep = (CheckBoxPreference) preferences.findPreference(KEY_PLAY_BEEP);
}
// Prevent the user from turning off both decode options
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
if (key.equals(KEY_DECODE_1D)) {
mDecodeQR.setEnabled(mDecode1D.isChecked());
mDecodeQR.setChecked(true);
} else if (key.equals(KEY_DECODE_QR)) {
mDecode1D.setEnabled(mDecodeQR.isChecked());
mDecode1D.setChecked(true);
}
}
}

View file

@ -0,0 +1,219 @@
/*
* 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.Point;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.Handler;
import android.os.Message;
import android.util.Log;
import android.view.Display;
import android.view.SurfaceHolder;
import android.view.WindowManager;
import com.google.zxing.ResultPoint;
/**
* This object wraps the Camera service object and expects to be the only one talking to it. The
* implementation encapsulates the steps needed to take preview-sized images, which are used for
* both preview and decoding.
*/
final class CameraManager {
private static final String TAG = "CameraManager";
private static CameraManager mCameraManager;
private Camera mCamera;
private final Context mContext;
private Point mScreenResolution;
private Rect mFramingRect;
private Handler mPreviewHandler;
private int mPreviewMessage;
private Handler mAutoFocusHandler;
private int mAutoFocusMessage;
private boolean mInitialized;
private boolean mPreviewing;
public static void init(Context context) {
if (mCameraManager == null) {
mCameraManager = new CameraManager(context);
}
}
public static CameraManager get() {
return mCameraManager;
}
private CameraManager(Context context) {
mContext = context;
mCamera = null;
mInitialized = false;
mPreviewing = false;
}
public void openDriver(SurfaceHolder holder) {
if (mCamera == null) {
mCamera = Camera.open();
mCamera.setPreviewDisplay(holder);
if (!mInitialized) {
mInitialized = true;
getScreenResolution();
}
setCameraParameters();
}
}
public void closeDriver() {
if (mCamera != null) {
mCamera.release();
mCamera = null;
}
}
public void startPreview() {
if (mCamera != null && !mPreviewing) {
mCamera.startPreview();
mPreviewing = true;
}
}
public void stopPreview() {
if (mCamera != null && mPreviewing) {
mCamera.setPreviewCallback(null);
mCamera.stopPreview();
mPreviewHandler = null;
mAutoFocusHandler = null;
mPreviewing = false;
}
}
/**
* A single preview frame will be returned to the handler supplied. The data will arrive as byte[]
* in the message.obj field, with width and height encoded as message.arg1 and message.arg2,
* respectively.
*
* @param handler The handler to send the message to.
* @param message The what field of the message to be sent.
*/
public void requestPreviewFrame(Handler handler, int message) {
if (mCamera != null && mPreviewing) {
mPreviewHandler = handler;
mPreviewMessage = message;
mCamera.setPreviewCallback(previewCallback);
}
}
public void requestAutoFocus(Handler handler, int message) {
if (mCamera != null && mPreviewing) {
mAutoFocusHandler = handler;
mAutoFocusMessage = message;
mCamera.autoFocus(autoFocusCallback);
}
}
/**
* Calculates the framing rect which the UI should draw to show the user where to place the
* barcode. The actual captured image should be a bit larger than indicated because they might
* frame the shot too tightly. This target helps with alignment as well as forces the user to hold
* the device far enough away to ensure the image will be in focus.
*
* @return The rectangle to draw on screen in window coordinates.
*/
public Rect getFramingRect() {
if (mFramingRect == null) {
int size = ((mScreenResolution.x < mScreenResolution.y) ? mScreenResolution.x :
mScreenResolution.y) * 3 / 4;
int leftOffset = (mScreenResolution.x - size) / 2;
int topOffset = (mScreenResolution.y - size) / 2;
mFramingRect = new Rect(leftOffset, topOffset, leftOffset + size, topOffset + size);
Log.v(TAG, "Calculated framing rect: " + mFramingRect);
}
return mFramingRect;
}
/**
* Converts the result points from still resolution coordinates to screen coordinates.
*
* @param points The points returned by the Reader subclass through Result.getResultPoints().
* @return An array of Points scaled to the size of the framing rect and offset appropriately
* so they can be drawn in screen coordinates.
*/
public Point[] convertResultPoints(ResultPoint[] points) {
Rect frame = getFramingRect();
int count = points.length;
Point[] output = new Point[count];
for (int x = 0; x < count; x++) {
output[x] = new Point();
output[x].x = frame.left + (int) (points[x].getX() + 0.5f);
output[x].y = frame.top + (int) (points[x].getY() + 0.5f);
}
return output;
}
/**
* Preview frames are delivered here, which we pass on to the registered handler. Make sure to
* clear the handler so it will only receive one message.
*/
private final Camera.PreviewCallback previewCallback = new Camera.PreviewCallback() {
public void onPreviewFrame(byte[] data, Camera camera) {
camera.setPreviewCallback(null);
if (mPreviewHandler != null) {
Message message = mPreviewHandler.obtainMessage(mPreviewMessage, mScreenResolution.x,
mScreenResolution.y, data);
message.sendToTarget();
mPreviewHandler = null;
}
}
};
private final Camera.AutoFocusCallback autoFocusCallback = new Camera.AutoFocusCallback() {
public void onAutoFocus(boolean success, Camera camera) {
if (mAutoFocusHandler != null) {
Message message = mAutoFocusHandler.obtainMessage(mAutoFocusMessage, success);
// Simulate continuous autofocus by sending a focus request every second.
mAutoFocusHandler.sendMessageDelayed(message, 1000);
mAutoFocusHandler = null;
}
}
};
/**
* Sets the camera up to take preview images which are used for both preview and decoding. We're
* counting on the default YUV420 semi-planar data. If that changes in the future, we'll need to
* specify it explicitly with setPreviewFormat().
*/
private void setCameraParameters() {
Camera.Parameters parameters = mCamera.getParameters();
parameters.setPreviewSize(mScreenResolution.x, mScreenResolution.y);
mCamera.setParameters(parameters);
Log.v(TAG, "Setting params for preview: width " + mScreenResolution.x + " height " +
mScreenResolution.y);
}
private Point getScreenResolution() {
if (mScreenResolution == null) {
WindowManager manager = (WindowManager) mContext.getSystemService(Context.WINDOW_SERVICE);
Display display = manager.getDefaultDisplay();
mScreenResolution = new Point(display.getWidth(), display.getHeight());
}
return mScreenResolution;
}
}

View file

@ -0,0 +1,82 @@
/*
* 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;
public final class Contents {
/**
* All the formats we know about.
*/
public static final class Format {
public static final String UPC_A = "UPC_A";
public static final String UPC_E = "UPC_E";
public static final String EAN_8 = "EAN_8";
public static final String EAN_13 = "EAN_13";
public static final String CODE_39 = "CODE_39";
public static final String CODE_128 = "CODE_128";
public static final String QR_CODE = "QR_CODE";
}
public static final class Type {
/**
* Plain text. Use Intent.putExtra(DATA, string). This can be used for URLs too, but string
* must include "http://" or "https://".
*/
public static final String TEXT = "TEXT_TYPE";
/**
* An email type. Use Intent.putExtra(DATA, string) where string is the email address.
*/
public static final String EMAIL = "EMAIL_TYPE";
/**
* Use Intent.putExtra(DATA, string) where string is the phone number to call.
*/
public static final String PHONE = "PHONE_TYPE";
/**
* An SMS type. Use Intent.putExtra(DATA, string) where string is the number to SMS.
*/
public static final String SMS = "SMS_TYPE";
/**
* A contact. Send a request to encode it as follows:
* <p/>
* import android.provider.Contacts;
* <p/>
* Intent intent = new Intent(Intents.Encode.ACTION);
* intent.putExtra(Intents.Encode.TYPE, CONTACT);
* Bundle bundle = new Bundle();
* bundle.putString(Contacts.Intents.Insert.NAME, "Jenny");
* bundle.putString(Contacts.Intents.Insert.PHONE, "8675309");
* bundle.putString(Contacts.Intents.Insert.EMAIL, "jenny@the80s.com");
* intent.putExtra(Intents.Encode.DATA, bundle);
*/
public static final String CONTACT = "CONTACT_TYPE";
/**
* A geographic location. Use as follows:
* Bundle bundle = new Bundle();
* bundle.putFloat("LAT", latitude);
* bundle.putFloat("LONG", longitude);
* intent.putExtra(Intents.Encode.DATA, bundle);
*/
public static final String LOCATION = "LOCATION_TYPE";
}
}

View file

@ -0,0 +1,167 @@
/*
* 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.SharedPreferences;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import android.preference.PreferenceManager;
import com.google.zxing.BarcodeFormat;
import com.google.zxing.DecodeHintType;
import com.google.zxing.MultiFormatReader;
import com.google.zxing.ReaderException;
import com.google.zxing.Result;
import java.util.Date;
import java.util.Hashtable;
import java.util.Vector;
/**
* This thread does all the heavy lifting of decoding the images.
*/
final class DecodeThread extends Thread {
public static final String BARCODE_BITMAP = "barcode_bitmap";
public Handler mHandler;
private BarcodesCaptureActivity mActivity;
private MultiFormatReader mMultiFormatReader;
DecodeThread(BarcodesCaptureActivity activity, String mode) {
mActivity = activity;
mMultiFormatReader = new MultiFormatReader();
// The prefs can't change while the thread is running, so pick them up once here.
if (mode == null || mode.length() == 0) {
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity);
boolean decode1D = prefs.getBoolean(BarcodesPreferenceActivity.KEY_DECODE_1D, true);
boolean decodeQR = prefs.getBoolean(BarcodesPreferenceActivity.KEY_DECODE_QR, true);
if (decode1D && decodeQR) {
setDecodeAllMode();
} else if (decode1D) {
setDecode1DMode();
} else if (decodeQR) {
setDecodeQRMode();
}
} else {
if (mode.equals(Intents.Scan.PRODUCT_MODE)) {
setDecodeProductMode();
} else if (mode.equals(Intents.Scan.ONE_D_MODE)) {
setDecode1DMode();
} else if (mode.equals(Intents.Scan.QR_CODE_MODE)) {
setDecodeQRMode();
} else {
setDecodeAllMode();
}
}
}
@Override
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message message) {
switch (message.what) {
case R.id.decode:
decode((byte[]) message.obj, message.arg1, message.arg2);
break;
case R.id.quit:
Looper.myLooper().quit();
break;
}
}
};
Looper.loop();
}
private void setDecodeProductMode() {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>();
vector.addElement(BarcodeFormat.UPC_A);
vector.addElement(BarcodeFormat.UPC_E);
vector.addElement(BarcodeFormat.EAN_13);
vector.addElement(BarcodeFormat.EAN_8);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
mMultiFormatReader.setHints(hints);
}
// TODO: This is fragile in case we add new formats. It would be better to have a new enum
// value which represented all 1D formats.
private void setDecode1DMode() {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>();
vector.addElement(BarcodeFormat.UPC_A);
vector.addElement(BarcodeFormat.UPC_E);
vector.addElement(BarcodeFormat.EAN_13);
vector.addElement(BarcodeFormat.EAN_8);
vector.addElement(BarcodeFormat.CODE_39);
vector.addElement(BarcodeFormat.CODE_128);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
mMultiFormatReader.setHints(hints);
}
private void setDecodeQRMode() {
Hashtable<DecodeHintType, Object> hints = new Hashtable<DecodeHintType, Object>(3);
Vector<BarcodeFormat> vector = new Vector<BarcodeFormat>();
vector.addElement(BarcodeFormat.QR_CODE);
hints.put(DecodeHintType.POSSIBLE_FORMATS, vector);
mMultiFormatReader.setHints(hints);
}
private void setDecodeAllMode() {
mMultiFormatReader.setHints(null);
}
/**
* Decode the data within the viewfinder rectangle, and time how long it took. For efficiency,
* reuse the same reader objects from one decode to the next.
*
* @param data The YUV preview frame.
* @param width The width of the preview frame.
* @param height The height of the preview frame.
*/
private void decode(byte[] data, int width, int height) {
Date startDate = new Date();
boolean success;
Result rawResult = null;
YUVMonochromeBitmapSource source = new YUVMonochromeBitmapSource(data, width, height,
CameraManager.get().getFramingRect());
try {
rawResult = mMultiFormatReader.decodeWithState(source);
success = true;
} catch (ReaderException e) {
success = false;
}
Date endDate = new Date();
if (success) {
Message message = Message.obtain(mActivity.mHandler, R.id.decode_succeeded, rawResult);
message.arg1 = (int) (endDate.getTime() - startDate.getTime());
Bundle bundle = new Bundle();
bundle.putParcelable(BARCODE_BITMAP, source.renderToBitmap());
message.setData(bundle);
message.sendToTarget();
} else {
Message message = Message.obtain(mActivity.mHandler, R.id.decode_failed);
message.arg1 = (int) (endDate.getTime() - startDate.getTime());
message.sendToTarget();
}
}
}

View file

@ -0,0 +1,112 @@
/*
* 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;
public final class Intents {
public static final class Scan {
/**
* Send this intent to open the Barcodes app in scanning mode, find a barcode, and return
* the results.
*/
public static final String ACTION = "com.google.zxing.client.android.SCAN";
// For compatibility only - do not use in new code, this will go away!
public static final String DEPRECATED_ACTION = "com.android.barcodes.SCAN";
/**
* By default, sending Scan.ACTION will decode all barcodes that we understand. However it
* may be useful to limit scanning to certain formats. Use Intent.putExtra(MODE, value) with
* one of the values below (optional).
*/
public static final String MODE = "SCAN_MODE";
/**
* Decode only UPC and EAN barcodes. This is the right choice for shopping apps which get
* prices, reviews, etc. for products.
*/
public static final String PRODUCT_MODE = "PRODUCT_MODE";
/**
* Decode only 1D barcodes (currently UPC, EAN, Code 39, and Code 128).
*/
public static final String ONE_D_MODE = "ONE_D_MODE";
/**
* Decode only QR codes.
*/
public static final String QR_CODE_MODE = "QR_CODE_MODE";
/**
* If a barcode is found, Barcodes returns RESULT_OK to onActivityResult() of the app which
* requested the scan via startSubActivity(). The barcodes contents can be retrieved with
* intent.getStringExtra(RESULT). If the user presses Back, the result code will be
* RESULT_CANCELED.
*/
public static final String RESULT = "SCAN_RESULT";
/**
* Call intent.getStringExtra(RESULT_FORMAT) to determine which barcode format was found.
* See Contents.Format for possible values.
*/
public static final String RESULT_FORMAT = "SCAN_RESULT_FORMAT";
}
public static final class Encode {
/**
* Send this intent to encode a piece of data as a QR code and display it full screen, so
* that another person can scan the barcode from your screen.
*/
public static final String ACTION = "com.google.zxing.client.android.ENCODE";
// For compatibility only - do not use in new code, this will go away!
public static final String DEPRECATED_ACTION = "com.android.barcodes.ENCODE";
/**
* The data to encode. Use Intent.putExtra(DATA, data) where data is either a String or a
* Bundle, depending on the type specified. See Contents for details.
*/
public static final String DATA = "ENCODE_DATA";
/**
* The type of data being supplied. Use Intent.putExtra(TYPE, type) with one of
* Contents.Type.
*/
public static final String TYPE = "ENCODE_TYPE";
}
public static final class SearchBookContents {
/**
* Use Google Book Search to search the contents of the book provided.
*/
public static final String ACTION = "com.google.zxing.client.android.SEARCH_BOOK_CONTENTS";
// For compatibility only - do not use in new code, this will go away!
public static final String DEPRECATED_ACTION = "com.android.barcodes.SEARCH_BOOK_CONTENTS";
/**
* The book to search, identified by ISBN number.
*/
public static final String ISBN = "ISBN";
/**
* An optional field which is the text to search for.
*/
public static final String QUERY = "QUERY";
}
}

View file

@ -0,0 +1,182 @@
/*
* 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.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.provider.Contacts;
import android.util.Log;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import java.net.URI;
public class QRCodeEncoder {
private static final String TAG = "QRCodeEncoder";
private static final String CHART_SERVER_URL = "//chart.apis.google.com/chart?cht=qr&chs=";
private Activity mActivity;
private String mContents;
private String mDisplayContents;
private String mTitle;
private NetworkThread mNetworkThread;
private String mUserAgent;
public QRCodeEncoder(Activity activity, Intent intent) {
mActivity = activity;
if (!encodeContents(intent)) {
throw new IllegalArgumentException("No valid data to encode.");
}
mUserAgent = mActivity.getString(R.string.zxing_user_agent);
}
// Once the core ZXing library supports encoding, we'll be able to generate the bitmap
// synchronously. For now, it's a network request, so it's handled on a thread.
public void requestBarcode(Handler handler, int pixelResolution) {
mNetworkThread = new NetworkThread(mContents, handler, pixelResolution);
mNetworkThread.start();
}
public String getContents() {
return mContents;
}
public String getDisplayContents() {
return mDisplayContents;
}
public String getTitle() {
return mTitle;
}
// Perhaps the string encoding should live in the core ZXing library too.
private boolean encodeContents(Intent intent) {
if (intent == null) return false;
String type = intent.getStringExtra(Intents.Encode.TYPE);
if (type == null || type.length() == 0) return false;
if (type.equals(Contents.Type.TEXT)) {
String string = intent.getStringExtra(Intents.Encode.DATA);
if (string != null && string.length() > 0) {
mContents = string;
mDisplayContents = string;
mTitle = mActivity.getString(R.string.contents_text);
}
} else if (type.equals(Contents.Type.EMAIL)) {
String string = intent.getStringExtra(Intents.Encode.DATA);
if (string != null && string.length() > 0) {
mContents = "mailto:" + string;
mDisplayContents = string;
mTitle = mActivity.getString(R.string.contents_email);
}
} else if (type.equals(Contents.Type.PHONE)) {
String string = intent.getStringExtra(Intents.Encode.DATA);
if (string != null && string.length() > 0) {
mContents = "tel:" + string;
mDisplayContents = string;
mTitle = mActivity.getString(R.string.contents_phone);
}
} else if (type.equals(Contents.Type.SMS)) {
String string = intent.getStringExtra(Intents.Encode.DATA);
if (string != null && string.length() > 0) {
mContents = "sms:" + string;
mDisplayContents = string;
mTitle = mActivity.getString(R.string.contents_sms);
}
} else if (type.equals(Contents.Type.CONTACT)) {
Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
if (bundle != null) {
String name = bundle.getString(Contacts.Intents.Insert.NAME);
if (name != null && !name.equals("")) {
mContents = "MECARD:N:" + name + ";";
mDisplayContents = name;
String phone = bundle.getString(Contacts.Intents.Insert.PHONE);
if (phone != null && !phone.equals("")) {
mContents += "TEL:" + phone + ";";
mDisplayContents += "\n" + phone;
}
String email = bundle.getString(Contacts.Intents.Insert.EMAIL);
if (email != null && !email.equals("")) {
mContents += "EMAIL:" + email + ";";
mDisplayContents += "\n" + email;
}
mContents += ";";
mTitle = mActivity.getString(R.string.contents_contact);
}
}
} else if (type.equals(Contents.Type.LOCATION)) {
Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
if (bundle != null) {
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) {
mContents = "geo:" + latitude + "," + longitude;
mDisplayContents = latitude + "," + longitude;
mTitle = mActivity.getString(R.string.contents_location);
}
}
}
return mContents != null && mContents.length() > 0;
}
private class NetworkThread extends Thread {
private String mContents;
private Handler mHandler;
private int mPixelResolution;
public NetworkThread(String contents, Handler handler, int pixelResolution) {
mContents = contents;
mHandler = handler;
mPixelResolution = pixelResolution;
}
public void run() {
String url = CHART_SERVER_URL + mPixelResolution + "x" + mPixelResolution + "&chl=" +
mContents;
try {
URI uri = new URI("http", url, null);
HttpGet get = new HttpGet(uri);
AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
HttpResponse response = client.execute(get);
HttpEntity entity = response.getEntity();
Bitmap image = BitmapFactory.decodeStream(entity.getContent());
if (image != null) {
Message message = Message.obtain(mHandler, R.id.encode_succeeded);
message.obj = image;
message.sendToTarget();
} else {
Log.e(TAG, "Could not decode png from the network");
Message message = Message.obtain(mHandler, R.id.encode_failed);
message.sendToTarget();
}
} catch (Exception e) {
Log.e(TAG, e.toString());
Message message = Message.obtain(mHandler, R.id.encode_failed);
message.sendToTarget();
}
}
}
}

View file

@ -0,0 +1,317 @@
/*
* 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.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;
public class SearchBookContentsActivity extends Activity {
private static final String TAG = "SearchBookContents";
// 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.
private static final String BOOK_SEARCH_URL = "//www.google.com/books?vid=isbn";
private static final String BOOK_SEARCH_COMMAND = "&jscmd=SearchWithinVolume2&q=";
private NetworkThread mNetworkThread;
private String mISBN;
private EditText mQueryTextView;
private Button mQueryButton;
private ListView mResultListView;
private TextView mHeaderView;
private String mUserAgent;
@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) &&
!intent.getAction().equals(Intents.SearchBookContents.DEPRECATED_ACTION))) {
finish();
return;
}
mISBN = intent.getStringExtra(Intents.SearchBookContents.ISBN);
setTitle(getString(R.string.sbc_name) + ": ISBN " + mISBN);
setContentView(R.layout.search_book_contents);
mQueryTextView = (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
mQueryTextView.setText(initialQuery);
}
mQueryTextView.setOnKeyListener(mKeyListener);
mQueryButton = (Button) findViewById(R.id.query_button);
mQueryButton.setOnClickListener(mButtonListener);
mResultListView = (ListView) findViewById(R.id.result_list_view);
LayoutInflater factory = LayoutInflater.from(this);
mHeaderView = (TextView) factory.inflate(R.layout.search_book_contents_header,
mResultListView, false);
mResultListView.addHeaderView(mHeaderView);
mUserAgent = getString(R.string.zxing_user_agent);
}
@Override
protected void onResume() {
super.onResume();
mQueryTextView.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);
}
public Handler mHandler = new Handler() {
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();
mHeaderView.setText(R.string.msg_sbc_failed);
break;
}
}
};
private void resetForNewQuery() {
mNetworkThread = null;
mQueryTextView.setEnabled(true);
mQueryTextView.selectAll();
mQueryButton.setEnabled(true);
}
private Button.OnClickListener mButtonListener = new Button.OnClickListener() {
public void onClick(View view) {
launchSearch();
}
};
private View.OnKeyListener mKeyListener = new View.OnKeyListener() {
public boolean onKey(View view, int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_ENTER) {
launchSearch();
return true;
}
return false;
}
};
private void launchSearch() {
if (mNetworkThread == null) {
String query = mQueryTextView.getText().toString();
if (query != null && query.length() > 0) {
mNetworkThread = new NetworkThread(mISBN, query, mHandler);
mNetworkThread.start();
mHeaderView.setText(R.string.msg_searching_book);
mResultListView.setAdapter(null);
mQueryTextView.setEnabled(false);
mQueryButton.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");
mHeaderView.setText("Found " + ((count == 1) ? "1 result" : count + " results"));
if (count > 0) {
JSONArray results = json.getJSONArray("search_results");
SearchBookContentsResult.setQuery(mQueryTextView.getText().toString());
List<SearchBookContentsResult> items = new ArrayList<SearchBookContentsResult>(count);
for (int x = 0; x < count; x++) {
items.add(parseResult(results.getJSONObject(x)));
}
mResultListView.setAdapter(new SearchBookContentsAdapter(this, items));
} else {
mResultListView.setAdapter(null);
}
} catch (JSONException e) {
Log.e(TAG, e.toString());
mResultListView.setAdapter(null);
mHeaderView.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 class NetworkThread extends Thread {
private String mISBN;
private String mQuery;
private Handler mHandler;
public NetworkThread(String isbn, String query, Handler handler) {
mISBN = isbn;
mQuery = query;
mHandler = handler;
}
public void run() {
String url = BOOK_SEARCH_URL + mISBN + BOOK_SEARCH_COMMAND + mQuery;
AndroidHttpClient client = null;
try {
URI uri = new URI("http", url, null);
HttpGet get = new HttpGet(uri);
get.setHeader("cookie", getCookie("http:" + url));
client = AndroidHttpClient.newInstance(mUserAgent);
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(mHandler, R.id.search_book_contents_succeeded);
message.obj = json;
message.sendToTarget();
} else {
Log.e(TAG, "HTTP returned " + response.getStatusLine().getStatusCode() +
" for http:" + url);
Message message = Message.obtain(mHandler, R.id.search_book_contents_failed);
message.sendToTarget();
}
} catch (Exception e) {
Log.e(TAG, e.toString());
Message message = Message.obtain(mHandler, 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");
HttpHead head = new HttpHead(url);
AndroidHttpClient client = AndroidHttpClient.newInstance(mUserAgent);
try {
HttpResponse response = client.execute(head);
if (response.getStatusLine().getStatusCode() == 200) {
Header[] cookies = response.getHeaders("set-cookie");
for (int x = 0; x < cookies.length; x++) {
CookieManager.getInstance().setCookie(url, cookies[x].getValue());
}
CookieSyncManager.getInstance().sync();
cookie = CookieManager.getInstance().getCookie(url);
}
} catch (IOException e) {
Log.e(TAG, e.toString());
}
}
return cookie;
}
private 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";
}
}
}

View file

@ -0,0 +1,54 @@
/*
* 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;
public 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;
}
}

View file

@ -0,0 +1,73 @@
/*
* 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.text.SpannableString;
import android.text.style.StyleSpan;
import android.util.AttributeSet;
import android.widget.LinearLayout;
import android.widget.TextView;
public class SearchBookContentsListItem extends LinearLayout {
private TextView mPageNumberView;
private TextView mSnippetView;
SearchBookContentsListItem(Context context) {
super(context);
}
public SearchBookContentsListItem(Context context, AttributeSet attrs) {
super(context, attrs);
}
@Override
protected void onFinishInflate() {
super.onFinishInflate();
mPageNumberView = (TextView) findViewById(R.id.page_number_view);
mSnippetView = (TextView) findViewById(R.id.snippet_view);
}
public void set(SearchBookContentsResult result) {
mPageNumberView.setText(result.getPageNumber());
String snippet = result.getSnippet();
if (snippet.length() > 0) {
if (result.getValidSnippet()) {
String lowerQuery = SearchBookContentsResult.getQuery().toLowerCase();
String lowerSnippet = snippet.toLowerCase();
SpannableString styledSnippet = new SpannableString(snippet);
StyleSpan boldSpan = new StyleSpan(android.graphics.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;
}
mSnippetView.setText(styledSnippet);
} else {
// This may be an error message, so don't try to bold the query terms within it
mSnippetView.setText(snippet);
}
} else {
mSnippetView.setText("");
}
}
}

View file

@ -0,0 +1,53 @@
/*
* 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;
public class SearchBookContentsResult {
static private String sQuery;
private String mPageNumber;
private String mSnippet;
private boolean mValidSnippet;
public SearchBookContentsResult(String pageNumber, String snippet, boolean validSnippet) {
mPageNumber = pageNumber;
mSnippet = snippet;
mValidSnippet = validSnippet;
}
public static void setQuery(String query) {
sQuery = query;
}
public String getPageNumber() {
return mPageNumber;
}
public String getSnippet() {
return mSnippet;
}
public boolean getValidSnippet() {
return mValidSnippet;
}
public static String getQuery() {
return sQuery;
}
}

View file

@ -0,0 +1,123 @@
/*
* 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.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.view.View;
/**
* This view is overlaid on top of the camera preview. It adds the viewfinder rectangle and partial
* transparency outside it, as well as the laser scanner animation and result points.
*/
public class ViewfinderView extends View {
private static final int[] SCANNER_ALPHA = {0, 64, 128, 192, 255, 192, 128, 64};
private static final int ANIMATION_DELAY = 100;
private Paint mPaint;
private Rect mBox;
private Bitmap mResultBitmap;
private int mMaskColor;
private int mResultColor;
private int mFrameColor;
private int mLaserColor;
private int mScannerAlpha;
// This constructor is used when the class is built from an XML resource.
public ViewfinderView(Context context, AttributeSet attrs) {
super(context, attrs);
// Initialize these once for performance rather than calling them every time in onDraw().
mPaint = new Paint();
mBox = new Rect();
Resources resources = getResources();
mMaskColor = resources.getColor(R.color.viewfinder_mask);
mResultColor = resources.getColor(R.color.result_view);
mFrameColor = resources.getColor(R.color.viewfinder_frame);
mLaserColor = resources.getColor(R.color.viewfinder_laser);
mScannerAlpha = 0;
}
@Override
public void onDraw(Canvas canvas) {
Rect frame = CameraManager.get().getFramingRect();
int width = canvas.getWidth();
int height = canvas.getHeight();
// Draw the exterior (i.e. outside the framing rect) darkened
mPaint.setColor(mResultBitmap != null ? mResultColor : mMaskColor);
mBox.set(0, 0, width, frame.top);
canvas.drawRect(mBox, mPaint);
mBox.set(0, frame.top, frame.left, frame.bottom + 1);
canvas.drawRect(mBox, mPaint);
mBox.set(frame.right + 1, frame.top, width, frame.bottom + 1);
canvas.drawRect(mBox, mPaint);
mBox.set(0, frame.bottom + 1, width, height);
canvas.drawRect(mBox, mPaint);
if (mResultBitmap != null) {
// Draw the opaque result bitmap over the scanning rectangle
mPaint.setAlpha(255);
canvas.drawBitmap(mResultBitmap, frame.left, frame.top, mPaint);
} else {
// Draw a two pixel solid black border inside the framing rect
mPaint.setColor(mFrameColor);
mBox.set(frame.left, frame.top, frame.right + 1, frame.top + 2);
canvas.drawRect(mBox, mPaint);
mBox.set(frame.left, frame.top + 2, frame.left + 2, frame.bottom - 1);
canvas.drawRect(mBox, mPaint);
mBox.set(frame.right - 1, frame.top, frame.right + 1, frame.bottom - 1);
canvas.drawRect(mBox, mPaint);
mBox.set(frame.left, frame.bottom - 1, frame.right + 1, frame.bottom + 1);
canvas.drawRect(mBox, mPaint);
// Draw a red "laser scanner" line through the middle to show decoding is active
mPaint.setColor(mLaserColor);
mPaint.setAlpha(SCANNER_ALPHA[mScannerAlpha]);
mScannerAlpha = (mScannerAlpha + 1) % SCANNER_ALPHA.length;
int middle = frame.height() / 2 + frame.top;
mBox.set(frame.left + 2, middle - 1, frame.right - 1, middle + 2);
canvas.drawRect(mBox, mPaint);
// Request another update at the animation interval, but only repaint the laser line,
// not the entire viewfinder mask.
postInvalidateDelayed(ANIMATION_DELAY, mBox.left, mBox.top, mBox.right, mBox.bottom);
}
}
public void drawViewfinder() {
mResultBitmap = null;
invalidate();
}
/**
* Draw a bitmap with the result points highlighted instead of the live scanning display.
*
* @param barcode An image of the decoded barcode.
*/
public void drawResultBitmap(Bitmap barcode) {
mResultBitmap = barcode;
invalidate();
}
}

View file

@ -0,0 +1,97 @@
/*
* 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.graphics.Bitmap;
import android.graphics.Rect;
import com.google.zxing.common.BaseMonochromeBitmapSource;
/**
* This object implements MonochromeBitmapSource around an array of YUV data, giving you the option
* to crop to a rectangle within the full data. This can be used to exclude superfluous pixels
* around the perimeter and speed up decoding.
*/
final class YUVMonochromeBitmapSource extends BaseMonochromeBitmapSource {
private final byte[] mYUVData;
private final int mDataWidth;
private final Rect mCrop;
/**
* Builds an object around a YUV buffer from the camera.
*
* @param yuvData A byte array of planar Y data, followed by interleaved U and V
* @param dataWidth The width of the Y data
* @param dataHeight The height of the Y data
* @param crop The rectangle within the yuvData to expose to MonochromeBitmapSource users
*/
YUVMonochromeBitmapSource(byte[] yuvData, int dataWidth, int dataHeight, Rect crop) {
mYUVData = yuvData;
mDataWidth = dataWidth;
mCrop = crop;
assert (crop.width() <= dataWidth);
assert (crop.height() <= dataHeight);
}
public int getHeight() {
return mCrop.height();
}
public int getWidth() {
return mCrop.width();
}
/**
* The Y channel is stored as planar data at the head of the array, so we just ignore the
* interleavd U and V which follow it.
*
* @param x The x coordinate to fetch within crop
* @param y The y coordinate to fetch within crop
* @return The luminance as an int, from 0-255
*/
public int getLuminance(int x, int y) {
return mYUVData[(y + mCrop.top) * mDataWidth + x + mCrop.left] & 0xff;
}
// Nothing to do, since we have direct access to the mYUVData array.
public void cacheRowForLuminance(int y) {
}
/**
* Create a greyscale Android Bitmap from the YUV data based on the crop rectangle.
*
* @return A 565 bitmap.
*/
public Bitmap renderToBitmap() {
int width = mCrop.width();
int height = mCrop.height();
int[] pixels = new int[width * height];
for (int y = 0; y < height; y++) {
int base = (y + mCrop.top) * mDataWidth + mCrop.left;
for (int x = 0; x < width; x++) {
int grey = mYUVData[base + x] & 0xff;
pixels[y * width + x] = (0xff << 24) | (grey << 16) | (grey << 8) | grey;
}
}
Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
bitmap.setPixels(pixels, 0, width, 0, 0, width, height);
return bitmap;
}
}

View file

@ -0,0 +1,158 @@
/*
* 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.result;
import android.app.Activity;
import android.telephony.PhoneNumberUtils;
import android.text.SpannableString;
import android.text.style.StyleSpan;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.AddressBookParsedResult;
import com.google.zxing.client.result.ParsedResult;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
public class AddressBookResultHandler extends ResultHandler {
private boolean mFields[];
private int mButtonCount;
// This takes all the work out of figuring out which buttons/actions should be in which
// positions, based on which fields are present in this barcode.
private int mapIndexToAction(int index) {
if (index < mButtonCount) {
int count = -1;
for (int x = 0; x < MAX_BUTTON_COUNT; x++) {
if (mFields[x]) count++;
if (count == index) return x;
}
}
return -1;
}
public AddressBookResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
AddressBookParsedResult addressResult = (AddressBookParsedResult) result;
String address = addressResult.getAddress();
boolean hasAddress = address != null && address.length() > 0;
String[] phoneNumbers = addressResult.getPhoneNumbers();
boolean hasPhoneNumber = phoneNumbers != null && phoneNumbers.length > 0;
String[] emails = addressResult.getEmails();
boolean hasEmailAddress = emails != null && emails.length > 0;
mFields = new boolean[MAX_BUTTON_COUNT];
mFields[0] = true; // Add contact is always available
mFields[1] = hasAddress;
mFields[2] = hasPhoneNumber;
mFields[3] = hasEmailAddress;
mButtonCount = 0;
for (int x = 0; x < MAX_BUTTON_COUNT; x++) {
if (mFields[x]) mButtonCount++;
}
}
public int getButtonCount() {
return mButtonCount;
}
public int getButtonText(int index) {
int action = mapIndexToAction(index);
switch (action) {
case 0:
return R.string.button_add_contact;
case 1:
return R.string.button_show_map;
case 2:
return R.string.button_dial;
case 3:
return R.string.button_email;
default:
throw new ArrayIndexOutOfBoundsException();
}
}
public void handleButtonPress(int index) {
AddressBookParsedResult addressResult = (AddressBookParsedResult) mResult;
int action = mapIndexToAction(index);
switch (action) {
case 0:
addContact(addressResult.getNames(), addressResult.getPhoneNumbers(),
addressResult.getEmails(), addressResult.getNote(),
addressResult.getAddress(), addressResult.getOrg(),
addressResult.getTitle());
break;
case 1:
searchMap(addressResult.getAddress());
break;
case 2:
dialPhone(addressResult.getPhoneNumbers()[0]);
break;
case 3:
sendEmail(addressResult.getEmails()[0], null, null);
break;
default:
break;
}
}
// Overriden so we can hyphenate phone numbers, format birthdays, and bold the name.
@Override
public CharSequence getDisplayContents() {
AddressBookParsedResult result = (AddressBookParsedResult) mResult;
StringBuffer contents = new StringBuffer();
ParsedResult.maybeAppend(result.getNames(), contents);
int namesLength = contents.length();
ParsedResult.maybeAppend(result.getTitle(), contents);
ParsedResult.maybeAppend(result.getOrg(), contents);
ParsedResult.maybeAppend(result.getAddress(), contents);
String[] numbers = result.getPhoneNumbers();
if (numbers != null) {
for (String number : numbers) {
ParsedResult.maybeAppend(PhoneNumberUtils.formatNumber(number), contents);
}
}
ParsedResult.maybeAppend(result.getEmails(), contents);
ParsedResult.maybeAppend(result.getURL(), contents);
String birthday = result.getBirthday();
if (birthday != null && birthday.length() > 0) {
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
Date date = format.parse(birthday, new ParsePosition(0));
ParsedResult.maybeAppend(DateFormat.getDateInstance().format(date.getTime()), contents);
}
ParsedResult.maybeAppend(result.getNote(), contents);
if (namesLength > 0) {
// Bold the full name to make it stand out a bit.
SpannableString styled = new SpannableString(contents.toString());
styled.setSpan(new StyleSpan(android.graphics.Typeface.BOLD), 0, namesLength, 0);
return styled;
} else {
return contents.toString();
}
}
public int getDisplayTitle() {
return R.string.result_address_book;
}
}

View file

@ -0,0 +1,57 @@
/*
* 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.result;
import android.content.Intent;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ParsedResultType;
import java.net.URISyntaxException;
/**
* A {@link com.google.zxing.client.result.ParsedResult} derived from a URI that encodes an Android
* {@link Intent}, and which should presumably trigger that intent on Android.
*/
public final class AndroidIntentParsedResult extends ParsedResult {
private final Intent mIntent;
private AndroidIntentParsedResult(Intent intent) {
super(ParsedResultType.ANDROID_INTENT);
mIntent = intent;
}
public static AndroidIntentParsedResult parse(String rawText) {
try {
return new AndroidIntentParsedResult(Intent.getIntent(rawText));
} catch (URISyntaxException urise) {
return null;
} catch (IllegalArgumentException iae) {
return null;
}
}
public Intent getIntent() {
return mIntent;
}
@Override
public String getDisplayResult() {
return mIntent.toString();
}
}

View file

@ -0,0 +1,101 @@
/*
* 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.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.CalendarParsedResult;
import com.google.zxing.client.result.ParsedResult;
import java.text.DateFormat;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
public class CalendarResultHandler extends ResultHandler {
private static final int[] mButtons = {
R.string.button_add_calendar
};
public CalendarResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
}
public int getButtonCount() {
return mButtons.length;
}
public int getButtonText(int index) {
return mButtons[index];
}
public void handleButtonPress(int index) {
CalendarParsedResult calendarResult = (CalendarParsedResult) mResult;
switch (index) {
case 0:
addCalendarEvent(calendarResult.getSummary(), calendarResult.getStart(),
calendarResult.getEnd());
break;
}
}
@Override
public CharSequence getDisplayContents() {
CalendarParsedResult calResult = (CalendarParsedResult) mResult;
StringBuffer result = new StringBuffer();
ParsedResult.maybeAppend(calResult.getSummary(), result);
appendTime(calResult.getStart(), result);
// The end can be null if the event has no duration, so use the start time.
String endString = calResult.getEnd();
if (endString == null) endString = calResult.getStart();
appendTime(endString, result);
ParsedResult.maybeAppend(calResult.getLocation(), result);
ParsedResult.maybeAppend(calResult.getAttendee(), result);
ParsedResult.maybeAppend(calResult.getTitle(), result);
return result.toString();
}
private void appendTime(String when, StringBuffer result) {
if (when.length() == 8) {
// Show only year/month/day
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
Date date = format.parse(when, new ParsePosition(0));
ParsedResult.maybeAppend(DateFormat.getDateInstance().format(date.getTime()), result);
} else {
// The when string can be local time, or UTC if it ends with a Z
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
Date date = format.parse(when.substring(0, 15), new ParsePosition(0));
long milliseconds = date.getTime();
if (when.length() == 16 && when.charAt(15) == 'Z') {
GregorianCalendar calendar = new GregorianCalendar();
int offset = (calendar.get(Calendar.ZONE_OFFSET) + calendar.get(Calendar.DST_OFFSET));
milliseconds += offset;
}
ParsedResult.maybeAppend(DateFormat.getDateTimeInstance().format(milliseconds), result);
}
}
public int getDisplayTitle() {
return R.string.result_calendar;
}
}

View file

@ -0,0 +1,61 @@
/*
* 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.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.EmailAddressParsedResult;
import com.google.zxing.client.result.ParsedResult;
public class EmailAddressResultHandler extends ResultHandler {
private static final int[] mButtons = {
R.string.button_email,
R.string.button_add_contact
};
public EmailAddressResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
}
public int getButtonCount() {
return mButtons.length;
}
public int getButtonText(int index) {
return mButtons[index];
}
public void handleButtonPress(int index) {
EmailAddressParsedResult emailResult = (EmailAddressParsedResult) mResult;
switch (index) {
case 0:
sendEmailFromUri(emailResult.getMailtoURI(), null, null);
break;
case 1:
String[] addresses = new String[1];
addresses[0] = emailResult.getEmailAddress();
addContact(null, null, addresses, null, null, null, null);
break;
}
}
public int getDisplayTitle() {
return R.string.result_email_address;
}
}

View file

@ -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.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.GeoParsedResult;
import com.google.zxing.client.result.ParsedResult;
public class GeoResultHandler extends ResultHandler {
private static final int[] mButtons = {
R.string.button_show_map,
R.string.button_get_directions
};
public GeoResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
}
public int getButtonCount() {
return mButtons.length;
}
public int getButtonText(int index) {
return mButtons[index];
}
public void handleButtonPress(int index) {
GeoParsedResult geoResult = (GeoParsedResult) mResult;
switch (index) {
case 0:
openMap(geoResult.getGeoURI());
break;
case 1:
getDirections(geoResult.getLatitude(), geoResult.getLongitude());
break;
}
}
public int getDisplayTitle() {
return R.string.result_geo;
}
}

View file

@ -0,0 +1,63 @@
/*
* 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.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ISBNParsedResult;
import com.google.zxing.client.result.ParsedResult;
public class ISBNResultHandler extends ResultHandler {
private static final int[] mButtons = {
R.string.button_product_search,
R.string.button_book_search,
R.string.button_search_book_contents
};
public ISBNResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
}
public int getButtonCount() {
return mButtons.length;
}
public int getButtonText(int index) {
return mButtons[index];
}
public void handleButtonPress(int index) {
ISBNParsedResult isbnResult = (ISBNParsedResult) mResult;
switch (index) {
case 0:
openProductSearch(isbnResult.getISBN());
break;
case 1:
openBookSearch(isbnResult.getISBN());
break;
case 2:
searchBookContents(isbnResult.getISBN());
break;
}
}
public int getDisplayTitle() {
return R.string.result_isbn;
}
}

View file

@ -0,0 +1,40 @@
/*
* 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.result;
import android.view.View;
import android.widget.Button;
/**
* Handles the result of barcode decoding in the context of the Android platform, by dispatching the
* proper intents to open other activities like GMail, Maps, etc.
*/
public final class ResultButtonListener implements Button.OnClickListener {
ResultHandler mResultHandler;
int mIndex;
public ResultButtonListener(ResultHandler resultHandler, int index) {
mResultHandler = resultHandler;
mIndex = index;
}
public void onClick(View view) {
mResultHandler.handleButtonPress(mIndex);
}
}

View file

@ -0,0 +1,264 @@
/*
* 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.result;
import android.app.Activity;
import android.content.Intent;
import android.net.Uri;
import android.provider.Contacts;
import com.google.zxing.client.android.Intents;
import com.google.zxing.client.android.R;
import com.google.zxing.client.android.SearchBookContentsActivity;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ParsedResultType;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.GregorianCalendar;
public abstract class ResultHandler {
public static final int MAX_BUTTON_COUNT = 4;
protected ParsedResult mResult;
private Activity mActivity;
public ResultHandler(Activity activity, ParsedResult result) {
mResult = result;
mActivity = activity;
}
/**
* Indicates how many buttons the derived class wants shown.
*
* @return The integer button count.
*/
public abstract int getButtonCount();
/**
* The text of the nth action button.
*
* @param index From 0 to getButtonCount() - 1
* @return The button text as a resource ID
*/
public abstract int getButtonText(int index);
/**
* Execute the action which corresponds to the nth button.
*
* @param index The button that was clicked.
*/
public abstract void handleButtonPress(int index);
/**
* Create a possibly styled string for the contents of the current barcode.
*
* @return The text to be displayed.
*/
public CharSequence getDisplayContents() {
String contents = mResult.getDisplayResult();
return contents.replace("\r", "");
}
/**
* A string describing the kind of barcode that was found, e.g. "Found contact info".
*
* @return The resource ID of the string.
*/
public abstract int getDisplayTitle();
/**
* A convenience method to get the parsed type. Should not be overridden.
*
* @return The parsed type, e.g. URI or ISBN
*/
public final ParsedResultType getType() {
return mResult.getType();
}
/**
* Sends an intent to create a new calendar event by prepopulating the Add Event UI. Older
* versions of the system have a bug where the event title will not be filled out.
*
* @param summary A description of the event
* @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 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));
if (start.length() == 8) {
intent.putExtra("allDay", true);
}
intent.putExtra("endTime", calculateMilliseconds(end));
intent.putExtra("title", summary);
launchIntent(intent);
}
private long calculateMilliseconds(String when) {
if (when.length() == 8) {
// Only contains year/month/day
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd");
Date date = format.parse(when, new ParsePosition(0));
return date.getTime();
} else {
// The when string can be local time, or UTC if it ends with a Z
SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd'T'HHmmss");
Date date = format.parse(when.substring(0, 15), new ParsePosition(0));
long milliseconds = date.getTime();
if (when.length() == 16 && when.charAt(15) == 'Z') {
GregorianCalendar calendar = new GregorianCalendar();
int offset = (calendar.get(java.util.Calendar.ZONE_OFFSET) +
calendar.get(java.util.Calendar.DST_OFFSET));
milliseconds += offset;
}
return milliseconds;
}
}
public void addContact(String[] names, String[] phoneNumbers, String[] emails, String note,
String address, String org, String title) {
Intent intent = new Intent(Contacts.Intents.Insert.ACTION, Contacts.People.CONTENT_URI);
putExtra(intent, Contacts.Intents.Insert.NAME, names);
putExtra(intent, Contacts.Intents.Insert.PHONE, phoneNumbers);
putExtra(intent, Contacts.Intents.Insert.EMAIL, emails);
putExtra(intent, Contacts.Intents.Insert.NOTES, note);
putExtra(intent, Contacts.Intents.Insert.POSTAL, address);
putExtra(intent, Contacts.Intents.Insert.COMPANY, org);
putExtra(intent, Contacts.Intents.Insert.JOB_TITLE, title);
launchIntent(intent);
}
public void shareByEmail(String contents) {
sendEmailFromUri("mailto:", mActivity.getString(R.string.msg_share_barcode), contents);
}
public void sendEmail(String address, String subject, String body) {
sendEmailFromUri("mailto:" + address, subject, body);
}
public void sendEmailFromUri(String uri, String subject, String body) {
Intent intent = new Intent(Intent.ACTION_SENDTO, Uri.parse(uri));
putExtra(intent, "subject", subject);
putExtra(intent, "body", body);
launchIntent(intent);
}
public void shareBySMS(String contents) {
sendSMSFromUri("smsto:", mActivity.getString(R.string.msg_share_barcode) + ":\n" + contents);
}
public void sendSMS(String phoneNumber, String body) {
sendSMSFromUri("smsto:" + phoneNumber, body);
}
public 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
intent.putExtra("compose_mode", true);
launchIntent(intent);
}
public void sendMMS(String phoneNumber, String subject, String body) {
sendMMSFromUri("mmsto:" + phoneNumber, subject, body);
}
public 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) {
putExtra(intent, "subject", mActivity.getString(R.string.msg_default_mms_subject));
} else {
putExtra(intent, "subject", subject);
}
putExtra(intent, "sms_body", body);
intent.putExtra("compose_mode", true);
launchIntent(intent);
}
public void dialPhone(String phoneNumber) {
launchIntent(new Intent(Intent.ACTION_DIAL, Uri.parse("tel:" + phoneNumber)));
}
public void dialPhoneFromUri(String uri) {
launchIntent(new Intent(Intent.ACTION_DIAL, Uri.parse(uri)));
}
public void openMap(String geoURI) {
launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(geoURI)));
}
public void searchMap(String address) {
launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse("geo:0,0?q=" + Uri.encode(address))));
}
public void getDirections(float latitude, float longitude) {
launchIntent(new Intent(Intent.ACTION_VIEW,
Uri.parse("http://maps.google.com/maps?f=d&daddr=" + latitude + "," + longitude)));
}
public void openProductSearch(String upc) {
Uri uri = Uri.parse("http://www.google.com/products?q=" + upc);
launchIntent(new Intent(Intent.ACTION_VIEW, uri));
}
public void openBookSearch(String isbn) {
Uri uri = Uri.parse("http://books.google.com/books?vid=isbn" + isbn);
launchIntent(new Intent(Intent.ACTION_VIEW, uri));
}
public void searchBookContents(String isbn) {
Intent intent = new Intent(Intents.SearchBookContents.ACTION);
intent.setClassName(mActivity, SearchBookContentsActivity.class.getName());
putExtra(intent, Intents.SearchBookContents.ISBN, isbn);
launchIntent(intent);
}
public void openURL(String url) {
launchIntent(new Intent(Intent.ACTION_VIEW, Uri.parse(url)));
}
public void webSearch(String query) {
Intent intent = new Intent(Intent.ACTION_WEB_SEARCH);
intent.putExtra("query", query);
launchIntent(intent);
}
private void launchIntent(Intent intent) {
if (intent != null) {
mActivity.startActivity(intent);
}
}
private static void putExtra(Intent intent, String key, String value) {
if (value != null && value.length() > 0) {
intent.putExtra(key, value);
}
}
private static void putExtra(Intent intent, String key, String[] value) {
if (value != null && value.length > 0) {
putExtra(intent, key, value[0]);
}
}
}

View file

@ -0,0 +1,75 @@
/*
* 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.result;
import android.app.Activity;
import com.google.zxing.Result;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ParsedResultType;
import com.google.zxing.client.result.ResultParser;
public class ResultHandlerFactory {
public static ResultHandler makeResultHandler(Activity activity, Result rawResult) {
ParsedResult result = parseResult(rawResult);
ParsedResultType type = result.getType();
if (type.equals(ParsedResultType.ADDRESSBOOK)) {
return new AddressBookResultHandler(activity, result);
} else if (type.equals(ParsedResultType.EMAIL_ADDRESS)) {
return new EmailAddressResultHandler(activity, result);
} else if (type.equals(ParsedResultType.UPC)) {
return new UPCResultHandler(activity, result);
} else if (type.equals(ParsedResultType.URI)) {
return new URIResultHandler(activity, result);
} else if (type.equals(ParsedResultType.TEXT)) {
return new TextResultHandler(activity, result);
} else if (type.equals(ParsedResultType.GEO)) {
return new GeoResultHandler(activity, result);
} else if (type.equals(ParsedResultType.TEL)) {
return new TelResultHandler(activity, result);
} else if (type.equals(ParsedResultType.SMS)) {
return new SMSResultHandler(activity, result);
} else if (type.equals(ParsedResultType.CALENDAR)) {
return new CalendarResultHandler(activity, result);
} else if (type.equals(ParsedResultType.ISBN)) {
return new ISBNResultHandler(activity, result);
} else {
// The TextResultHandler is the fallthrough for unsupported formats.
return new TextResultHandler(activity, result);
}
}
private static ParsedResult parseResult(Result rawResult) {
ParsedResult result = ResultParser.parseResult(rawResult);
// Disabled for now. To reactivate, create an AndroidIntentResultHandler.
// if (result.getType().equals(ParsedResultType.TEXT)) {
// String rawText = rawResult.getText();
// AndroidIntentParsedResult androidResult = AndroidIntentParsedResult.parse(rawText);
// if (androidResult != null) {
// Intent intent = androidResult.getIntent();
// if (!Intent.ACTION_VIEW.equals(intent.getAction())) {
// // For now, don't take anything that just parses as a View action. A lot
// // of things are accepted as a View action by default.
// result = androidResult;
// }
// }
// }
return result;
}
}

View file

@ -0,0 +1,72 @@
/*
* 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.result;
import android.app.Activity;
import android.telephony.PhoneNumberUtils;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.SMSParsedResult;
public class SMSResultHandler extends ResultHandler {
private static final int[] mButtons = {
R.string.button_sms,
R.string.button_mms
};
public SMSResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
}
public int getButtonCount() {
return mButtons.length;
}
public int getButtonText(int index) {
return mButtons[index];
}
public void handleButtonPress(int index) {
SMSParsedResult smsResult = (SMSParsedResult) mResult;
switch (index) {
case 0:
sendSMS(smsResult.getNumber(), smsResult.getBody());
break;
case 1:
sendMMS(smsResult.getNumber(), smsResult.getSubject(), smsResult.getBody());
break;
}
}
@Override
public CharSequence getDisplayContents() {
SMSParsedResult smsResult = (SMSParsedResult) mResult;
StringBuffer contents = new StringBuffer();
ParsedResult.maybeAppend(PhoneNumberUtils.formatNumber(smsResult.getNumber()), contents);
ParsedResult.maybeAppend(smsResult.getVia(), contents);
ParsedResult.maybeAppend(smsResult.getSubject(), contents);
ParsedResult.maybeAppend(smsResult.getBody(), contents);
ParsedResult.maybeAppend(smsResult.getTitle(), contents);
return contents.toString();
}
public int getDisplayTitle() {
return R.string.result_sms;
}
}

View file

@ -0,0 +1,70 @@
/*
* 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.result;
import android.app.Activity;
import android.telephony.PhoneNumberUtils;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.TelParsedResult;
public class TelResultHandler extends ResultHandler {
private static final int[] mButtons = {
R.string.button_dial,
R.string.button_add_contact
};
public TelResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
}
public int getButtonCount() {
return mButtons.length;
}
public int getButtonText(int index) {
return mButtons[index];
}
public void handleButtonPress(int index) {
TelParsedResult telResult = (TelParsedResult) mResult;
switch (index) {
case 0:
dialPhoneFromUri(telResult.getTelURI());
break;
case 1:
String[] numbers = new String[1];
numbers[0] = telResult.getNumber();
addContact(null, numbers, null, null, null, null, null);
break;
}
}
// Overriden so we can take advantage of Android's phone number hyphenation routines.
@Override
public CharSequence getDisplayContents() {
String contents = mResult.getDisplayResult();
contents = contents.replace("\r", "");
return PhoneNumberUtils.formatNumber(contents);
}
public int getDisplayTitle() {
return R.string.result_tel;
}
}

View file

@ -0,0 +1,64 @@
/*
* 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.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
/**
* This class handles TextParsedResult as well as unknown formats.
*/
public class TextResultHandler extends ResultHandler {
private static final int[] mButtons = {
R.string.button_web_search,
R.string.button_share_by_email,
R.string.button_share_by_sms
};
public TextResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
}
public int getButtonCount() {
return mButtons.length;
}
public int getButtonText(int index) {
return mButtons[index];
}
public void handleButtonPress(int index) {
switch (index) {
case 0:
webSearch(mResult.getDisplayResult());
break;
case 1:
shareByEmail(mResult.getDisplayResult());
break;
case 2:
shareBySMS(mResult.getDisplayResult());
break;
}
}
public int getDisplayTitle() {
return R.string.result_text;
}
}

View file

@ -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.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.UPCParsedResult;
public class UPCResultHandler extends ResultHandler {
private static final int[] mButtons = {
R.string.button_product_search,
R.string.button_web_search
};
public UPCResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
}
public int getButtonCount() {
return mButtons.length;
}
public int getButtonText(int index) {
return mButtons[index];
}
public void handleButtonPress(int index) {
UPCParsedResult upcResult = (UPCParsedResult) mResult;
switch (index) {
case 0:
openProductSearch(upcResult.getUPC());
break;
case 1:
webSearch(upcResult.getUPC());
break;
}
}
public int getDisplayTitle() {
return R.string.result_upc;
}
}

View file

@ -0,0 +1,63 @@
/*
* 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.result;
import android.app.Activity;
import com.google.zxing.client.android.R;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.URIParsedResult;
public class URIResultHandler extends ResultHandler {
private static final int[] mButtons = {
R.string.button_open_browser,
R.string.button_share_by_email,
R.string.button_share_by_sms
};
public URIResultHandler(Activity activity, ParsedResult result) {
super(activity, result);
}
public int getButtonCount() {
return mButtons.length;
}
public int getButtonText(int index) {
return mButtons[index];
}
public void handleButtonPress(int index) {
URIParsedResult uriResult = (URIParsedResult) mResult;
switch (index) {
case 0:
openURL(uriResult.getURI());
break;
case 1:
shareByEmail(uriResult.getURI());
break;
case 2:
shareBySMS(uriResult.getURI());
break;
}
}
public int getDisplayTitle() {
return R.string.result_uri;
}
}