Issue 918 Add MECARD/vCard switch after factoring out and tidying some of the contact encoding logic

git-svn-id: https://zxing.googlecode.com/svn/trunk@2097 59b500cc-1b3d-0410-9834-0bbf25fbcc57
This commit is contained in:
srowen 2011-12-23 13:59:15 +00:00
parent 41a450d25e
commit c77baf34a8
38 changed files with 505 additions and 226 deletions

View file

@ -56,6 +56,8 @@
<string name="history_send">إرسال السجل</string>
<string name="history_title">سجل</string>
<string name="menu_about">حول</string>
<string name="menu_encode_mecard">استخدام MECARD</string>
<string name="menu_encode_vcard">استخدام vCard</string>
<string name="menu_help">تعليمات</string>
<string name="menu_history">سجل</string>
<string name="menu_settings">إعدادات</string>
@ -128,7 +130,7 @@
<string name="sbc_name">البحث في Google Book</string>
<string name="share_name">مشاركة عبر الرمز الشريطي</string>
<string name="title_about">إصدار ماسح الرمز الشريطي</string>
<string name="wifi_changing_network">طلب الاتصال بالشبكة\u2026</string>
<string name="wifi_changing_network">طلب الاتصال بالشبكة\u2026</string>
<string name="wifi_ssid_label">اسم الشبكة</string>
<string name="wifi_type_label">نوع</string>
<string name="zxing_url">http://code.google.com/p/zxing</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Изпрати историята</string>
<string name="history_title">История</string>
<string name="menu_about">Относно</string>
<string name="menu_encode_mecard">Използвайте MECARD</string>
<string name="menu_encode_vcard">Използвайте vCard</string>
<string name="menu_help">Помощ</string>
<string name="menu_history">История</string>
<string name="menu_settings">Настройки</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Envia l\'historial</string>
<string name="history_title">Historial</string>
<string name="menu_about">Quant a</string>
<string name="menu_encode_mecard">Utilitza MECARD</string>
<string name="menu_encode_vcard">Utilitza vCard</string>
<string name="menu_help">Ajuda</string>
<string name="menu_history">Historial</string>
<string name="menu_settings">Configuració</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Poslat historii</string>
<string name="history_title">Historie</string>
<string name="menu_about">O</string>
<string name="menu_encode_mecard">Použití MECARD</string>
<string name="menu_encode_vcard">Použití vCard</string>
<string name="menu_help">Pomoc!</string>
<string name="menu_history">Historie</string>
<string name="menu_settings">Nastavení</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Send historik</string>
<string name="history_title">Historik</string>
<string name="menu_about">Om</string>
<string name="menu_encode_mecard">Brug MECARD</string>
<string name="menu_encode_vcard">Brug vCard</string>
<string name="menu_help">Hjælp</string>
<string name="menu_history">Historik</string>
<string name="menu_settings">Indstillinger</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Historie senden</string>
<string name="history_title">Historie</string>
<string name="menu_about">Info</string>
<string name="menu_encode_mecard">Verwenden Sie MECARD</string>
<string name="menu_encode_vcard">Verwenden Sie vCard</string>
<string name="menu_help">Hilfe</string>
<string name="menu_history">Historie</string>
<string name="menu_settings">Einstellungen</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Αποστολή ιστορικού</string>
<string name="history_title">Ιστορικό</string>
<string name="menu_about">Σχετικά</string>
<string name="menu_encode_mecard">Χρησιμοποιήστε MECARD</string>
<string name="menu_encode_vcard">Χρησιμοποιήστε vCard</string>
<string name="menu_help">Βοήθεια</string>
<string name="menu_history">Ιστορικό</string>
<string name="menu_settings">Ρυθμίσεις</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Enviar historial</string>
<string name="history_title">Historial</string>
<string name="menu_about">Acerca de</string>
<string name="menu_encode_mecard">Usar MECARD</string>
<string name="menu_encode_vcard">Usar vCard</string>
<string name="menu_help">Ayuda</string>
<string name="menu_history">Historial</string>
<string name="menu_settings">Configuración</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Lähetä historia</string>
<string name="history_title">Historia</string>
<string name="menu_about">Tietoja</string>
<string name="menu_encode_mecard">Käytä MECARD</string>
<string name="menu_encode_vcard">Käytä vCard</string>
<string name="menu_help">Apua</string>
<string name="menu_history">Historia</string>
<string name="menu_settings">Asetukset</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Envoyer historique</string>
<string name="history_title">Historique</string>
<string name="menu_about">A propos</string>
<string name="menu_encode_mecard">Utilisez MECARD</string>
<string name="menu_encode_vcard">Utilisez vCard</string>
<string name="menu_help">Aide</string>
<string name="menu_history">Historique</string>
<string name="menu_settings">Paramètres</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">שלח היסטוריה</string>
<string name="history_title">היסטוריה</string>
<string name="menu_about">אודות</string>
<string name="menu_encode_mecard">השתמש MECARD</string>
<string name="menu_encode_vcard">השתמש vCard</string>
<string name="menu_help">עזרה</string>
<string name="menu_history">היסטוריה</string>
<string name="menu_settings">הגדרות</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">इतिहास भेजें</string>
<string name="history_title">इतिहास</string>
<string name="menu_about">के बारे में</string>
<string name="menu_encode_mecard">MECARD का प्रयोग करें</string>
<string name="menu_encode_vcard">vCard का प्रयोग करें</string>
<string name="menu_help">मदद</string>
<string name="menu_history">इतिहास</string>
<string name="menu_settings">सेटिंग्स</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Előzmények küldése</string>
<string name="history_title">Előzmények</string>
<string name="menu_about">Rólunk</string>
<string name="menu_encode_mecard">Használja MECARD</string>
<string name="menu_encode_vcard">Használja vCard</string>
<string name="menu_help">Súgó</string>
<string name="menu_history">Előzmények</string>
<string name="menu_settings">Beállítások</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Kirim riwayat</string>
<string name="history_title">Riwayat</string>
<string name="menu_about">Tentang</string>
<string name="menu_encode_mecard">Gunakan MECARD</string>
<string name="menu_encode_vcard">Gunakan vCard</string>
<string name="menu_help">Bantuan</string>
<string name="menu_history">Riwayat</string>
<string name="menu_settings">Pengaturan</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Invia cronologia</string>
<string name="history_title">Cronologia</string>
<string name="menu_about">Info</string>
<string name="menu_encode_mecard">Usa MECARD</string>
<string name="menu_encode_vcard">Usa vCard</string>
<string name="menu_help">Aiuto</string>
<string name="menu_history">Cronologia</string>
<string name="menu_settings">Impostazioni</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">שלח היסטוריה</string>
<string name="history_title">היסטוריה</string>
<string name="menu_about">אודות</string>
<string name="menu_encode_mecard">השתמש MECARD</string>
<string name="menu_encode_vcard">השתמש vCard</string>
<string name="menu_help">עזרה</string>
<string name="menu_history">היסטוריה</string>
<string name="menu_settings">הגדרות</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">履歴を送信</string>
<string name="history_title">履歴</string>
<string name="menu_about">詳細</string>
<string name="menu_encode_mecard">MECARDを使用してください</string>
<string name="menu_encode_vcard">vCardを使用してください</string>
<string name="menu_help">ヘルプ</string>
<string name="menu_history">履歴</string>
<string name="menu_settings">設定</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">기록 보내기</string>
<string name="history_title">기록</string>
<string name="menu_about">정보</string>
<string name="menu_encode_mecard">MECARD를 사용하여</string>
<string name="menu_encode_vcard">vCard를 사용하여</string>
<string name="menu_help">도움말</string>
<string name="menu_history">기록</string>
<string name="menu_settings">설정</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Verzend geschiedenis</string>
<string name="history_title">Geschiedenis</string>
<string name="menu_about">Over</string>
<string name="menu_encode_mecard">Gebruik MECARD</string>
<string name="menu_encode_vcard">Gebruik vCard</string>
<string name="menu_help">Help</string>
<string name="menu_history">Geschiedenis</string>
<string name="menu_settings">Instellingen</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Wyślij historię</string>
<string name="history_title">Historia</string>
<string name="menu_about">O aplikacji</string>
<string name="menu_encode_mecard">Użyj MECARD</string>
<string name="menu_encode_vcard">Użyj vCard</string>
<string name="menu_help">Pomoc</string>
<string name="menu_history">Historia</string>
<string name="menu_settings">Ustawienia</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Enviar histórico</string>
<string name="history_title">Histórico</string>
<string name="menu_about">Sobre</string>
<string name="menu_encode_mecard">Usar MECARD</string>
<string name="menu_encode_vcard">Usar vCard</string>
<string name="menu_help">Ajuda</string>
<string name="menu_history">Histórico</string>
<string name="menu_settings">Definições</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Отправить историю</string>
<string name="history_title">История</string>
<string name="menu_about">О программе</string>
<string name="menu_encode_mecard">Используйте MECARD</string>
<string name="menu_encode_vcard">Используйте vCard</string>
<string name="menu_help">Помощь</string>
<string name="menu_history">История</string>
<string name="menu_settings">Настройки</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Poslať históriu</string>
<string name="history_title">História</string>
<string name="menu_about">O programe</string>
<string name="menu_encode_mecard">Použitie MECARD</string>
<string name="menu_encode_vcard">Použitie vCard</string>
<string name="menu_help">Nápoveda</string>
<string name="menu_history">História</string>
<string name="menu_settings">Nastavenia</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Pošlji zgodovino</string>
<string name="history_title">Zgodovina</string>
<string name="menu_about">O programu</string>
<string name="menu_encode_mecard">Uporaba MECARD</string>
<string name="menu_encode_vcard">Uporaba vCard</string>
<string name="menu_help">Pomoč</string>
<string name="menu_history">Zgodovina</string>
<string name="menu_settings">Nastavitve</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Skicka historik</string>
<string name="history_title">Historik</string>
<string name="menu_about">Om</string>
<string name="menu_encode_mecard">Använd MECARD</string>
<string name="menu_encode_vcard">Använd vCard</string>
<string name="menu_help">Hjälp</string>
<string name="menu_history">Historik</string>
<string name="menu_settings">Inställningar</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Geçmişi gönder</string>
<string name="history_title">Geçmiş</string>
<string name="menu_about">Hakkında</string>
<string name="menu_encode_mecard">MECARD kullanın</string>
<string name="menu_encode_vcard">VCard kullanın</string>
<string name="menu_help">Yardım</string>
<string name="menu_history">Geçmiş</string>
<string name="menu_settings">Ayarlar</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">发送历史</string>
<string name="history_title">历史</string>
<string name="menu_about">关于</string>
<string name="menu_encode_mecard">使用MECARD</string>
<string name="menu_encode_vcard">使用vCard</string>
<string name="menu_help">帮助</string>
<string name="menu_history">历史</string>
<string name="menu_settings">设置</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">發送紀錄</string>
<string name="history_title">紀錄</string>
<string name="menu_about">關於</string>
<string name="menu_encode_mecard">使用MECARD</string>
<string name="menu_encode_vcard">使用vCard</string>
<string name="menu_help">說明</string>
<string name="menu_history">紀錄</string>
<string name="menu_settings">設定</string>

