Overhaul contact encoding in Android; encode phone type too

git-svn-id: https://zxing.googlecode.com/svn/trunk@3012 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2014-01-07 22:00:38 +00:00
parent 83277b92eb
commit dfd6fe71c2
8 changed files with 382 additions and 150 deletions

View file

@ -18,6 +18,7 @@ package com.google.zxing.client.android.encode;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
/**
* Implementations encode according to some scheme for encoding contact information, like VCard or
@ -31,12 +32,13 @@ abstract class ContactEncoder {
* @return first, the best effort encoding of all data in the appropriate format; second, a
* display-appropriate version of the contact information
*/
abstract String[] encode(Iterable<String> names,
abstract String[] encode(List<String> names,
String organization,
Iterable<String> addresses,
Iterable<String> phones,
Iterable<String> emails,
Iterable<String> urls,
List<String> addresses,
List<String> phones,
List<String> phoneTypes,
List<String> emails,
List<String> urls,
String note);
/**
@ -50,37 +52,38 @@ abstract class ContactEncoder {
return result.isEmpty() ? null : result;
}
static void doAppend(StringBuilder newContents,
StringBuilder newDisplayContents,
String prefix,
String value,
Formatter fieldFormatter,
char terminator) {
static void append(StringBuilder newContents,
StringBuilder newDisplayContents,
String prefix,
String value,
Formatter fieldFormatter,
char terminator) {
String trimmed = trim(value);
if (trimmed != null) {
newContents.append(prefix).append(':').append(fieldFormatter.format(trimmed)).append(terminator);
newContents.append(prefix).append(fieldFormatter.format(trimmed, 0)).append(terminator);
newDisplayContents.append(trimmed).append('\n');
}
}
static void doAppendUpToUnique(StringBuilder newContents,
StringBuilder newDisplayContents,
String prefix,
Iterable<String> values,
int max,
Formatter formatter,
Formatter fieldFormatter,
char terminator) {
static void appendUpToUnique(StringBuilder newContents,
StringBuilder newDisplayContents,
String prefix,
List<String> values,
int max,
Formatter displayFormatter,
Formatter fieldFormatter,
char terminator) {
if (values == null) {
return;
}
int count = 0;
Collection<String> uniques = new HashSet<>(2);
for (String value : values) {
for (int i = 0; i < values.size(); i++) {
String value = values.get(i);
String trimmed = trim(value);
if (trimmed != null && !trimmed.isEmpty() && !uniques.contains(trimmed)) {
newContents.append(prefix).append(':').append(fieldFormatter.format(trimmed)).append(terminator);
String display = formatter == null ? trimmed : formatter.format(trimmed);
newContents.append(prefix).append(fieldFormatter.format(trimmed, i)).append(terminator);
CharSequence display = displayFormatter == null ? trimmed : displayFormatter.format(trimmed, i);
newDisplayContents.append(display).append('\n');
if (++count == max) {
break;

View file

@ -22,7 +22,12 @@ package com.google.zxing.client.android.encode;
* @author Sean Owen
*/
interface Formatter {
String format(String source);
/**
* @param value value to format
* @param index index of value in a list of values to be formatted
* @return formatted value
*/
CharSequence format(CharSequence value, int index);
}

View file

@ -18,6 +18,7 @@ package com.google.zxing.client.android.encode;
import android.telephony.PhoneNumberUtils;
import java.util.List;
import java.util.regex.Pattern;
/**
@ -27,72 +28,70 @@ import java.util.regex.Pattern;
*/
final class MECARDContactEncoder extends ContactEncoder {
private static final Pattern RESERVED_MECARD_CHARS = Pattern.compile("([\\\\:;])");
private static final Pattern NEWLINE = Pattern.compile("\\n");
private static final Pattern COMMA = Pattern.compile(",");
private static final Formatter MECARD_FIELD_FORMATTER = new Formatter() {
@Override
public String format(String source) {
return NEWLINE.matcher(RESERVED_MECARD_CHARS.matcher(source).replaceAll("\\\\$1")).replaceAll("");
}
};
private static final char TERMINATOR = ';';
private static final Pattern NOT_DIGITS = Pattern.compile("[^0-9]+");
@Override
public String[] encode(Iterable<String> names,
public String[] encode(List<String> names,
String organization,
Iterable<String> addresses,
Iterable<String> phones,
Iterable<String> emails,
Iterable<String> urls,
List<String> addresses,
List<String> phones,
List<String> phoneTypes,
List<String> emails,
List<String> urls,
String note) {
StringBuilder newContents = new StringBuilder(100);
newContents.append("MECARD:");
StringBuilder newDisplayContents = new StringBuilder(100);
appendUpToUnique(newContents, newDisplayContents, "N", names, 1, new Formatter() {
@Override
public String format(String source) {
return source == null ? null : COMMA.matcher(source).replaceAll("");
}
});
append(newContents, newDisplayContents, "ORG", organization);
appendUpToUnique(newContents, newDisplayContents, "ADR", addresses, 1, null);
appendUpToUnique(newContents, newDisplayContents, "TEL", phones, Integer.MAX_VALUE, new Formatter() {
@Override
public String format(String source) {
CharSequence s = PhoneNumberUtils.formatNumber(source);
return s == null ? null : NOT_DIGITS.matcher(s).replaceAll("");
}
});
appendUpToUnique(newContents, newDisplayContents, "EMAIL", emails, Integer.MAX_VALUE, null);
appendUpToUnique(newContents, newDisplayContents, "URL", urls, Integer.MAX_VALUE, null);
append(newContents, newDisplayContents, "NOTE", note);
Formatter fieldFormatter = new MECARDFieldFormatter();
appendUpToUnique(newContents, newDisplayContents, "N", names, 1, new
MECARDNameDisplayFormatter(), fieldFormatter, TERMINATOR);
append(newContents, newDisplayContents, "ORG", organization, fieldFormatter, TERMINATOR);
appendUpToUnique(newContents, newDisplayContents, "ADR", addresses, 1, null, fieldFormatter, TERMINATOR);
appendUpToUnique(newContents, newDisplayContents, "TEL", phones, Integer.MAX_VALUE,
new MECARDTelDisplayFormatter(), fieldFormatter, TERMINATOR);
appendUpToUnique(newContents, newDisplayContents, "EMAIL", emails, Integer.MAX_VALUE, null,
fieldFormatter, TERMINATOR);
appendUpToUnique(newContents, newDisplayContents, "URL", urls, Integer.MAX_VALUE, null,
fieldFormatter, TERMINATOR);
append(newContents, newDisplayContents, "NOTE", note, fieldFormatter, TERMINATOR);
newContents.append(';');
return new String[] { newContents.toString(), newDisplayContents.toString() };
}
private static void append(StringBuilder newContents,
StringBuilder newDisplayContents,
String prefix,
String value) {
doAppend(newContents, newDisplayContents, prefix, value, MECARD_FIELD_FORMATTER, TERMINATOR);
private static class MECARDFieldFormatter implements Formatter {
private static final Pattern RESERVED_MECARD_CHARS = Pattern.compile("([\\\\:;])");
private static final Pattern NEWLINE = Pattern.compile("\\n");
@Override
public CharSequence format(CharSequence value, int index) {
return ':' + NEWLINE.matcher(RESERVED_MECARD_CHARS.matcher(value).replaceAll("\\\\$1")).replaceAll("");
}
}
private static void appendUpToUnique(StringBuilder newContents,
StringBuilder newDisplayContents,
String prefix,
Iterable<String> values,
int max,
Formatter formatter) {
doAppendUpToUnique(newContents,
newDisplayContents,
prefix,
values,
max,
formatter,
MECARD_FIELD_FORMATTER,
TERMINATOR);
private static class MECARDTelDisplayFormatter implements Formatter {
private static final Pattern NOT_DIGITS = Pattern.compile("[^0-9]+");
@Override
public CharSequence format(CharSequence value, int index) {
return NOT_DIGITS.matcher(PhoneNumberUtils.formatNumber(value.toString())).replaceAll("");
}
}
private static class MECARDNameDisplayFormatter implements Formatter {
private static final Pattern COMMA = Pattern.compile(",");
@Override
public CharSequence format(CharSequence value, int index) {
return COMMA.matcher(value).replaceAll("");
}
}
}

View file

@ -43,9 +43,9 @@ import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
/**
@ -259,26 +259,22 @@ final class QRCodeEncoder {
String name = bundle.getString(ContactsContract.Intents.Insert.NAME);
String organization = bundle.getString(ContactsContract.Intents.Insert.COMPANY);
String address = bundle.getString(ContactsContract.Intents.Insert.POSTAL);
Collection<String> phones = new ArrayList<>(Contents.PHONE_KEYS.length);
for (int x = 0; x < Contents.PHONE_KEYS.length; x++) {
phones.add(bundle.getString(Contents.PHONE_KEYS[x]));
}
Collection<String> emails = new ArrayList<>(Contents.EMAIL_KEYS.length);
for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) {
emails.add(bundle.getString(Contents.EMAIL_KEYS[x]));
}
List<String> phones = getAllBundleValues(bundle, Contents.PHONE_KEYS);
List<String> phoneTypes = getAllBundleValues(bundle, Contents.PHONE_TYPE_KEYS);
List<String> emails = getAllBundleValues(bundle, Contents.EMAIL_KEYS);
String url = bundle.getString(Contents.URL_KEY);
Iterable<String> urls = url == null ? null : Collections.singletonList(url);
List<String> urls = url == null ? null : Collections.singletonList(url);
String note = bundle.getString(Contents.NOTE_KEY);
ContactEncoder mecardEncoder = useVCard ? new VCardContactEncoder() : new MECARDContactEncoder();
String[] encoded = mecardEncoder.encode(Collections.singleton(name),
organization,
Collections.singleton(address),
phones,
emails,
urls,
note);
ContactEncoder encoder = useVCard ? new VCardContactEncoder() : new MECARDContactEncoder();
String[] encoded = encoder.encode(Collections.singletonList(name),
organization,
Collections.singletonList(address),
phones,
phoneTypes,
emails,
urls,
note);
// Make sure we've encoded at least one field.
if (!encoded[1].isEmpty()) {
contents = encoded[0];
@ -307,14 +303,24 @@ final class QRCodeEncoder {
}
}
private static List<String> getAllBundleValues(Bundle bundle, String[] keys) {
List<String> values = new ArrayList<>(keys.length);
for (String key : keys) {
Object value = bundle.get(key);
values.add(value == null ? null : value.toString());
}
return values;
}
private void encodeQRCodeContents(AddressBookParsedResult contact) {
ContactEncoder encoder = useVCard ? new VCardContactEncoder() : new MECARDContactEncoder();
String[] encoded = encoder.encode(toIterable(contact.getNames()),
String[] encoded = encoder.encode(toList(contact.getNames()),
contact.getOrg(),
toIterable(contact.getAddresses()),
toIterable(contact.getPhoneNumbers()),
toIterable(contact.getEmails()),
toIterable(contact.getURLs()),
toList(contact.getAddresses()),
toList(contact.getPhoneNumbers()),
null,
toList(contact.getEmails()),
toList(contact.getURLs()),
null);
// Make sure we've encoded at least one field.
if (!encoded[1].isEmpty()) {
@ -324,7 +330,7 @@ final class QRCodeEncoder {
}
}
private static Iterable<String> toIterable(String[] values) {
private static List<String> toList(String[] values) {
return values == null ? null : Arrays.asList(values);
}

View file

@ -16,9 +16,15 @@
package com.google.zxing.client.android.encode;
import android.telephony.PhoneNumberUtils;
import android.provider.ContactsContract;
import java.util.regex.Pattern;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Encodes contact information according to the vCard format.
@ -27,65 +33,123 @@ import java.util.regex.Pattern;
*/
final class VCardContactEncoder extends ContactEncoder {
private static final Pattern RESERVED_VCARD_CHARS = Pattern.compile("([\\\\,;])");
private static final Pattern NEWLINE = Pattern.compile("\\n");
private static final Formatter VCARD_FIELD_FORMATTER = new Formatter() {
@Override
public String format(String source) {
return NEWLINE.matcher(RESERVED_VCARD_CHARS.matcher(source).replaceAll("\\\\$1")).replaceAll("");
}
};
private static final char TERMINATOR = '\n';
@Override
public String[] encode(Iterable<String> names,
public String[] encode(List<String> names,
String organization,
Iterable<String> addresses,
Iterable<String> phones,
Iterable<String> emails,
Iterable<String> urls,
List<String> addresses,
List<String> phones,
List<String> phoneTypes,
List<String> emails,
List<String> urls,
String note) {
StringBuilder newContents = new StringBuilder(100);
newContents.append("BEGIN:VCARD").append(TERMINATOR);
newContents.append("VERSION:3.0").append(TERMINATOR);
StringBuilder newDisplayContents = new StringBuilder(100);
appendUpToUnique(newContents, newDisplayContents, "N", names, 1, null);
append(newContents, newDisplayContents, "ORG", organization);
appendUpToUnique(newContents, newDisplayContents, "ADR", addresses, 1, null);
appendUpToUnique(newContents, newDisplayContents, "TEL", phones, Integer.MAX_VALUE, new Formatter() {
@Override
public String format(String source) {
return PhoneNumberUtils.formatNumber(source);
}
});
appendUpToUnique(newContents, newDisplayContents, "EMAIL", emails, Integer.MAX_VALUE, null);
appendUpToUnique(newContents, newDisplayContents, "URL", urls, Integer.MAX_VALUE, null);
append(newContents, newDisplayContents, "NOTE", note);
Formatter fieldFormatter = new VCardFieldFormatter();
appendUpToUnique(newContents, newDisplayContents, "N", names, 1, null, fieldFormatter, TERMINATOR);
append(newContents, newDisplayContents, "ORG", organization, fieldFormatter, TERMINATOR);
appendUpToUnique(newContents, newDisplayContents, "ADR", addresses, 1, null, fieldFormatter, TERMINATOR);
List<Map<String,Set<String>>> phoneMetadata = buildPhoneMetadata(phones, phoneTypes);
appendUpToUnique(newContents, newDisplayContents, "TEL", phones, Integer.MAX_VALUE,
new VCardTelDisplayFormatter(phoneMetadata),
new VCardFieldFormatter(phoneMetadata), TERMINATOR);
appendUpToUnique(newContents, newDisplayContents, "EMAIL", emails, Integer.MAX_VALUE, null,
fieldFormatter, TERMINATOR);
appendUpToUnique(newContents, newDisplayContents, "URL", urls, Integer.MAX_VALUE, null,
fieldFormatter, TERMINATOR);
append(newContents, newDisplayContents, "NOTE", note, fieldFormatter, TERMINATOR);
newContents.append("END:VCARD").append(TERMINATOR);
return new String[] { newContents.toString(), newDisplayContents.toString() };
}
private static void append(StringBuilder newContents,
StringBuilder newDisplayContents,
String prefix,
String value) {
doAppend(newContents, newDisplayContents, prefix, value, VCARD_FIELD_FORMATTER, TERMINATOR);
static List<Map<String,Set<String>>> buildPhoneMetadata(Collection<String> phones, List<String> phoneTypes) {
if (phoneTypes == null || phoneTypes.isEmpty()) {
return null;
}
List<Map<String,Set<String>>> metadataForIndex = new ArrayList<>();
for (int i = 0; i < phones.size(); i++) {
if (phoneTypes.size() <= i) {
metadataForIndex.add(null);
} else {
Map<String,Set<String>> metadata = new HashMap<>();
metadataForIndex.add(metadata);
Set<String> typeTokens = new HashSet<>();
metadata.put("TYPE", typeTokens);
String typeString = phoneTypes.get(i);
Integer androidType = maybeIntValue(typeString);
if (androidType == null) {
typeTokens.add(typeString);
} else {
String purpose = vCardPurposeLabelForAndroidType(androidType);
String context = vCardContextLabelForAndroidType(androidType);
if (purpose != null) {
typeTokens.add(purpose);
}
if (context != null) {
typeTokens.add(context);
}
}
}
}
return metadataForIndex;
}
private static void appendUpToUnique(StringBuilder newContents,
StringBuilder newDisplayContents,
String prefix,
Iterable<String> values,
int max,
Formatter formatter) {
doAppendUpToUnique(newContents,
newDisplayContents,
prefix,
values,
max,
formatter,
VCARD_FIELD_FORMATTER,
TERMINATOR);
private static Integer maybeIntValue(String value) {
try {
return Integer.valueOf(value);
} catch (NumberFormatException nfe) {
return null;
}
}
private static String vCardPurposeLabelForAndroidType(int androidType) {
switch (androidType) {
case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME:
case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK:
case ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX:
return "fax";
case ContactsContract.CommonDataKinds.Phone.TYPE_PAGER:
case ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER:
return "pager";
case ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD:
return "textphone";
case ContactsContract.CommonDataKinds.Phone.TYPE_MMS:
return "text";
default:
return null;
}
}
private static String vCardContextLabelForAndroidType(int androidType) {
switch (androidType) {
case ContactsContract.CommonDataKinds.Phone.TYPE_HOME:
case ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE:
case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME:
case ContactsContract.CommonDataKinds.Phone.TYPE_PAGER:
return "home";
case ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN:
case ContactsContract.CommonDataKinds.Phone.TYPE_WORK:
case ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE:
case ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK:
case ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER:
return "work";
default:
return null;
}
}
}

View file

@ -0,0 +1,79 @@
/*
* Copyright (C) 2014 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.encode;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
/**
* @author Sean Owen
*/
final class VCardFieldFormatter implements Formatter {
private static final Pattern RESERVED_VCARD_CHARS = Pattern.compile("([\\\\,;])");
private static final Pattern NEWLINE = Pattern.compile("\\n");
private final List<Map<String,Set<String>>> metadataForIndex;
VCardFieldFormatter() {
this(null);
}
VCardFieldFormatter(List<Map<String,Set<String>>> metadataForIndex) {
this.metadataForIndex = metadataForIndex;
}
@Override
public CharSequence format(CharSequence value, int index) {
value = RESERVED_VCARD_CHARS.matcher(value).replaceAll("\\\\$1");
value = NEWLINE.matcher(value).replaceAll("");
Map<String,Set<String>> metadata =
metadataForIndex == null || metadataForIndex.size() <= index ? null : metadataForIndex.get(index);
value = formatMetadata(value, metadata);
return value;
}
private static CharSequence formatMetadata(CharSequence value, Map<String,Set<String>> metadata) {
StringBuilder withMetadata = new StringBuilder();
if (metadata != null) {
for (Map.Entry<String,Set<String>> metadatum : metadata.entrySet()) {
Set<String> values = metadatum.getValue();
if (values == null || values.isEmpty()) {
continue;
}
withMetadata.append(';').append(metadatum.getKey()).append('=');
if (values.size() > 1) {
withMetadata.append('"');
}
Iterator<String> valuesIt = values.iterator();
withMetadata.append(valuesIt.next());
while (valuesIt.hasNext()) {
withMetadata.append(',').append(valuesIt.next());
}
if (values.size() > 1) {
withMetadata.append('"');
}
}
}
withMetadata.append(':').append(value);
return withMetadata;
}
}

View file

@ -0,0 +1,73 @@
/*
* Copyright (C) 2014 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.encode;
import android.telephony.PhoneNumberUtils;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* @author Sean Owen
*/
final class VCardTelDisplayFormatter implements Formatter {
private final List<Map<String,Set<String>>> metadataForIndex;
VCardTelDisplayFormatter() {
this(null);
}
VCardTelDisplayFormatter(List<Map<String,Set<String>>> metadataForIndex) {
this.metadataForIndex = metadataForIndex;
}
@Override
public CharSequence format(CharSequence value, int index) {
value = PhoneNumberUtils.formatNumber(value.toString());
Map<String,Set<String>> metadata =
metadataForIndex == null || metadataForIndex.size() <= index ? null : metadataForIndex.get(index);
value = formatMetadata(value, metadata);
return value;
}
private static CharSequence formatMetadata(CharSequence value, Map<String,Set<String>> metadata) {
if (metadata == null || metadata.isEmpty()) {
return value;
}
StringBuilder withMetadata = new StringBuilder();
for (Map.Entry<String,Set<String>> metadatum : metadata.entrySet()) {
Set<String> values = metadatum.getValue();
if (values == null || values.isEmpty()) {
continue;
}
Iterator<String> valuesIt = values.iterator();
withMetadata.append(valuesIt.next());
while (valuesIt.hasNext()) {
withMetadata.append(',').append(valuesIt.next());
}
}
if (withMetadata.length() > 0) {
withMetadata.append(' ');
}
withMetadata.append(value);
return withMetadata;
}
}

View file

@ -220,11 +220,14 @@ public final class ShareActivity extends Activity {
try {
int foundPhone = 0;
int phonesNumberColumn = phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
int phoneTypeColumn = phonesCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE);
while (phonesCursor.moveToNext() && foundPhone < Contents.PHONE_KEYS.length) {
String number = phonesCursor.getString(phonesNumberColumn);
if (number != null && !number.isEmpty()) {
bundle.putString(Contents.PHONE_KEYS[foundPhone], massageContactData(number));
}
int type = phonesCursor.getInt(phoneTypeColumn);
bundle.putInt(Contents.PHONE_TYPE_KEYS[foundPhone], type);
foundPhone++;
}
} finally {