Update sms: and geo: handling to better respect new RFCs
authorsrowen <srowen@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Tue, 30 Mar 2010 18:33:54 +0000 (18:33 +0000)
committersrowen <srowen@59b500cc-1b3d-0410-9834-0bbf25fbcc57>
Tue, 30 Mar 2010 18:33:54 +0000 (18:33 +0000)
git-svn-id: http://zxing.googlecode.com/svn/trunk@1266 59b500cc-1b3d-0410-9834-0bbf25fbcc57

android/src/com/google/zxing/client/android/result/SMSResultHandler.java
core/src/com/google/zxing/client/result/GeoParsedResult.java
core/src/com/google/zxing/client/result/GeoResultParser.java
core/src/com/google/zxing/client/result/ResultParser.java
core/src/com/google/zxing/client/result/SMSMMSResultParser.java
core/src/com/google/zxing/client/result/SMSParsedResult.java
core/src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java [new file with mode: 0644]
core/test/src/com/google/zxing/client/result/GeoParsedResultTestCase.java
core/test/src/com/google/zxing/client/result/ParsedReaderResultTestCase.java
core/test/src/com/google/zxing/client/result/SMSMMSParsedResultTestCase.java

index 06da292..3675e7a 100644 (file)
@@ -53,10 +53,11 @@ public final class SMSResultHandler extends ResultHandler {
     SMSParsedResult smsResult = (SMSParsedResult) getResult();
     switch (index) {
       case 0:
-        sendSMS(smsResult.getNumber(), smsResult.getBody());
+        // Don't know of a way yet to express a SENDTO intent with multiple recipients
+        sendSMS(smsResult.getNumbers()[0], smsResult.getBody());
         break;
       case 1:
-        sendMMS(smsResult.getNumber(), smsResult.getSubject(), smsResult.getBody());
+        sendMMS(smsResult.getNumbers()[0], smsResult.getSubject(), smsResult.getBody());
         break;
     }
   }
