Create a ParsedNdefMessage class with supporting implementations.

Change-Id: I13a199ad01a726a30e8851a675386c34f2dfa605
This commit is contained in:
Nick Kralevich
2010-10-14 17:04:14 -07:00
parent ab67f493dc
commit 8149e89466
17 changed files with 377 additions and 109 deletions

View File

@@ -16,20 +16,12 @@
package com.android.apps.tag; package com.android.apps.tag;
import com.android.apps.tag.record.ParsedNdefRecord;
import com.android.apps.tag.record.SmartPoster;
import com.android.apps.tag.record.TextRecord;
import com.android.apps.tag.record.UriRecord;
import com.google.common.collect.Iterables;
import com.google.common.primitives.Bytes;
import android.net.Uri; import android.net.Uri;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord; import android.nfc.NdefRecord;
import com.google.common.primitives.Bytes;
import java.nio.charset.Charsets; import java.nio.charset.Charsets;
import java.util.ArrayList;
import java.util.List;
/** /**
* Utilities for dealing with conversions to and from NdefRecords. * Utilities for dealing with conversions to and from NdefRecords.
@@ -60,34 +52,4 @@ public class NdefUtil {
return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, return new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
NdefRecord.RTD_URI, EMPTY, payload); NdefRecord.RTD_URI, EMPTY, payload);
} }
public static Iterable<TextRecord> getTextFields(NdefMessage message) {
return Iterables.filter(getObjects(message), TextRecord.class);
}
public static Iterable<UriRecord> getUris(NdefMessage message) {
return Iterables.filter(getObjects(message), UriRecord.class);
}
/**
* Parse the provided {@code NdefMessage}, extracting all known
* objects from the message. Typically this list will consist of
* {@link String}s corresponding to NDEF text records, or {@link Uri}s
* corresponding to NDEF URI records.
* <p>
* TODO: Is this API too generic? Should we keep it?
*/
public static Iterable<ParsedNdefRecord> getObjects(NdefMessage message) {
List<ParsedNdefRecord> retval = new ArrayList<ParsedNdefRecord>();
for (NdefRecord record : message.getRecords()) {
if (UriRecord.isUri(record)) {
retval.add(UriRecord.parse(record));
} else if (TextRecord.isText(record)) {
retval.add(TextRecord.parse(record));
} else if (SmartPoster.isPoster(record)) {
retval.add(SmartPoster.parse(record));
}
}
return retval;
}
} }

View File

@@ -16,14 +16,10 @@
package com.android.apps.tag; package com.android.apps.tag;
import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
import android.content.Context; import android.content.Context;
import android.database.Cursor; import android.database.Cursor;
import android.net.Uri;
import android.nfc.FormatException; import android.nfc.FormatException;
import android.nfc.NdefMessage; import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.text.format.DateUtils; import android.text.format.DateUtils;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
@@ -32,9 +28,12 @@ import android.view.ViewGroup;
import android.widget.Adapter; import android.widget.Adapter;
import android.widget.CursorAdapter; import android.widget.CursorAdapter;
import android.widget.TextView; import android.widget.TextView;
import com.android.apps.tag.record.SmartPoster;
import com.android.apps.tag.record.TextRecord; import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
import com.android.apps.tag.record.UriRecord; import com.android.apps.tag.message.NdefMessageParser;
import com.android.apps.tag.message.ParsedNdefMessage;
import java.util.Locale;
/** /**
* A custom {@link Adapter} that renders tag entries for a list. * A custom {@link Adapter} that renders tag entries for a list.
@@ -63,23 +62,8 @@ public class TagAdapter extends CursorAdapter {
if (msg == null) { if (msg == null) {
mainLine.setText("Invalid tag"); mainLine.setText("Invalid tag");
} else { } else {
try { ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
SmartPoster poster = SmartPoster.parse(msg.getRecords()[0]); mainLine.setText(parsedMsg.getSnippet(Locale.getDefault()));
TextRecord title = poster.getTitle();
if (title != null) {
mainLine.setText(title.getText());
}
} catch (IllegalArgumentException e) {
// Not a smart poster
NdefRecord record = msg.getRecords()[0];
Uri uri = null;
try {
uri = UriRecord.parse(record).getUri();
mainLine.setText(uri.toString());
} catch (IllegalArgumentException e2) {
mainLine.setText("Not a smart poster or URL");
}
}
} }
dateLine.setText(DateUtils.getRelativeTimeSpanString( dateLine.setText(DateUtils.getRelativeTimeSpanString(
context, cursor.getLong(cursor.getColumnIndex(NdefMessagesTable.DATE)))); context, cursor.getLong(cursor.getColumnIndex(NdefMessagesTable.DATE))));

View File

@@ -16,8 +16,6 @@
package com.android.apps.tag; package com.android.apps.tag;
import com.google.common.annotations.VisibleForTesting;
import android.content.Context; import android.content.Context;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper; import android.database.sqlite.SQLiteOpenHelper;
@@ -27,6 +25,8 @@ import android.nfc.FormatException;
import android.nfc.NdefMessage; import android.nfc.NdefMessage;
import android.nfc.NdefRecord; import android.nfc.NdefRecord;
import com.google.common.annotations.VisibleForTesting;
/** /**
* Database utilities for the saved tags. * Database utilities for the saved tags.
*/ */

