- Created Barcode Scanner 3.0 beta 1, for Donut and above only.
authordswitkin <dswitkin@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 19 Oct 2009 18:04:57 +0000 (18:04 +0000)
committerdswitkin <dswitkin@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Mon, 19 Oct 2009 18:04:57 +0000 (18:04 +0000)
- Added support for ACTION_SEND intent for encoding a QR from Contacts.
- Made VCARD parsing a little less strict.
- Updated help and fixed a minor UI issue.
- Removed vestigial intent support using the old package name.

git-svn-id: http://zxing.googlecode.com/svn/trunk@1075 59b500cc-1b3d-0410-9834-0bbf25fbcc57

android/AndroidManifest.xml
android/assets/html/whatsnew.html
android/res/values/strings.xml
android/src/com/google/zxing/client/android/encode/EncodeActivity.java
android/src/com/google/zxing/client/android/encode/QRCodeEncoder.java
core/src/com/google/zxing/client/result/VCardResultParser.java

index 63e9ca3..3feca9b 100755 (executable)
@@ -20,10 +20,10 @@ version to be published. The next versionCode will be 7, regardless of whether t
 versionName is 2.31, 2.4, or 3.0. -->
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
           package="com.google.zxing.client.android"
-          android:versionName="3.0 alpha3"
-          android:versionCode="34">
-  <!-- Allows this app to run on Cupcake devices. -->
-  <uses-sdk android:minSdkVersion="3"/>
+          android:versionName="3.0 beta1"
+          android:versionCode="35">
+  <!-- We require Donut (Android 1.6) or later. -->
+  <uses-sdk android:minSdkVersion="4"/>
   <!-- Donut-specific flags which allow us to run on large and high dpi screens. -->
   <supports-screens
     android:largeScreens="true"
@@ -44,31 +44,26 @@ versionName is 2.31, 2.4, or 3.0. -->
         <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>
       <!-- Allow web apps to launch Barcode Scanner by linking to http://zxing.appspot.com/scan. -->
       <intent-filter>
         <action android:name="android.intent.action.VIEW"/>
-        <category android:name="android.intent.category.DEFAULT" />
+        <category android:name="android.intent.category.DEFAULT"/>
         <category android:name="android.intent.category.BROWSABLE"/>
-        <data android:scheme="http" android:host="zxing.appspot.com" android:path="/scan" />
+        <data android:scheme="http" android:host="zxing.appspot.com" android:path="/scan"/>
       </intent-filter>
       <!-- We also support a Google Product Search URL. -->
       <intent-filter>
         <action android:name="android.intent.action.VIEW"/>
-        <category android:name="android.intent.category.DEFAULT" />
+        <category android:name="android.intent.category.DEFAULT"/>
         <category android:name="android.intent.category.BROWSABLE"/>
-        <data android:scheme="http" android:host="www.google.com" android:path="/m/products/scan" />
+        <data android:scheme="http" android:host="www.google.com" android:path="/m/products/scan"/>
       </intent-filter>
       <!-- And the UK version. -->
       <intent-filter>
         <action android:name="android.intent.action.VIEW"/>
-        <category android:name="android.intent.category.DEFAULT" />
+        <category android:name="android.intent.category.DEFAULT"/>
         <category android:name="android.intent.category.BROWSABLE"/>
-        <data android:scheme="http" android:host="www.google.co.uk" android:path="/m/products/scan" />
+        <data android:scheme="http" android:host="www.google.co.uk" android:path="/m/products/scan"/>
       </intent-filter>
     </activity>
     <activity android:name=".PreferencesActivity"
@@ -79,10 +74,11 @@ versionName is 2.31, 2.4, or 3.0. -->
         <action android:name="com.google.zxing.client.android.ENCODE"/>
         <category android:name="android.intent.category.DEFAULT"/>
       </intent-filter>
+      <!-- This allows us to handle the Share button in Contacts. -->
       <intent-filter>
-        <!-- For compatibility only - do not use in new code, this will go away! -->
-        <action android:name="com.android.barcodes.ENCODE"/>
+        <action android:name="android.intent.action.SEND"/>
         <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/x-vcard"/>
       </intent-filter>
     </activity>
     <activity android:name=".book.SearchBookContentsActivity"
@@ -93,11 +89,6 @@ versionName is 2.31, 2.4, or 3.0. -->
         <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>
     <activity android:name=".share.ShareActivity"
               android:label="@string/share_name"