@@ -65,11 +66,14 @@ public final class SMSResultHandler extends ResultHandler {
   public CharSequence getDisplayContents() {
     SMSParsedResult smsResult = (SMSParsedResult) getResult();
     StringBuffer contents = new StringBuffer();
-    ParsedResult.maybeAppend(PhoneNumberUtils.formatNumber(smsResult.getNumber()), contents);
-    ParsedResult.maybeAppend(smsResult.getVia(), contents);
+    String[] rawNumbers = smsResult.getNumbers();
+    String[] formattedNumbers = new String[rawNumbers.length];
+    for (int i = 0; i < rawNumbers.length; i++) {
+      formattedNumbers[i] = PhoneNumberUtils.formatNumber(rawNumbers[i]);
+    }
+    ParsedResult.maybeAppend(formattedNumbers, contents);
     ParsedResult.maybeAppend(smsResult.getSubject(), contents);
     ParsedResult.maybeAppend(smsResult.getBody(), contents);
-    ParsedResult.maybeAppend(smsResult.getTitle(), contents);
     return contents.toString();
   }
 
index aabebcd..947b9e8 100644 (file)
@@ -21,21 +21,28 @@ package com.google.zxing.client.result;
  */
 public final class GeoParsedResult extends ParsedResult {
 
-  private final String geoURI;
   private final double latitude;
   private final double longitude;
   private final double altitude;
 
-  GeoParsedResult(String geoURI, double latitude, double longitude, double altitude) {
+  GeoParsedResult(double latitude, double longitude, double altitude) {
     super(ParsedResultType.GEO);
-    this.geoURI = geoURI;
     this.latitude = latitude;
     this.longitude = longitude;
     this.altitude = altitude;
   }
 
   public String getGeoURI() {
-    return geoURI;
+    StringBuffer result = new StringBuffer();
+    result.append("geo:");
+    result.append(latitude);
+    result.append(',');
+    result.append(longitude);
+    if (altitude > 0) {
+      result.append(',');
+      result.append(altitude);
+    }
+    return result.toString();
   }
 
   /**
@@ -60,11 +67,11 @@ public final class GeoParsedResult extends ParsedResult {
   }
 
   public String getDisplayResult() {
-    StringBuffer result = new StringBuffer(50);
+    StringBuffer result = new StringBuffer();
     result.append(latitude);
     result.append(", ");
     result.append(longitude);
-    if (altitude > 0.0f) {
+    if (altitude > 0.0) {
       result.append(", ");
       result.append(altitude);
       result.append('m');
index f086059..3d21bf3 100644 (file)
@@ -47,6 +47,9 @@ final class GeoResultParser extends ResultParser {
     double latitude, longitude, altitude;
     try {
       latitude = Double.parseDouble(geoURIWithoutQuery.substring(0, latitudeEnd));
+      if (latitude > 90.0 || latitude < -90.0) {
+        return null;
+      }
       if (longitudeEnd < 0) {
         longitude = Double.parseDouble(geoURIWithoutQuery.substring(latitudeEnd + 1));
         altitude = 0.0;
@@ -54,11 +57,13 @@ final class GeoResultParser extends ResultParser {
         longitude = Double.parseDouble(geoURIWithoutQuery.substring(latitudeEnd + 1, longitudeEnd));
         altitude = Double.parseDouble(geoURIWithoutQuery.substring(longitudeEnd + 1));
       }
+      if (longitude > 180.0 || longitude < -180.0 || altitude < 0) {
+        return null;
+      }
     } catch (NumberFormatException nfe) {
       return null;
     }
-    return new GeoParsedResult(rawText.startsWith("GEO:") ? "geo:" + rawText.substring(4) : rawText,
-                               latitude, longitude, altitude);
+    return new GeoParsedResult(latitude, longitude, altitude);
   }
 
 }
\ No newline at end of file
index 18613ab..ce29b55 100644 (file)
@@ -59,6 +59,8 @@ public abstract class ResultParser {
       return result;
     } else if ((result = SMSMMSResultParser.parse(theResult)) != null) {
       return result;
+    } else if ((result = SMSTOMMSTOResultParser.parse(theResult)) != null) {
+      return result;
     } else if ((result = GeoResultParser.parse(theResult)) != null) {
       return result;
     } else if ((result = URLTOResultParser.parse(theResult)) != null) {
index 88bcfa3..e309132 100644 (file)
@@ -19,15 +19,20 @@ package com.google.zxing.client.result;
 import com.google.zxing.Result;
 
 import java.util.Hashtable;
+import java.util.Vector;
 
 /**
- * <p>Parses an "sms:" URI result, which specifies a number to SMS and optional
- * "via" number. See <a href="http://gbiv.com/protocols/uri/drafts/draft-antti-gsm-sms-url-04.txt">
- * the IETF draft</a> on this.</p>
+ * <p>Parses an "sms:" URI result, which specifies a number to SMS.
+ * See <a href="http://tools.ietf.org/html/rfc5724"> RFC 5724</a> on this.</p>
  *
- * <p>This actually also parses URIs starting with "mms:", "smsto:", "mmsto:", "SMSTO:", and
- * "MMSTO:", and treats them all the same way, and effectively converts them to an "sms:" URI
- * for purposes of forwarding to the platform.</p>
+ * <p>This class supports "via" syntax for numbers, which is not part of the spec.
+ * For example "+12125551212;via=+12124440101" may appear as a number.
+ * It also supports a "subject" query parameter, which is not mentioned in the spec.
+ * These are included since they were mentioned in earlier IETF drafts and might be
+ * used.</p>
+ *
+ * <p>This actually also parses URIs starting with "mms:" and treats them all the same way,
+ * and effectively converts them to an "sms:" URI for purposes of forwarding to the platform.</p>
  *
  * @author Sean Owen
  */
@@ -41,14 +46,8 @@ final class SMSMMSResultParser extends ResultParser {
     if (rawText == null) {
       return null;
     }
-    int prefixLength;
-    if (rawText.startsWith("sms:") || rawText.startsWith("SMS:") ||
-        rawText.startsWith("mms:") || rawText.startsWith("MMS:")) {
-      prefixLength = 4;
-    } else if (rawText.startsWith("smsto:") || rawText.startsWith("SMSTO:") ||
-               rawText.startsWith("mmsto:") || rawText.startsWith("MMSTO:")) {
-      prefixLength = 6;
-    } else {
+    if (!(rawText.startsWith("sms:") || rawText.startsWith("SMS:") ||
+          rawText.startsWith("mms:") || rawText.startsWith("MMS:"))) {
       return null;
     }
 
@@ -64,40 +63,45 @@ final class SMSMMSResultParser extends ResultParser {
     }
 
     // Drop sms, query portion
-    int queryStart = rawText.indexOf('?', prefixLength);
+    int queryStart = rawText.indexOf('?', 4);
     String smsURIWithoutQuery;
     // If it's not query syntax, the question mark is part of the subject or message
     if (queryStart < 0 || !querySyntax) {
-      smsURIWithoutQuery = rawText.substring(prefixLength);
+      smsURIWithoutQuery = rawText.substring(4);
     } else {
-      smsURIWithoutQuery = rawText.substring(prefixLength, queryStart);
+      smsURIWithoutQuery = rawText.substring(4, queryStart);
     }
-    int numberEnd = smsURIWithoutQuery.indexOf(';');
-    String number;
-    String via;
+
+    int lastComma = -1;
+    int comma;
+    Vector numbers = new Vector(1);
+    Vector vias = new Vector(1);
+    while ((comma = smsURIWithoutQuery.indexOf(',', lastComma + 1)) > lastComma) {
+      String numberPart = smsURIWithoutQuery.substring(lastComma + 1, comma);
+      addNumberVia(numbers, vias, numberPart);
+      lastComma = comma;
+    }
+    addNumberVia(numbers, vias, smsURIWithoutQuery.substring(lastComma + 1));    
+
+    return new SMSParsedResult(toStringArray(numbers), toStringArray(vias), subject, body);
+  }
+
+  private static void addNumberVia(Vector numbers, Vector vias, String numberPart) {
+    int numberEnd = numberPart.indexOf(';');
     if (numberEnd < 0) {
-      number = smsURIWithoutQuery;
-      via = null;
+      numbers.addElement(numberPart);
+      vias.addElement(null);
     } else {
-      number = smsURIWithoutQuery.substring(0, numberEnd);
-      String maybeVia = smsURIWithoutQuery.substring(numberEnd + 1);
+      numbers.addElement(numberPart.substring(0, numberEnd));
+      String maybeVia = numberPart.substring(numberEnd + 1);
+      String via;
       if (maybeVia.startsWith("via=")) {
         via = maybeVia.substring(4);
       } else {
         via = null;
       }
+      vias.addElement(via);
     }
-
-    // Thanks to dominik.wild for suggesting this enhancement to support
-    // smsto:number:body URIs
-    if (body == null) {
-      int bodyStart = number.indexOf(':');
-      if (bodyStart >= 0) {
-        body = number.substring(bodyStart + 1);
-        number = number.substring(0, bodyStart);
-      }
-    }
-    return new SMSParsedResult("sms:" + number, number, via, subject, body, null);
   }
 
 }
\ No newline at end of file
index cccb4bb..93792ae 100644 (file)
@@ -21,33 +21,68 @@ package com.google.zxing.client.result;
  */
 public final class SMSParsedResult extends ParsedResult {
 
-  private final String smsURI;
-  private final String number;
-  private final String via;
+  private final String[] numbers;
+  private final String[] vias;
   private final String subject;
   private final String body;
-  private final String title;
 
-  public SMSParsedResult(String smsURI, String number, String via, String subject, String body, String title) {
+  public SMSParsedResult(String number, String via, String subject, String body) {
     super(ParsedResultType.SMS);
-    this.smsURI = smsURI;
-    this.number = number;
-    this.via = via;
+    this.numbers = new String[] {number};
+    this.vias = new String[] {via};
+    this.subject = subject;
+    this.body = body;
+  }
+
+  public SMSParsedResult(String[] numbers, String[] vias, String subject, String body) {
+    super(ParsedResultType.SMS);
+    this.numbers = numbers;
+    this.vias = vias;
     this.subject = subject;
     this.body = body;
-    this.title = title;
   }
 
   public String getSMSURI() {
-    return smsURI;
+    StringBuffer result = new StringBuffer();
+    result.append("sms:");
+    boolean first = true;
+    for (int i = 0; i < numbers.length; i++) {
+      if (first) {
+        first = false;
+      } else {
+        result.append(',');
+      }
+      result.append(numbers[i]);
+      if (vias[i] != null) {
+        result.append(";via=");
+        result.append(vias[i]);
+      }
+    }
+    boolean hasBody = body != null;
+    boolean hasSubject = subject != null;
+    if (hasBody || hasSubject) {
+      result.append('?');
+      if (hasBody) {
+        result.append("body=");
+        result.append(body);
+      }
+      if (hasSubject) {
+        if (hasBody) {
+          result.append('&');
+        }
+        result.append("subject=");
+        result.append(subject);
+      }
+    }
+    return result.toString();
   }
 
-  public String getNumber() {
-    return number;
+  public String[] getNumbers() {
+    return numbers;
   }
 
-  public String getVia() {
-    return via;
+  public String[] getVias() {
+    return vias;
   }
 
   public String getSubject() {
@@ -58,17 +93,11 @@ public final class SMSParsedResult extends ParsedResult {
     return body;
   }
 
-  public String getTitle() {
-    return title;
-  }
-
   public String getDisplayResult() {
     StringBuffer result = new StringBuffer(100);
-    maybeAppend(number, result);
-    maybeAppend(via, result);
+    maybeAppend(numbers, result);
     maybeAppend(subject, result);
     maybeAppend(body, result);
-    maybeAppend(title, result);
     return result.toString();
   }
 
diff --git a/core/src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java b/core/src/com/google/zxing/client/result/SMSTOMMSTOResultParser.java
new file mode 100644 (file)
index 0000000..76418d0
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright 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.result;
+
+import com.google.zxing.Result;
+
+/**
+ * <p>Parses an "smsto:" URI result, whose format is not standardized but appears to be like:
+ * <code>smsto:number(:body)</code>.</p>
+ *
+ * <p>This actually also parses URIs starting with "smsto:", "mmsto:", "SMSTO:", and
+ * "MMSTO:", and treats them all the same way, and effectively converts them to an "sms:" URI
+ * for purposes of forwarding to the platform.</p>
+ *
+ * @author Sean Owen
+ */
+final class SMSTOMMSTOResultParser extends ResultParser {
+
+  private SMSTOMMSTOResultParser() {
+  }
+
+  public static SMSParsedResult parse(Result result) {
+    String rawText = result.getText();
+    if (rawText == null) {
+      return null;
+    }
+    if (!(rawText.startsWith("smsto:") || rawText.startsWith("SMSTO:") ||
+          rawText.startsWith("mmsto:") || rawText.startsWith("MMSTO:"))) {
+      return null;
+    }
+    // Thanks to dominik.wild for suggesting this enhancement to support
+    // smsto:number:body URIs
+    String number = rawText.substring(6);
+    String body = null;
+    int bodyStart = number.indexOf(':');
+    if (bodyStart >= 0) {
+      body = number.substring(bodyStart + 1);
+      number = number.substring(0, bodyStart);
+    }
+    return new SMSParsedResult(number, null, null, body);
+  }
+
+}
\ No newline at end of file
index f10820e..77c56cc 100644 (file)
@@ -29,7 +29,8 @@ public final class GeoParsedResultTestCase extends TestCase {
 
   public void testGeo() {
     doTest("geo:1,2", 1.0, 2.0, 0.0);
-    doTest("geo:100.33,-32.3344,3.35", 100.33, -32.3344, 3.35);
+    doTest("geo:80.33,-32.3344,3.35", 80.33, -32.3344, 3.35);
+    doTest("geo:-20.33,132.3344,0.01", -20.33, 132.3344, 0.01);
   }
 
   private static void doTest(String contents, double latitude, double longitude, double altitude) {
index ae7ccf9..df66383 100644 (file)
@@ -158,7 +158,7 @@ public final class ParsedReaderResultTestCase extends TestCase {
     doTestResult("geo:1,2", "1.0, 2.0", ParsedResultType.GEO);
     doTestResult("GEO:1,2", "1.0, 2.0", ParsedResultType.GEO);
     doTestResult("geo:1,2,3", "1.0, 2.0, 3.0m", ParsedResultType.GEO);
-    doTestResult("geo:100.33,-32.3344,3.35", "100.33, -32.3344, 3.35m", ParsedResultType.GEO);
+    doTestResult("geo:80.33,-32.3344,3.35", "80.33, -32.3344, 3.35m", ParsedResultType.GEO);
     doTestResult("geo", "geo", ParsedResultType.TEXT);
     doTestResult("geography", "geography", ParsedResultType.TEXT);
   }
@@ -221,18 +221,22 @@ public final class ParsedReaderResultTestCase extends TestCase {
   public void testSMS() {
     doTestResult("sms:+15551212", "+15551212", ParsedResultType.SMS);
     doTestResult("SMS:+15551212", "+15551212", ParsedResultType.SMS);
+    doTestResult("sms:+15551212;via=999333", "+15551212", ParsedResultType.SMS);
+    doTestResult("sms:+15551212?subject=foo&body=bar", "+15551212\nfoo\nbar", ParsedResultType.SMS);
+    doTestResult("sms:+15551212,+12124440101", "+15551212\n+12124440101", ParsedResultType.SMS);    
+  }
+
+  public void testSMSTO() {
     doTestResult("SMSTO:+15551212", "+15551212", ParsedResultType.SMS);
     doTestResult("smsto:+15551212", "+15551212", ParsedResultType.SMS);
-    doTestResult("sms:+15551212;via=999333", "+15551212\n999333", ParsedResultType.SMS);
-    doTestResult("sms:+15551212?subject=foo&body=bar", "+15551212\nfoo\nbar", ParsedResultType.SMS);
-    doTestResult("sms:+15551212:subject", "+15551212\nsubject", ParsedResultType.SMS);
-    doTestResult("sms:+15551212:My message", "+15551212\nMy message", ParsedResultType.SMS);
+    doTestResult("smsto:+15551212:subject", "+15551212\nsubject", ParsedResultType.SMS);
+    doTestResult("smsto:+15551212:My message", "+15551212\nMy message", ParsedResultType.SMS);
     // Need to handle question mark in the subject
-    doTestResult("sms:+15551212:What's up?", "+15551212\nWhat's up?", ParsedResultType.SMS);
+    doTestResult("smsto:+15551212:What's up?", "+15551212\nWhat's up?", ParsedResultType.SMS);
     // Need to handle colon in the subject
-    doTestResult("sms:+15551212:Directions: Do this", "+15551212\nDirections: Do this",
+    doTestResult("smsto:+15551212:Directions: Do this", "+15551212\nDirections: Do this",
         ParsedResultType.SMS);
-    doTestResult("sms:212-555-1212:Here's a longer message. Should be fine.",
+    doTestResult("smsto:212-555-1212:Here's a longer message. Should be fine.",
         "212-555-1212\nHere's a longer message. Should be fine.",
         ParsedResultType.SMS);
   }
@@ -240,16 +244,20 @@ public final class ParsedReaderResultTestCase extends TestCase {
   public void testMMS() {
     doTestResult("mms:+15551212", "+15551212", ParsedResultType.SMS);
     doTestResult("MMS:+15551212", "+15551212", ParsedResultType.SMS);
+    doTestResult("mms:+15551212;via=999333", "+15551212", ParsedResultType.SMS);
+    doTestResult("mms:+15551212?subject=foo&body=bar", "+15551212\nfoo\nbar", ParsedResultType.SMS);
+    doTestResult("mms:+15551212,+12124440101", "+15551212\n+12124440101", ParsedResultType.SMS);        
+  }
+
+  public void testMMSTO() {
     doTestResult("MMSTO:+15551212", "+15551212", ParsedResultType.SMS);
     doTestResult("mmsto:+15551212", "+15551212", ParsedResultType.SMS);
-    doTestResult("mms:+15551212;via=999333", "+15551212\n999333", ParsedResultType.SMS);
-    doTestResult("mms:+15551212?subject=foo&body=bar", "+15551212\nfoo\nbar", ParsedResultType.SMS);
-    doTestResult("mms:+15551212:subject", "+15551212\nsubject", ParsedResultType.SMS);
-    doTestResult("mms:+15551212:My message", "+15551212\nMy message", ParsedResultType.SMS);
-    doTestResult("mms:+15551212:What's up?", "+15551212\nWhat's up?", ParsedResultType.SMS);
-    doTestResult("mms:+15551212:Directions: Do this", "+15551212\nDirections: Do this",
+    doTestResult("mmsto:+15551212:subject", "+15551212\nsubject", ParsedResultType.SMS);
+    doTestResult("mmsto:+15551212:My message", "+15551212\nMy message", ParsedResultType.SMS);
+    doTestResult("mmsto:+15551212:What's up?", "+15551212\nWhat's up?", ParsedResultType.SMS);
+    doTestResult("mmsto:+15551212:Directions: Do this", "+15551212\nDirections: Do this",
         ParsedResultType.SMS);
-    doTestResult("mms:212-555-1212:Here's a longer message. Should be fine.",
+    doTestResult("mmsto:212-555-1212:Here's a longer message. Should be fine.",
         "212-555-1212\nHere's a longer message. Should be fine.", ParsedResultType.SMS);
   }
 
index 6dc2848..20d362a 100644 (file)
@@ -20,6 +20,8 @@ import com.google.zxing.BarcodeFormat;
 import com.google.zxing.Result;
 import junit.framework.TestCase;
 
+import java.util.Arrays;
+
 /**
  * Tests {@link SMSParsedResult}.
  *
@@ -40,15 +42,18 @@ public final class SMSMMSParsedResultTestCase extends TestCase {
   }
 
   private static void doTest(String contents, String number, String subject, String body, String via) {
+    doTest(contents, new String[] {number}, subject, body, new String[] {via});
+  }
+
+  private static void doTest(String contents, String[] numbers, String subject, String body, String[] vias) {
     Result fakeResult = new Result(contents, null, null, BarcodeFormat.QR_CODE);
     ParsedResult result = ResultParser.parseResult(fakeResult);
     assertSame(ParsedResultType.SMS, result.getType());
     SMSParsedResult smsResult = (SMSParsedResult) result;
-    assertEquals(number, smsResult.getNumber());
+    assertTrue(Arrays.equals(numbers, smsResult.getNumbers()));
     assertEquals(subject, smsResult.getSubject());
     assertEquals(body, smsResult.getBody());
-    assertEquals(via, smsResult.getVia());
-    assertEquals("sms:" + number, smsResult.getSMSURI());
+    assertTrue(Arrays.equals(vias, smsResult.getVias()));
   }
 
 }