View File

@@ -16,8 +16,6 @@
package com.android.apps.tag; package com.android.apps.tag;
import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.Dialog; import android.app.Dialog;
@@ -35,6 +33,8 @@ import android.view.Menu;
import android.view.View; import android.view.View;
import android.widget.ListView; import android.widget.ListView;
import com.android.apps.tag.TagDBHelper.NdefMessagesTable;
/** /**
* An {@link Activity} that displays a flat list of tags that can be "opened". * An {@link Activity} that displays a flat list of tags that can be "opened".
*/ */

View File

@@ -30,10 +30,11 @@ import android.view.LayoutInflater;
import android.view.WindowManager; import android.view.WindowManager;
import android.widget.LinearLayout; import android.widget.LinearLayout;
import android.widget.TextView; import android.widget.TextView;
import com.android.apps.tag.record.ParsedNdefRecord;
import com.android.apps.tag.record.SmartPoster; import com.android.apps.tag.message.NdefMessageParser;
import com.android.apps.tag.record.TextRecord; import com.android.apps.tag.message.ParsedNdefMessage;
import com.android.apps.tag.record.UriRecord;
import java.util.Locale;
/** /**
* An {@link Activity} which handles a broadcast of a new tag that the device just discovered. * An {@link Activity} which handles a broadcast of a new tag that the device just discovered.
@@ -93,35 +94,11 @@ public class TagViewer extends Activity {
// The body of the dialog should use the light theme // The body of the dialog should use the light theme
// Build the views from the logical records in the messages // Build the views from the logical records in the messages
boolean first = true;
for (NdefMessage msg : msgs) { for (NdefMessage msg : msgs) {
Iterable<ParsedNdefRecord> objects = NdefUtil.getObjects(msg); ParsedNdefMessage parsedMsg = NdefMessageParser.parse(msg);
for (ParsedNdefRecord object : objects) { TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false);
if (!first) { text.setText(parsedMsg.getSnippet(Locale.getDefault()));
list.addView(inflater.inflate(R.layout.tag_divider, list, false)); list.addView(text);
first = false;
}
if (object instanceof TextRecord) {
TextRecord textRecord = (TextRecord) object;
TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false);
text.setText(textRecord.getText());
list.addView(text);
} else if (object instanceof UriRecord) {
UriRecord uriRecord = (UriRecord) object;
TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false);
text.setText(uriRecord.getUri().toString());
list.addView(text);
} else if (object instanceof SmartPoster) {
TextView text = (TextView) inflater.inflate(R.layout.tag_text, list, false);
SmartPoster poster = (SmartPoster) object;
TextRecord title = poster.getTitle();
if (title != null) {
text.setText(title.getText());
}
list.addView(text);
}
}
} }
} }

View File

@@ -0,0 +1,37 @@
/*
* Copyright (C) 2010 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 com.android.apps.tag.message;
import java.util.Locale;
/**
* A parsed message containing no elements.
*/
class EmptyMessage implements ParsedNdefMessage {
/* package private */ EmptyMessage() { }
@Override
public String getSnippet(Locale locale) {
return "Empty Tag"; // TODO: localize
}
@Override
public boolean isStarred() {
return false;
}
}

View File