View file

@ -56,6 +56,8 @@
<string name="history_send">Send history</string>
<string name="history_title">History</string>
<string name="menu_about">About</string>
<string name="menu_encode_mecard">Use MECARD</string>
<string name="menu_encode_vcard">Use vCard</string>
<string name="menu_help">Help</string>
<string name="menu_history">History</string>
<string name="menu_settings">Settings</string>

View file

@ -314,15 +314,15 @@ public final class CaptureActivity extends Activity implements SurfaceHolder.Cal
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, SHARE_ID, 0, R.string.menu_share)
menu.add(Menu.NONE, SHARE_ID, Menu.NONE, R.string.menu_share)
.setIcon(android.R.drawable.ic_menu_share);
menu.add(0, HISTORY_ID, 0, R.string.menu_history)
menu.add(Menu.NONE, HISTORY_ID, Menu.NONE, R.string.menu_history)
.setIcon(android.R.drawable.ic_menu_recent_history);
menu.add(0, SETTINGS_ID, 0, R.string.menu_settings)
menu.add(Menu.NONE, SETTINGS_ID, Menu.NONE, R.string.menu_settings)
.setIcon(android.R.drawable.ic_menu_preferences);
menu.add(0, HELP_ID, 0, R.string.menu_help)
menu.add(Menu.NONE, HELP_ID, Menu.NONE, R.string.menu_help)
.setIcon(android.R.drawable.ic_menu_help);
menu.add(0, ABOUT_ID, 0, R.string.menu_about)
menu.add(Menu.NONE, ABOUT_ID, Menu.NONE, R.string.menu_about)
.setIcon(android.R.drawable.ic_menu_info_details);
return true;
}