index 25a6978..5d54abd 100644 (file)
@@ -8,6 +8,8 @@
   <li>Added support for high resolution and high DPI screens.</li>
   <li>Made the scanning rectangle wider for better performance on 1D barcodes.</li>
   <li>Wrote a new history feature (press Menu from the scanning screen).</li>
+  <li>Added support for sharing contacts via ACTION_SEND.</li>
+  <li>Better performance and stability on Donut (Android 1.6) and later.</li>
 </ul>
 </body>
 </html>
\ No newline at end of file
index fde40a5..286d763 100755 (executable)
@@ -90,7 +90,7 @@
   <string name="preferences_play_beep_title">Beep</string>
   <string name="preferences_vibrate_title">Vibrate</string>
   <string name="preferences_result_title">Result settings</string>
-  <string name="preferences_custom_product_search_title">Custom product search URL</string>
+  <string name="preferences_custom_product_search_title">Custom search URL</string>
   <string name="preferences_custom_product_search_summary">Use %s as a placeholder for the product
     ID</string>
 
index 926a914..de5527c 100755 (executable)
@@ -113,11 +113,14 @@ public final class EncodeActivity extends Activity {
     super.onCreate(icicle);
 
     Intent intent = getIntent();
-    if (intent != null && (intent.getAction().equals(Intents.Encode.ACTION))) {
-      setContentView(R.layout.encode);
-    } else {
-      finish();
+    if (intent != null) {
+      String action = intent.getAction();
+      if (action.equals(Intents.Encode.ACTION) || action.equals(Intent.ACTION_SEND)) {
+        setContentView(R.layout.encode);
+        return;
+      }
     }
+    finish();
   }
 
   @Override
index 2f4a573..4e1d9fb 100755 (executable)
@@ -18,15 +18,20 @@ package com.google.zxing.client.android.encode;
 
 import com.google.zxing.BarcodeFormat;
 import com.google.zxing.MultiFormatWriter;
+import com.google.zxing.Result;
 import com.google.zxing.WriterException;
-import com.google.zxing.client.android.Intents;
 import com.google.zxing.client.android.Contents;
+import com.google.zxing.client.android.Intents;
 import com.google.zxing.client.android.R;
+import com.google.zxing.client.result.AddressBookParsedResult;
+import com.google.zxing.client.result.ParsedResult;
+import com.google.zxing.client.result.ResultParser;
 import com.google.zxing.common.ByteMatrix;
 
 import android.app.Activity;
 import android.content.Intent;
 import android.graphics.Bitmap;
+import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
@@ -34,9 +39,13 @@ import android.provider.Contacts;
 import android.telephony.PhoneNumberUtils;
 import android.util.Log;
 
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+
 /**
  * This class does the work of decoding the user's request and extracting all the data
- * to be encoded in a QR Code.
+ * to be encoded in a barcode.
  *
  * @author dswitkin@google.com (Daniel Switkin)
  */
@@ -49,8 +58,19 @@ final class QRCodeEncoder {
 
   public QRCodeEncoder(Activity activity, Intent intent) {
     this.activity = activity;
-    if (!encodeContents(intent)) {
+    if (intent == null) {
+      throw new IllegalArgumentException("No valid data to encode.");
+    }
+
+    String action = intent.getAction();
+    if (action.equals(Intents.Encode.ACTION)) {
+      if (!encodeContentsFromZXingIntent(intent)) {
+        throw new IllegalArgumentException("No valid data to encode.");
+      }
+    } else if (action.equals(Intent.ACTION_SEND)) {
+      if (!encodeContentsFromShareIntent(intent)) {
       throw new IllegalArgumentException("No valid data to encode.");
+      }
     }
   }
 
@@ -71,21 +91,17 @@ final class QRCodeEncoder {
   public String getTitle() {
     return title;
   }
-  
+
   public String getFormat() {
     return format.toString();
   }
 
   // It would be nice if the string encoding lived in the core ZXing library,
   // but we use platform specific code like PhoneNumberUtils, so it can't.
-  private boolean encodeContents(Intent intent) {
-    if (intent == null) {
-      return false;
-    }
-    
-    // default to QR_CODE if no format given
+  private boolean encodeContentsFromZXingIntent(Intent intent) {
+     // Default to QR_CODE if no format given.
     String format = intent.getStringExtra(Intents.Encode.FORMAT);
-    if (format == null || format.length() == 0 || 
+    if (format == null || format.length() == 0 ||
         format.equals(Contents.Format.QR_CODE)) {
       String type = intent.getStringExtra(Intents.Encode.TYPE);
       if (type == null || type.length() == 0) {
@@ -117,6 +133,32 @@ final class QRCodeEncoder {
     return contents != null && contents.length() > 0;
   }
 
+  // Handles send intents from the Contacts app, retrieving a contact as a VCARD.
+  private boolean encodeContentsFromShareIntent(Intent intent) {
+    format = BarcodeFormat.QR_CODE;
+    try {
+      Uri uri = (Uri)intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
+      InputStream stream = activity.getContentResolver().openInputStream(uri);
+      int length = stream.available();
+      byte[] vcard = new byte[length];
+      stream.read(vcard, 0, length);
+      String vcardString = new String(vcard, "utf-8");
+      Result result = new Result(vcardString, vcard, null, BarcodeFormat.QR_CODE);
+      ParsedResult parsedResult = ResultParser.parseResult(result);
+      if (!(parsedResult instanceof AddressBookParsedResult)) {
+        return false;
+      }
+      if (!encodeQRCodeContents((AddressBookParsedResult) parsedResult)) {
+        return false;
+      }
+    } catch (FileNotFoundException e) {
+      return false;
+    } catch (IOException e) {
+      return false;
+    }
+    return contents != null && contents.length() > 0;
+  }
+
   private void encodeQRCodeContents(Intent intent, String type) {
     if (type.equals(Contents.Type.TEXT)) {
       String data = intent.getStringExtra(Intents.Encode.DATA);
@@ -202,6 +244,59 @@ final class QRCodeEncoder {
     }
   }
 
+  private boolean encodeQRCodeContents(AddressBookParsedResult contact) {
+    StringBuilder newContents = new StringBuilder();
+    StringBuilder newDisplayContents = new StringBuilder();
+    newContents.append("MECARD:");
+    String[] names = contact.getNames();
+    if (names != null && names.length > 0) {
+      newContents.append("N:").append(names[0]).append(';');
+      newDisplayContents.append(names[0]);
+    }
+    String address = contact.getAddress();
+    if (address != null && address.length() > 0) {
+      newContents.append("ADR:").append(address).append(';');
+      newDisplayContents.append('\n').append(address);
+    }
+    String[] phoneNumbers = contact.getPhoneNumbers();
+    if (phoneNumbers != null) {
+      for (int x = 0; x < phoneNumbers.length; x++) {
+        String phone = phoneNumbers[x];
+        if (phone != null && phone.length() > 0) {
+          newContents.append("TEL:").append(phone).append(';');
+          newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
+        }
+      }
+    }
+    String[] emails = contact.getEmails();
+    if (emails != null) {
+      for (int x = 0; x < emails.length; x++) {
+        String email = emails[x];
+        if (email != null && email.length() > 0) {
+          newContents.append("EMAIL:").append(email).append(';');
+          newDisplayContents.append('\n').append(email);
+        }
+      }
+    }
+    String url = contact.getURL();
+    if (url != null && url.length() > 0) {
+      newContents.append("URL:").append(url).append(';');
+      newDisplayContents.append('\n').append(url);
+    }
+    // Make sure we've encoded at least one field.
+    if (newDisplayContents.length() > 0) {
+      newContents.append(';');
+      contents = newContents.toString();
+      displayContents = newDisplayContents.toString();
+      title = activity.getString(R.string.contents_contact);
+      return true;
+    } else {
+      contents = null;
+      displayContents = null;
+      return false;
+    }
+  }
+
   private static final class EncodeThread extends Thread {
     private static final String TAG = "EncodeThread";
 
index 51182ae..2aa423b 100644 (file)
@@ -32,8 +32,11 @@ final class VCardResultParser extends ResultParser {
   }
 
   public static AddressBookParsedResult parse(Result result) {
+    // Although we should insist on the raw text ending with "END:VCARD", there's no reason
+    // to throw out everything else we parsed just because this was omitted. In fact, Eclair
+    // is doing just that, and we can't parse its contacts without this leniency.
     String rawText = result.getText();
-    if (rawText == null || !rawText.startsWith("BEGIN:VCARD") || !rawText.endsWith("END:VCARD")) {
+    if (rawText == null || !rawText.startsWith("BEGIN:VCARD")) {
       return null;
     }
     String[] names = matchVCardPrefixedField("FN", rawText, true);