@@ -0,0 +1,76 @@
/*
* Copyright (C) 2010 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 com.android.apps.tag.message;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import com.android.apps.tag.record.ParsedNdefRecord;
import com.android.apps.tag.record.SmartPoster;
import com.android.apps.tag.record.TextRecord;
import com.android.apps.tag.record.UriRecord;
import java.util.ArrayList;
import java.util.List;
/**
* Utility class for creating {@link ParsedNdefMessage}s.
*/
public class NdefMessageParser {
// Utility class
private NdefMessageParser() { }
/** Parse an NdefMessage */
public static ParsedNdefMessage parse(NdefMessage message) {
List<ParsedNdefRecord> elements = getRecords(message);
if (elements.isEmpty()) {
return new EmptyMessage();
}
ParsedNdefRecord first = elements.get(0);
if (elements.size() == 1) {
if (first instanceof SmartPoster) {
return new SmartPosterMessage((SmartPoster) first);
}
if (first instanceof TextRecord) {
return new TextMessage((TextRecord) first);
}
if (first instanceof UriRecord) {
return new UriMessage((UriRecord) first);
}
}
return new UnknownMessage(elements);
}
public static List<ParsedNdefRecord> getRecords(NdefMessage message) {
List<ParsedNdefRecord> elements = new ArrayList<ParsedNdefRecord>();
for (NdefRecord record : message.getRecords()) {
if (UriRecord.isUri(record)) {
elements.add(UriRecord.parse(record));
} else if (TextRecord.isText(record)) {
elements.add(TextRecord.parse(record));
} else if (SmartPoster.isPoster(record)) {
elements.add(SmartPoster.parse(record));
}
}
return elements;
}
}

View File

@@ -0,0 +1,35 @@
/*
* Copyright (C) 2010 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 com.android.apps.tag.message;
import java.util.Locale;
/**
* A parsed version of an {@link android.nfc.NdefMessage}
*/
public interface ParsedNdefMessage {
/**
* Returns the snippet information associated with the NdefMessage
* most appropriate for the given {@code locale}.
*/
public String getSnippet(Locale locale);
// TODO: Determine if this is the best place for holding whether
// the user has starred this parsed message.
public boolean isStarred();
}

View File

@@ -0,0 +1,48 @@
/*
* Copyright (C) 2010 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 com.android.apps.tag.message;
import com.android.apps.tag.record.SmartPoster;
import com.android.apps.tag.record.TextRecord;
import com.google.common.base.Preconditions;
import java.util.Locale;
/**
* A message consisting of one {@link SmartPoster} object.
*/
class SmartPosterMessage implements ParsedNdefMessage {
private final SmartPoster mPoster;
SmartPosterMessage(SmartPoster poster) {
mPoster = Preconditions.checkNotNull(poster);
}
@Override
public String getSnippet(Locale locale) {
TextRecord title = mPoster.getTitle();
if (title == null) {
return mPoster.getUriRecord().getUri().toString();
}
return title.getText();
}
@Override
public boolean isStarred() {
return false;
}
}

View File