View file

@ -0,0 +1,95 @@
/*
* Copyright (C) 2011 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.Collection;
import java.util.HashSet;
/**
* Implementations encode according to some scheme for encoding contact information, like VCard or
* MECARD.
*
* @author Sean Owen
*/
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,
String organization,
Iterable<String> addresses,
Iterable<String> phones,
Iterable<String> emails,
String url,
String note);
/**
* @return null if s is null or empty, or result of s.trim() otherwise
*/
static String trim(String s) {
if (s == null) {
return null;
}
String result = s.trim();
return result.length() == 0 ? null : result;
}
static void doAppend(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);
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) {
if (values == null) {
return;
}
int count = 0;
Collection<String> uniques = new HashSet<String>(2);
for (String value : values) {
String trimmed = trim(value);
if (trimmed != null) {
if (!uniques.contains(trimmed)) {
newContents.append(prefix).append(':').append(fieldFormatter.format(trimmed)).append(terminator);
String display = formatter == null ? trimmed : formatter.format(trimmed);
newDisplayContents.append(display).append('\n');
if (++count == max) {
break;
}
uniques.add(trimmed);
}
}
}
}
}

View file

@ -40,6 +40,7 @@ import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.regex.Pattern;
/**
* This class encodes data from an Intent into a QR code, and then displays it full screen so that
@ -51,7 +52,11 @@ public final class EncodeActivity extends Activity {
private static final String TAG = EncodeActivity.class.getSimpleName();
private static final int SHARE_MENU = Menu.FIRST;
private static final int ENCODE_FORMAT_MENU = Menu.FIRST + 1;
private static final int MAX_BARCODE_FILENAME_LENGTH = 24;
private static final Pattern NOT_ALPHANUMERIC = Pattern.compile("[^A-Za-z0-9]");
private static final String USE_VCARD_KEY = "USE_VCARD";
private QRCodeEncoder qrCodeEncoder;
@ -73,22 +78,41 @@ public final class EncodeActivity extends Activity {
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
menu.add(0, Menu.FIRST, 0, R.string.menu_share).setIcon(android.R.drawable.ic_menu_share);
menu.add(Menu.NONE, SHARE_MENU, Menu.NONE, R.string.menu_share).setIcon(android.R.drawable.ic_menu_share);
int encodeNameResource = qrCodeEncoder.isUseVCard() ? R.string.menu_encode_mecard : R.string.menu_encode_vcard;
menu.add(Menu.NONE, ENCODE_FORMAT_MENU, Menu.NONE, encodeNameResource)
.setIcon(android.R.drawable.ic_menu_sort_alphabetically);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case SHARE_MENU:
share();
return true;
case ENCODE_FORMAT_MENU:
Intent intent = getIntent();
intent.putExtra(USE_VCARD_KEY, !qrCodeEncoder.isUseVCard());
startActivity(getIntent());
finish();
return true;
default:
return false;
}
}
private void share() {
QRCodeEncoder encoder = qrCodeEncoder;
if (encoder == null) { // Odd
Log.w(TAG, "No existing barcode to send?");
return true;
return;
}
String contents = encoder.getContents();
if (contents == null) {
Log.w(TAG, "No existing barcode to send?");
return true;
return;
}
Bitmap bitmap;
@ -96,7 +120,7 @@ public final class EncodeActivity extends Activity {
bitmap = encoder.encodeAsBitmap();
} catch (WriterException we) {
Log.w(TAG, we);
return true;
return;
}
File bsRoot = new File(Environment.getExternalStorageDirectory(), "BarcodeScanner");
@ -104,7 +128,7 @@ public final class EncodeActivity extends Activity {
if (!barcodesRoot.exists() && !barcodesRoot.mkdirs()) {
Log.w(TAG, "Couldn't make dir " + barcodesRoot);
showErrorMessage(R.string.msg_unmount_usb);
return true;
return;
}
File barcodeFile = new File(barcodesRoot, makeBarcodeFileName(contents) + ".png");
barcodeFile.delete();
@ -115,7 +139,7 @@ public final class EncodeActivity extends Activity {
} catch (FileNotFoundException fnfe) {
Log.w(TAG, "Couldn't access file " + barcodeFile + " due to " + fnfe);
showErrorMessage(R.string.msg_unmount_usb);
return true;
return;
} finally {
if (fos != null) {
try {
@ -128,24 +152,17 @@ public final class EncodeActivity extends Activity {
Intent intent = new Intent(Intent.ACTION_SEND, Uri.parse("mailto:"));
intent.putExtra(Intent.EXTRA_SUBJECT, getString(R.string.app_name) + " - " + encoder.getTitle());
intent.putExtra(Intent.EXTRA_TEXT, encoder.getContents());
intent.putExtra(Intent.EXTRA_TEXT, contents);
intent.putExtra(Intent.EXTRA_STREAM, Uri.parse("file://" + barcodeFile.getAbsolutePath()));
intent.setType("image/png");
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
startActivity(Intent.createChooser(intent, null));
return true;
}
private static CharSequence makeBarcodeFileName(CharSequence contents) {
int fileNameLength = Math.min(MAX_BARCODE_FILENAME_LENGTH, contents.length());
StringBuilder fileName = new StringBuilder(fileNameLength);
for (int i = 0; i < fileNameLength; i++) {
char c = contents.charAt(i);
if ((c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || (c >= '0' && c <= '9')) {
fileName.append(c);
} else {
fileName.append('_');
}
String fileName = NOT_ALPHANUMERIC.matcher(contents).replaceAll("_");
if (fileName.length() > MAX_BARCODE_FILENAME_LENGTH) {
fileName = fileName.substring(0, MAX_BARCODE_FILENAME_LENGTH);
}
return fileName;
}
@ -166,7 +183,8 @@ public final class EncodeActivity extends Activity {
return;
}
try {
qrCodeEncoder = new QRCodeEncoder(this, intent, smallerDimension);
boolean useVCard = intent.getBooleanExtra(USE_VCARD_KEY, false);
qrCodeEncoder = new QRCodeEncoder(this, intent, smallerDimension, useVCard);
Bitmap bitmap = qrCodeEncoder.encodeAsBitmap();
ImageView view = (ImageView) findViewById(R.id.image_view);
view.setImageBitmap(bitmap);
@ -180,11 +198,7 @@ public final class EncodeActivity extends Activity {
setTitle(getString(R.string.app_name));
}
} catch (WriterException e) {
Log.e(TAG, "Could not encode barcode", e);
showErrorMessage(R.string.msg_encode_contents_failed);
qrCodeEncoder = null;
} catch (IllegalArgumentException e) {
Log.e(TAG, "Could not encode barcode", e);
Log.w(TAG, "Could not encode barcode", e);
showErrorMessage(R.string.msg_encode_contents_failed);
qrCodeEncoder = null;
}

View file

@ -0,0 +1,28 @@
/*
* Copyright (C) 2011 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;
/**
* Encapsulates some simple formatting logic, to aid refactoring in {@link ContactEncoder}.
*
* @author Sean Owen
*/
interface Formatter {
String format(String source);
}

