summaryrefslogtreecommitdiffstats
path: root/src/android/bluetooth/client/map/BluetoothMapBmessageParser.java
diff options
context:
space:
mode:
authorHemant Gupta <hemantg@codeaurora.org>2014-04-08 16:04:13 +0530
committerMike Lockwood <lockwood@google.com>2014-07-01 10:32:00 -0700
commit192d793d2586b620027edd5b45ff4c72a86cc7be (patch)
treef1dff5dc934a746446b40edaf6916f53890d68b7 /src/android/bluetooth/client/map/BluetoothMapBmessageParser.java
parent6d420592e1cc06422be9369ed01ac9027b2a1101 (diff)
downloadandroid_frameworks_opt_bluetooth-192d793d2586b620027edd5b45ff4c72a86cc7be.tar.gz
android_frameworks_opt_bluetooth-192d793d2586b620027edd5b45ff4c72a86cc7be.tar.bz2
android_frameworks_opt_bluetooth-192d793d2586b620027edd5b45ff4c72a86cc7be.zip
Bluetooth: Support MAP and PBAP Client role on Bluedroid.
Implementation of android.bluetooth.client.pbap and android.bluetooth.client.map STATIC JAVA lib for PBAP and MAP client role(s). These static libraries can be used by application for PBAP and MAP Client role support on Bluedroid. Change-Id: I173d2c095661704e2efb39516837c6b681193e9a
Diffstat (limited to 'src/android/bluetooth/client/map/BluetoothMapBmessageParser.java')
-rw-r--r--src/android/bluetooth/client/map/BluetoothMapBmessageParser.java421
1 files changed, 421 insertions, 0 deletions
diff --git a/src/android/bluetooth/client/map/BluetoothMapBmessageParser.java b/src/android/bluetooth/client/map/BluetoothMapBmessageParser.java
new file mode 100644
index 0000000..fa3d817
--- /dev/null
+++ b/src/android/bluetooth/client/map/BluetoothMapBmessageParser.java
@@ -0,0 +1,421 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * 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 android.bluetooth.client.map;
+
+import android.util.Log;
+
+import com.android.vcard.VCardEntry;
+import com.android.vcard.VCardEntryConstructor;
+import com.android.vcard.VCardEntryHandler;
+import com.android.vcard.VCardParser;
+import com.android.vcard.VCardParser_V21;
+import com.android.vcard.VCardParser_V30;
+import com.android.vcard.exception.VCardException;
+import com.android.vcard.exception.VCardVersionException;
+import android.bluetooth.client.map.BluetoothMapBmessage.Status;
+import android.bluetooth.client.map.BluetoothMapBmessage.Type;
+import android.bluetooth.client.map.utils.BmsgTokenizer;
+import android.bluetooth.client.map.utils.BmsgTokenizer.Property;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.text.ParseException;
+
+class BluetoothMapBmessageParser {
+
+ private final static String TAG = "BluetoothMapBmessageParser";
+
+ private final static String CRLF = "\r\n";
+
+ private final static Property BEGIN_BMSG = new Property("BEGIN", "BMSG");
+ private final static Property END_BMSG = new Property("END", "BMSG");
+
+ private final static Property BEGIN_VCARD = new Property("BEGIN", "VCARD");
+ private final static Property END_VCARD = new Property("END", "VCARD");
+
+ private final static Property BEGIN_BENV = new Property("BEGIN", "BENV");
+ private final static Property END_BENV = new Property("END", "BENV");
+
+ private final static Property BEGIN_BBODY = new Property("BEGIN", "BBODY");
+ private final static Property END_BBODY = new Property("END", "BBODY");
+
+ private final static Property BEGIN_MSG = new Property("BEGIN", "MSG");
+ private final static Property END_MSG = new Property("END", "MSG");
+
+ private final static int CRLF_LEN = 2;
+
+ /*
+ * length of "container" for 'message' in bmessage-body-content:
+ * BEGIN:MSG<CRLF> + <CRLF> + END:MSG<CRFL>
+ */
+ private final static int MSG_CONTAINER_LEN = 22;
+
+ private BmsgTokenizer mParser;
+
+ private final BluetoothMapBmessage mBmsg;
+
+ private BluetoothMapBmessageParser() {
+ mBmsg = new BluetoothMapBmessage();
+ }
+
+ static public BluetoothMapBmessage createBmessage(String str) {
+ BluetoothMapBmessageParser p = new BluetoothMapBmessageParser();
+
+ try {
+ p.parse(str);
+ } catch (IOException e) {
+ Log.e(TAG, "I/O exception when parsing bMessage", e);
+ return null;
+ } catch (ParseException e) {
+ Log.e(TAG, "Cannot parse bMessage", e);
+ return null;
+ }
+
+ return p.mBmsg;
+ }
+
+ private ParseException expected(Property... props) {
+ boolean first = true;
+ StringBuilder sb = new StringBuilder();
+
+ for (Property prop : props) {
+ if (!first) {
+ sb.append(" or ");
+ }
+ sb.append(prop);
+ first = false;
+ }
+
+ return new ParseException("Expected: " + sb.toString(), mParser.pos());
+ }
+
+ private void parse(String str) throws IOException, ParseException {
+
+ Property prop;
+
+ /*
+ * <bmessage-object>::= { "BEGIN:BMSG" <CRLF> <bmessage-property>
+ * [<bmessage-originator>]* <bmessage-envelope> "END:BMSG" <CRLF> }
+ */
+
+ mParser = new BmsgTokenizer(str + CRLF);
+
+ prop = mParser.next();
+ if (!prop.equals(BEGIN_BMSG)) {
+ throw expected(BEGIN_BMSG);
+ }
+
+ prop = parseProperties();
+
+ while (prop.equals(BEGIN_VCARD)) {
+
+ /* <bmessage-originator>::= <vcard> <CRLF> */
+
+ StringBuilder vcard = new StringBuilder();
+ prop = extractVcard(vcard);
+
+ VCardEntry entry = parseVcard(vcard.toString());
+ mBmsg.mOriginators.add(entry);
+ }
+
+ if (!prop.equals(BEGIN_BENV)) {
+ throw expected(BEGIN_BENV);
+ }
+
+ prop = parseEnvelope(1);
+
+ if (!prop.equals(END_BMSG)) {
+ throw expected(END_BENV);
+ }
+
+ /*
+ * there should be no meaningful data left in stream here so we just
+ * ignore whatever is left
+ */
+
+ mParser = null;
+ }
+
+ private Property parseProperties() throws ParseException {
+
+ Property prop;
+
+ /*
+ * <bmessage-property>::=<bmessage-version-property>
+ * <bmessage-readstatus-property> <bmessage-type-property>
+ * <bmessage-folder-property> <bmessage-version-property>::="VERSION:"
+ * <common-digit>*"."<common-digit>* <CRLF>
+ * <bmessage-readstatus-property>::="STATUS:" 'readstatus' <CRLF>
+ * <bmessage-type-property>::="TYPE:" 'type' <CRLF>
+ * <bmessage-folder-property>::="FOLDER:" 'foldername' <CRLF>
+ */
+
+ do {
+ prop = mParser.next();
+
+ if (prop.name.equals("VERSION")) {
+ mBmsg.mBmsgVersion = prop.value;
+
+ } else if (prop.name.equals("STATUS")) {
+ for (Status s : Status.values()) {
+ if (prop.value.equals(s.toString())) {
+ mBmsg.mBmsgStatus = s;
+ break;
+ }
+ }
+
+ } else if (prop.name.equals("TYPE")) {
+ for (Type t : Type.values()) {
+ if (prop.value.equals(t.toString())) {
+ mBmsg.mBmsgType = t;
+ break;
+ }
+ }
+
+ } else if (prop.name.equals("FOLDER")) {
+ mBmsg.mBmsgFolder = prop.value;
+
+ }
+
+ } while (!prop.equals(BEGIN_VCARD) && !prop.equals(BEGIN_BENV));
+
+ return prop;
+ }
+
+ private Property parseEnvelope(int level) throws IOException, ParseException {
+
+ Property prop;
+
+ /*
+ * we can support as many nesting level as we want, but MAP spec clearly
+ * defines that there should be no more than 3 levels. so we verify it
+ * here.
+ */
+
+ if (level > 3) {
+ throw new ParseException("bEnvelope is nested more than 3 times", mParser.pos());
+ }
+
+ /*
+ * <bmessage-envelope> ::= { "BEGIN:BENV" <CRLF> [<bmessage-recipient>]*
+ * <bmessage-envelope> | <bmessage-content> "END:BENV" <CRLF> }
+ */
+
+ prop = mParser.next();
+
+ while (prop.equals(BEGIN_VCARD)) {
+
+ /* <bmessage-originator>::= <vcard> <CRLF> */
+
+ StringBuilder vcard = new StringBuilder();
+ prop = extractVcard(vcard);
+
+ if (level == 1) {
+ VCardEntry entry = parseVcard(vcard.toString());
+ mBmsg.mRecipients.add(entry);
+ }
+ }
+
+ if (prop.equals(BEGIN_BENV)) {
+ prop = parseEnvelope(level + 1);
+
+ } else if (prop.equals(BEGIN_BBODY)) {
+ prop = parseBody();
+
+ } else {
+ throw expected(BEGIN_BENV, BEGIN_BBODY);
+ }
+
+ if (!prop.equals(END_BENV)) {
+ throw expected(END_BENV);
+ }
+
+ return mParser.next();
+ }
+
+ private Property parseBody() throws IOException, ParseException {
+
+ Property prop;
+
+ /*
+ * <bmessage-content>::= { "BEGIN:BBODY"<CRLF> [<bmessage-body-part-ID>
+ * <CRLF>] <bmessage-body-property> <bmessage-body-content>* <CRLF>
+ * "END:BBODY"<CRLF> } <bmessage-body-part-ID>::="PARTID:" 'Part-ID'
+ * <bmessage-body-property>::=[<bmessage-body-encoding-property>]
+ * [<bmessage-body-charset-property>]
+ * [<bmessage-body-language-property>]
+ * <bmessage-body-content-length-property>
+ * <bmessage-body-encoding-property>::="ENCODING:"'encoding' <CRLF>
+ * <bmessage-body-charset-property>::="CHARSET:"'charset' <CRLF>
+ * <bmessage-body-language-property>::="LANGUAGE:"'language' <CRLF>
+ * <bmessage-body-content-length-property>::= "LENGTH:" <common-digit>*
+ * <CRLF>
+ */
+
+ do {
+ prop = mParser.next();
+
+ if (prop.name.equals("PARTID")) {
+ } else if (prop.name.equals("ENCODING")) {
+ mBmsg.mBbodyEncoding = prop.value;
+
+ } else if (prop.name.equals("CHARSET")) {
+ mBmsg.mBbodyCharset = prop.value;
+
+ } else if (prop.name.equals("LANGUAGE")) {
+ mBmsg.mBbodyLanguage = prop.value;
+
+ } else if (prop.name.equals("LENGTH")) {
+ try {
+ mBmsg.mBbodyLength = Integer.valueOf(prop.value);
+ } catch (NumberFormatException e) {
+ throw new ParseException("Invalid LENGTH value", mParser.pos());
+ }
+
+ }
+
+ } while (!prop.equals(BEGIN_MSG));
+
+ /*
+ * <bmessage-body-content>::={ "BEGIN:MSG"<CRLF> 'message'<CRLF>
+ * "END:MSG"<CRLF> }
+ */
+
+ int messageLen = mBmsg.mBbodyLength - MSG_CONTAINER_LEN;
+ int offset = messageLen + CRLF_LEN;
+ int restartPos = mParser.pos() + offset;
+
+ /*
+ * length is specified in bytes so we need to convert from unicode
+ * string back to bytes array
+ */
+
+ String remng = mParser.remaining();
+ byte[] data = remng.getBytes();
+
+ /* restart parsing from after 'message'<CRLF> */
+ mParser = new BmsgTokenizer(new String(data, offset, data.length - offset), restartPos);
+
+ prop = mParser.next(true);
+
+ if (prop != null && prop.equals(END_MSG)) {
+ mBmsg.mMessage = new String(data, 0, messageLen);
+ } else {
+
+ data = null;
+
+ /*
+ * now we check if bMessage can be parsed if LENGTH is handled as
+ * number of characters instead of number of bytes
+ */
+
+ Log.w(TAG, "byte LENGTH seems to be invalid, trying with char length");
+
+ mParser = new BmsgTokenizer(remng.substring(offset));
+
+ prop = mParser.next();
+
+ if (!prop.equals(END_MSG)) {
+ throw expected(END_MSG);
+ }
+
+ mBmsg.mMessage = remng.substring(0, messageLen);
+ }
+
+ prop = mParser.next();
+
+ if (!prop.equals(END_BBODY)) {
+ throw expected(END_BBODY);
+ }
+
+ return mParser.next();
+ }
+
+ private Property extractVcard(StringBuilder out) throws IOException, ParseException {
+ Property prop;
+
+ out.append(BEGIN_VCARD).append(CRLF);
+
+ do {
+ prop = mParser.next();
+ out.append(prop).append(CRLF);
+ } while (!prop.equals(END_VCARD));
+
+ return mParser.next();
+ }
+
+ private class VcardHandler implements VCardEntryHandler {
+
+ VCardEntry vcard;
+
+ @Override
+ public void onStart() {
+ }
+
+ @Override
+ public void onEntryCreated(VCardEntry entry) {
+ vcard = entry;
+ }
+
+ @Override
+ public void onEnd() {
+ }
+ };
+
+ private VCardEntry parseVcard(String str) throws IOException, ParseException {
+ VCardEntry vcard = null;
+
+ try {
+ VCardParser p = new VCardParser_V21();
+ VCardEntryConstructor c = new VCardEntryConstructor();
+ VcardHandler handler = new VcardHandler();
+ c.addEntryHandler(handler);
+ p.addInterpreter(c);
+ p.parse(new ByteArrayInputStream(str.getBytes()));
+
+ vcard = handler.vcard;
+
+ } catch (VCardVersionException e1) {
+
+ try {
+ VCardParser p = new VCardParser_V30();
+ VCardEntryConstructor c = new VCardEntryConstructor();
+ VcardHandler handler = new VcardHandler();
+ c.addEntryHandler(handler);
+ p.addInterpreter(c);
+ p.parse(new ByteArrayInputStream(str.getBytes()));
+
+ vcard = handler.vcard;
+
+ } catch (VCardVersionException e2) {
+ // will throw below
+ } catch (VCardException e2) {
+ // will throw below
+ }
+
+ } catch (VCardException e1) {
+ // will throw below
+ }
+
+ if (vcard == null) {
+ throw new ParseException("Cannot parse vCard object (neither 2.1 nor 3.0?)",
+ mParser.pos());
+ }
+
+ return vcard;
+ }
+}