@@ -0,0 +1,43 @@
/*
* Copyright (C) 2010 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 com.android.apps.tag.message;
import com.android.apps.tag.record.TextRecord;
import com.google.common.base.Preconditions;
import java.util.Locale;
/**
* A message containing one text element
*/
class TextMessage implements ParsedNdefMessage {
private final TextRecord mRecord;
TextMessage(TextRecord record) {
mRecord = Preconditions.checkNotNull(record);
}
@Override
public String getSnippet(Locale locale) {
return mRecord.getText();
}
@Override
public boolean isStarred() {
return false;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2010 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 com.android.apps.tag.message;
import com.android.apps.tag.record.ParsedNdefRecord;
import com.google.common.collect.ImmutableList;
import java.util.Locale;
/**
* The catchall parsed message format for when nothing else better applies.
*/
class UnknownMessage implements ParsedNdefMessage {
private final ImmutableList<ParsedNdefRecord> mRecords;
UnknownMessage(Iterable<ParsedNdefRecord> records) {
mRecords = ImmutableList.copyOf(records);
}
@Override
public String getSnippet(Locale locale) {
// TODO: localize
return "Unknown record type with " + mRecords.size() + " elements.";
}
@Override
public boolean isStarred() {
return false;
}
}

View File

@@ -0,0 +1,45 @@
/*
* Copyright (C) 2010 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 com.android.apps.tag.message;
import com.android.apps.tag.record.UriRecord;
import com.google.common.base.Preconditions;
import java.util.Locale;
/**
* A {@link ParsedNdefMessage} consisting of one {@link UriRecord}.
*/
class UriMessage implements ParsedNdefMessage {
private final UriRecord mRecord;
UriMessage(UriRecord record) {
mRecord = Preconditions.checkNotNull(record);
}
@Override
public String getSnippet(Locale locale) {
// URIs cannot be localized
return mRecord.getUri().toString();
}
@Override
public boolean isStarred() {
return false;
}
}

View File

@@ -16,16 +16,16 @@
package com.android.apps.tag.record; package com.android.apps.tag.record;
import com.android.apps.tag.NdefUtil;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import android.nfc.FormatException; import android.nfc.FormatException;
import android.nfc.NdefMessage; import android.nfc.NdefMessage;
import android.nfc.NdefRecord; import android.nfc.NdefRecord;
import java.util.Arrays; import com.android.apps.tag.message.NdefMessageParser;
import com.google.common.base.Preconditions;
import com.google.common.collect.Iterables;
import java.util.Arrays;
import java.util.NoSuchElementException;
import javax.annotation.Nullable; import javax.annotation.Nullable;
/** /**
@@ -73,8 +73,12 @@ public class SmartPoster implements ParsedNdefRecord {
Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER)); Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_SMART_POSTER));
try { try {
NdefMessage subRecords = new NdefMessage(record.getPayload()); NdefMessage subRecords = new NdefMessage(record.getPayload());
UriRecord uri = Iterables.getOnlyElement(NdefUtil.getUris(subRecords));
Iterable<TextRecord> textFields = NdefUtil.getTextFields(subRecords); Iterable<ParsedNdefRecord> records = NdefMessageParser.getRecords(subRecords);
UriRecord uri = Iterables.getOnlyElement(Iterables.filter(records, UriRecord.class));
Iterable<TextRecord> textFields = Iterables.filter(records, TextRecord.class);
TextRecord title = null; TextRecord title = null;
if (!Iterables.isEmpty(textFields)) { if (!Iterables.isEmpty(textFields)) {
title = Iterables.get(textFields, 0); title = Iterables.get(textFields, 0);
@@ -83,6 +87,8 @@ public class SmartPoster implements ParsedNdefRecord {
return new SmartPoster(uri, title); return new SmartPoster(uri, title);
} catch (FormatException e) { } catch (FormatException e) {
throw new IllegalArgumentException(e); throw new IllegalArgumentException(e);
} catch (NoSuchElementException e) {
throw new IllegalArgumentException(e);
} }
} }

View File

@@ -17,6 +17,7 @@
package com.android.apps.tag.record; package com.android.apps.tag.record;
import android.nfc.NdefRecord; import android.nfc.NdefRecord;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
@@ -27,6 +28,7 @@ import java.util.Arrays;
*/ */
public class TextRecord implements ParsedNdefRecord { public class TextRecord implements ParsedNdefRecord {
/** ISO/IANA language code */
private final String mLanguageCode; private final String mLanguageCode;
private final String mText; private final String mText;
@@ -44,6 +46,11 @@ public class TextRecord implements ParsedNdefRecord {
return mText; return mText;
} }
/**
* Returns the ISO/IANA language code associated with this text element.
*
* TODO: this should return a {@link java.util.Locale}
*/
public String getLanguageCode() { public String getLanguageCode() {
return mLanguageCode; return mLanguageCode;
} }

View File

@@ -18,6 +18,7 @@ package com.android.apps.tag.record;
import android.net.Uri; import android.net.Uri;
import android.nfc.NdefRecord; import android.nfc.NdefRecord;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.BiMap; import com.google.common.collect.BiMap;
import com.google.common.collect.ImmutableBiMap; import com.google.common.collect.ImmutableBiMap;

View File

@@ -16,10 +16,11 @@
package com.android.apps.tag; package com.android.apps.tag;
import android.nfc.NdefRecord;
import android.test.AndroidTestCase; import android.test.AndroidTestCase;
import com.android.apps.tag.record.TextRecord; import com.android.apps.tag.record.TextRecord;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import android.nfc.NdefRecord;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;

View File

@@ -16,8 +16,9 @@
package com.android.apps.tag; package com.android.apps.tag;
import android.test.AndroidTestCase;
import android.nfc.NdefMessage; import android.nfc.NdefMessage;
import android.test.AndroidTestCase;
import com.android.apps.tag.record.SmartPoster; import com.android.apps.tag.record.SmartPoster;
/** /**