View file

@ -0,0 +1,96 @@
/*
* Copyright (C) 2011 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.regex.Pattern;
/**
* Encodes contact information according to the MECARD format.
*
* @author Sean Owen
*/
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 = ';';
@Override
public String[] encode(Iterable<String> names,
String organization,
Iterable<String> addresses,
Iterable<String> phones,
Iterable<String> emails,
String url,
String note) {
StringBuilder newContents = new StringBuilder(100);
StringBuilder newDisplayContents = new StringBuilder(100);
newContents.append("MECARD:");
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) {
return PhoneNumberUtils.formatNumber(source);
}
});
appendUpToUnique(newContents, newDisplayContents, "EMAIL", emails, Integer.MAX_VALUE, null);
append(newContents, newDisplayContents, "URL", url);
append(newContents, newDisplayContents, "NOTE", note);
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 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);
}
}

View file

@ -40,10 +40,11 @@ import android.util.Log;
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.HashSet;
import java.util.Map;
/**
@ -65,35 +66,36 @@ final class QRCodeEncoder {
private String title;
private BarcodeFormat format;
private final int dimension;
private final boolean useVCard;
QRCodeEncoder(Activity activity, Intent intent, int dimension) {
QRCodeEncoder(Activity activity, Intent intent, int dimension, boolean useVCard) throws WriterException {
this.activity = activity;
this.dimension = dimension;
this.useVCard = useVCard;
String action = intent.getAction();
if (action.equals(Intents.Encode.ACTION)) {
if (!encodeContentsFromZXingIntent(intent)) {
throw new IllegalArgumentException("No valid data to encode.");
}
encodeContentsFromZXingIntent(intent);
} else if (action.equals(Intent.ACTION_SEND)) {
if (!encodeContentsFromShareIntent(intent)) {
throw new IllegalArgumentException("No valid data to encode.");
}
encodeContentsFromShareIntent(intent);
}
this.dimension = dimension;
}
public String getContents() {
String getContents() {
return contents;
}
public String getDisplayContents() {
String getDisplayContents() {
return displayContents;
}
public String getTitle() {
String getTitle() {
return title;
}
boolean isUseVCard() {
return useVCard;
}
// 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 encodeContentsFromZXingIntent(Intent intent) {
@ -126,26 +128,23 @@ final class QRCodeEncoder {
}
// Handles send intents from multitude of Android applications
private boolean encodeContentsFromShareIntent(Intent intent) {
private void encodeContentsFromShareIntent(Intent intent) throws WriterException {
// Check if this is a plain text encoding, or contact
if (intent.hasExtra(Intent.EXTRA_TEXT)) {
return encodeContentsFromShareIntentPlainText(intent);
encodeContentsFromShareIntentPlainText(intent);
} else {
// Attempt default sharing.
encodeContentsFromShareIntentDefault(intent);
}
// Attempt default sharing.
return encodeContentsFromShareIntentDefault(intent);
}
private boolean encodeContentsFromShareIntentPlainText(Intent intent) {
private void encodeContentsFromShareIntentPlainText(Intent intent) throws WriterException {
// Notice: Google Maps shares both URL and details in one text, bummer!
String theContents = intent.getStringExtra(Intent.EXTRA_TEXT);
String theContents = ContactEncoder.trim(intent.getStringExtra(Intent.EXTRA_TEXT));
// We only support non-empty and non-blank texts.
// Trim text to avoid URL breaking.
if (theContents == null) {
return false;
}
theContents = theContents.trim();
if (theContents.length() == 0) {
return false;
if (theContents == null || theContents.length() == 0) {
throw new WriterException("Empty EXTRA_TEXT");
}
contents = theContents;
// We only do QR code.
@ -158,49 +157,47 @@ final class QRCodeEncoder {
displayContents = contents;
}
title = activity.getString(R.string.contents_text);
return true;
}
// Handles send intents from the Contacts app, retrieving a contact as a VCARD.
// Note: Does not work on HTC devices due to broken custom Contacts application.
private boolean encodeContentsFromShareIntentDefault(Intent intent) {
private void encodeContentsFromShareIntentDefault(Intent intent) throws WriterException {
format = BarcodeFormat.QR_CODE;
Bundle bundle = intent.getExtras();
if (bundle == null) {
throw new WriterException("No extras");
}
Uri uri = (Uri) bundle.getParcelable(Intent.EXTRA_STREAM);
if (uri == null) {
throw new WriterException("No EXTRA_STREAM");
}
byte[] vcard;
String vcardString;
try {
Uri uri = (Uri)intent.getExtras().getParcelable(Intent.EXTRA_STREAM);
InputStream stream = activity.getContentResolver().openInputStream(uri);
int length = stream.available();
if (length <= 0) {
Log.w(TAG, "Content stream is empty");
return false;
throw new WriterException("Content stream is empty");
}
byte[] vcard = new byte[length];
vcard = new byte[length];
int bytesRead = stream.read(vcard, 0, length);
if (bytesRead < length) {
Log.w(TAG, "Unable to fully read available bytes from content stream");
return false;
throw new WriterException("Unable to fully read available bytes from content stream");
}
String vcardString = new String(vcard, 0, bytesRead, "UTF-8");
Log.d(TAG, "Encoding share intent content:");
Log.d(TAG, vcardString);
Result result = new Result(vcardString, vcard, null, BarcodeFormat.QR_CODE);
ParsedResult parsedResult = ResultParser.parseResult(result);
if (!(parsedResult instanceof AddressBookParsedResult)) {
Log.d(TAG, "Result was not an address");
return false;
}
if (!encodeQRCodeContents((AddressBookParsedResult) parsedResult)) {
Log.d(TAG, "Unable to encode contents");
return false;
}
} catch (IOException e) {
Log.w(TAG, e);
return false;
} catch (NullPointerException e) {
Log.w(TAG, e);
// In case the uri was not found in the Intent.
return false;
vcardString = new String(vcard, 0, bytesRead, "UTF-8");
} catch (IOException ioe) {
throw new WriterException(ioe);
}
Log.d(TAG, "Encoding share intent content:");
Log.d(TAG, vcardString);
Result result = new Result(vcardString, vcard, null, BarcodeFormat.QR_CODE);
ParsedResult parsedResult = ResultParser.parseResult(result);
if (!(parsedResult instanceof AddressBookParsedResult)) {
throw new WriterException("Result was not an address");
}
encodeQRCodeContents((AddressBookParsedResult) parsedResult);
if (contents == null || contents.length() == 0) {
throw new WriterException("No content to encode");
}
return contents != null && contents.length() > 0;
}
private void encodeQRCodeContents(Intent intent, String type) {
@ -212,21 +209,21 @@ final class QRCodeEncoder {
title = activity.getString(R.string.contents_text);
}
} else if (type.equals(Contents.Type.EMAIL)) {
String data = trim(intent.getStringExtra(Intents.Encode.DATA));
String data = ContactEncoder.trim(intent.getStringExtra(Intents.Encode.DATA));
if (data != null) {
contents = "mailto:" + data;
displayContents = data;
title = activity.getString(R.string.contents_email);
}
} else if (type.equals(Contents.Type.PHONE)) {
String data = trim(intent.getStringExtra(Intents.Encode.DATA));
String data = ContactEncoder.trim(intent.getStringExtra(Intents.Encode.DATA));
if (data != null) {
contents = "tel:" + data;
displayContents = PhoneNumberUtils.formatNumber(data);
title = activity.getString(R.string.contents_phone);
}
} else if (type.equals(Contents.Type.SMS)) {
String data = trim(intent.getStringExtra(Intents.Encode.DATA));
String data = ContactEncoder.trim(intent.getStringExtra(Intents.Encode.DATA));
if (data != null) {
contents = "sms:" + data;
displayContents = PhoneNumberUtils.formatNumber(data);
@ -237,69 +234,33 @@ final class QRCodeEncoder {
Bundle bundle = intent.getBundleExtra(Intents.Encode.DATA);
if (bundle != null) {
StringBuilder newContents = new StringBuilder(100);
StringBuilder newDisplayContents = new StringBuilder(100);
newContents.append("MECARD:");
String name = trim(bundle.getString(ContactsContract.Intents.Insert.NAME));
if (name != null) {
newContents.append("N:").append(escapeMECARD(name)).append(';');
newDisplayContents.append(name);
}
String address = trim(bundle.getString(ContactsContract.Intents.Insert.POSTAL));
if (address != null) {
newContents.append("ADR:").append(escapeMECARD(address)).append(';');
newDisplayContents.append('\n').append(address);
}
Collection<String> uniquePhones = new HashSet<String>(Contents.PHONE_KEYS.length);
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<String>(Contents.PHONE_KEYS.length);
for (int x = 0; x < Contents.PHONE_KEYS.length; x++) {
String phone = trim(bundle.getString(Contents.PHONE_KEYS[x]));
if (phone != null) {
uniquePhones.add(phone);
}
phones.add(bundle.getString(Contents.PHONE_KEYS[x]));
}
for (String phone : uniquePhones) {
newContents.append("TEL:").append(escapeMECARD(phone)).append(';');
newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
}
Collection<String> uniqueEmails = new HashSet<String>(Contents.EMAIL_KEYS.length);
Collection<String> emails = new ArrayList<String>(Contents.EMAIL_KEYS.length);
for (int x = 0; x < Contents.EMAIL_KEYS.length; x++) {
String email = trim(bundle.getString(Contents.EMAIL_KEYS[x]));
if (email != null) {
uniqueEmails.add(email);
}
}
for (String email : uniqueEmails) {
newContents.append("EMAIL:").append(escapeMECARD(email)).append(';');
newDisplayContents.append('\n').append(email);
}
String url = trim(bundle.getString(Contents.URL_KEY));
if (url != null) {
// escapeMECARD(url) -> wrong escape e.g. http\://zxing.google.com
newContents.append("URL:").append(url).append(';');
newDisplayContents.append('\n').append(url);
}
String note = trim(bundle.getString(Contents.NOTE_KEY));
if (note != null) {
newContents.append("NOTE:").append(escapeMECARD(note)).append(';');
newDisplayContents.append('\n').append(note);
emails.add(bundle.getString(Contents.EMAIL_KEYS[x]));
}
String url = bundle.getString(Contents.URL_KEY);
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,
url,
note);
// Make sure we've encoded at least one field.
if (newDisplayContents.length() > 0) {
newContents.append(';');
contents = newContents.toString();
displayContents = newDisplayContents.toString();
if (encoded[1].length() > 0) {
contents = encoded[0];
displayContents = encoded[1];
title = activity.getString(R.string.contents_contact);
} else {
contents = null;
displayContents = null;
}
}
@ -319,68 +280,23 @@ final class QRCodeEncoder {
}
}
private boolean encodeQRCodeContents(AddressBookParsedResult contact) {
StringBuilder newContents = new StringBuilder(100);
StringBuilder newDisplayContents = new StringBuilder(100);
newContents.append("MECARD:");
String[] names = contact.getNames();
if (names != null && names.length > 0) {
String name = trim(names[0]);
if (name != null) {
newContents.append("N:").append(escapeMECARD(name)).append(';');
newDisplayContents.append(name);
}
}
for (String address : trimAndDeduplicate(contact.getAddresses())) {
newContents.append("ADR:").append(escapeMECARD(address)).append(';');
newDisplayContents.append('\n').append(address);
}
for (String phone : trimAndDeduplicate(contact.getPhoneNumbers())) {
newContents.append("TEL:").append(escapeMECARD(phone)).append(';');
newDisplayContents.append('\n').append(PhoneNumberUtils.formatNumber(phone));
}
for (String email : trimAndDeduplicate(contact.getEmails())) {
newContents.append("EMAIL:").append(escapeMECARD(email)).append(';');
newDisplayContents.append('\n').append(email);
}
String url = trim(contact.getURL());
if (url != null) {
newContents.append("URL:").append(escapeMECARD(url)).append(';');
newDisplayContents.append('\n').append(url);
}
private void encodeQRCodeContents(AddressBookParsedResult contact) {
ContactEncoder encoder = useVCard ? new VCardContactEncoder() : new MECARDContactEncoder();
String[] encoded = encoder.encode(Arrays.asList(contact.getNames()),
contact.getOrg(),
Arrays.asList(contact.getAddresses()),
Arrays.asList(contact.getPhoneNumbers()),
Arrays.asList(contact.getEmails()),
contact.getURL(),
null);
// Make sure we've encoded at least one field.
if (newDisplayContents.length() > 0) {
newContents.append(';');
contents = newContents.toString();
displayContents = newDisplayContents.toString();
if (encoded[1].length() > 0) {
contents = encoded[0];
displayContents = encoded[1];
title = activity.getString(R.string.contents_contact);
return true;
} else {
contents = null;
displayContents = null;
return false;
}
}
private static Iterable<String> trimAndDeduplicate(String[] values) {
if (values == null || values.length == 0) {
return Collections.emptySet();
}
Collection<String> uniqueValues = new HashSet<String>(values.length);
for (String value : values) {
uniqueValues.add(trim(value));
}
return uniqueValues;
}
Bitmap encodeAsBitmap() throws WriterException {
Map<EncodeHintType,Object> hints = null;
String encoding = guessAppropriateEncoding(contents);
@ -393,7 +309,6 @@ final class QRCodeEncoder {
int width = result.getWidth();
int height = result.getHeight();
int[] pixels = new int[width * height];
// All are 0, or black, by default
for (int y = 0; y < height; y++) {
int offset = y * width;
for (int x = 0; x < width; x++) {
@ -416,28 +331,4 @@ final class QRCodeEncoder {
return null;
}
private static String trim(String s) {
if (s == null) {
return null;
}
String result = s.trim();
return result.length() == 0 ? null : result;
}
private static String escapeMECARD(String input) {
if (input == null || (input.indexOf(':') < 0 && input.indexOf(';') < 0)) {
return input;
}
int length = input.length();
StringBuilder result = new StringBuilder(length);
for (int i = 0; i < length; i++) {
char c = input.charAt(i);
if (c == ':' || c == ';') {
result.append('\\');
}
result.append(c);
}
return result.toString();
}
}

View file

@ -0,0 +1,90 @@
/*
* Copyright (C) 2011 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.regex.Pattern;
/**
* Encodes contact information according to the vCard format.
*
* @author Sean Owen
*/
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,
String organization,
Iterable<String> addresses,
Iterable<String> phones,
Iterable<String> emails,
String url,
String note) {
StringBuilder newContents = new StringBuilder(100);
StringBuilder newDisplayContents = new StringBuilder(100);
newContents.append("BEGIN:VCARD").append(TERMINATOR);
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);
append(newContents, newDisplayContents, "URL", url);
append(newContents, newDisplayContents, "NOTE", note);
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);
}
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);
}
}

View file

@ -30,5 +30,9 @@ public final class WriterException extends Exception {
public WriterException(String message) {
super(message);
}
public WriterException(Throwable cause) {
super(cause);
}
}

View file

@ -100,10 +100,13 @@ public final class VCardResultParser extends ResultParser {
while (i < max) {
// At start or after newling, match prefix, followed by optional metadata
// At start or after newline, match prefix, followed by optional metadata
// (led by ;) ultimately ending in colon
Matcher matcher = Pattern.compile("(?:^|\n)" + prefix + "(?:;([^:]*))?:",
Pattern.CASE_INSENSITIVE).matcher(rawText);
if (i > 0) {
i--; // Find from i-1 not i since looking at the preceding character
}
if (!matcher.find(i)) {
break;
}