17949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton/*
27949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * Copyright (C) 2010 The Android Open Source Project
37949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton *
47949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * Licensed under the Apache License, Version 2.0 (the "License");
57949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * you may not use this file except in compliance with the License.
67949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * You may obtain a copy of the License at
77949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton *
87949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton *      http://www.apache.org/licenses/LICENSE-2.0
97949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton *
107949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * Unless required by applicable law or agreed to in writing, software
117949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * distributed under the License is distributed on an "AS IS" BASIS,
127949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
137949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * See the License for the specific language governing permissions and
147949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * limitations under the License.
157949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton */
167949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
177949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonpackage com.android.apps.tag.record;
187949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
197949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport com.android.apps.tag.R;
202ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevichimport com.google.common.annotations.VisibleForTesting;
217949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport com.google.common.base.Preconditions;
222ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevichimport com.google.common.primitives.Bytes;
237949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
247949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport android.app.Activity;
25a6855221fb6e5c6c19d31283a18ced44cccdf442Jason parksimport android.content.Context;
267949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport android.nfc.NdefRecord;
277949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport android.view.LayoutInflater;
287949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport android.view.View;
297949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport android.view.ViewGroup;
307949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport android.widget.TextView;
317949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
327949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport java.io.UnsupportedEncodingException;
332ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevichimport java.nio.charset.Charset;
347949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamiltonimport java.util.Arrays;
352ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevichimport java.util.Locale;
367949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
377949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton/**
387949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton * An NFC Text Record
397949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton */
4040d9a0cd0a37d184ee737b2d7138a39e4292ce3eJason parkspublic class TextRecord extends ParsedNdefRecord {
417949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
4258fbc09c19d87b6463bb68f74eefc43af1c13dbfJeff Hamilton    public static final String RECORD_TYPE = "TextRecord";
4358fbc09c19d87b6463bb68f74eefc43af1c13dbfJeff Hamilton
447949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    /** ISO/IANA language code */
457949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    private final String mLanguageCode;
467949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    private final String mText;
477949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
487949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    private TextRecord(String languageCode, String text) {
497949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        mLanguageCode = Preconditions.checkNotNull(languageCode);
507949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        mText = Preconditions.checkNotNull(text);
517949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    }
527949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
537949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    @Override
5440d9a0cd0a37d184ee737b2d7138a39e4292ce3eJason parks    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
557949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
567949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        text.setText(mText);
577949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        return text;
587949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    }
597949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
60a6855221fb6e5c6c19d31283a18ced44cccdf442Jason parks    @Override
61a6855221fb6e5c6c19d31283a18ced44cccdf442Jason parks    public String getSnippet(Context context, Locale locale) {
62a6855221fb6e5c6c19d31283a18ced44cccdf442Jason parks        return mText;
63a6855221fb6e5c6c19d31283a18ced44cccdf442Jason parks    }
64a6855221fb6e5c6c19d31283a18ced44cccdf442Jason parks
657949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    public String getText() {
667949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        return mText;
677949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    }
687949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
697949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    /**
707949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton     * Returns the ISO/IANA language code associated with this text element.
717949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton     *
727949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton     * TODO: this should return a {@link java.util.Locale}
737949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton     */
742ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich    @VisibleForTesting
757949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    public String getLanguageCode() {
767949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        return mLanguageCode;
777949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    }
787949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
797949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    // TODO: deal with text fields which span multiple NdefRecords
807949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    public static TextRecord parse(NdefRecord record) {
817949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
827949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT));
837949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        try {
847949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
857949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            byte[] payload = record.getPayload();
868722731539a5dad00bb5db54df17b6db11f751e7Nick Kralevich            Preconditions.checkArgument(payload.length > 0);
877949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
887949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            /*
897949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             * payload[0] contains the "Status Byte Encodings" field, per
907949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             * the NFC Forum "Text Record Type Definition" section 3.2.1.
917949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             *
927949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             * bit7 is the Text Encoding Field.
937949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             *
947949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             * if (Bit_7 == 0): The text is encoded in UTF-8
957949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             * if (Bit_7 == 1): The text is encoded in UTF16
967949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             *
977949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             * Bit_6 is reserved for future use and must be set to zero.
987949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             *
997949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             * Bits 5 to 0 are the length of the IANA language code.
1007949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton             */
1017949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
1027949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16";
1037949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            int languageCodeLength = payload[0] & 0077;
10413aac27873c59071b321b38475d57a849e328e6aNick Kralevich            Preconditions.checkArgument(payload.length - languageCodeLength - 1 >= 0);
1057949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
1067949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
1077949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            String text = new String(payload,
1087949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton                    languageCodeLength + 1,
1097949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton                    payload.length - languageCodeLength - 1,
1107949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton                    textEncoding);
1117949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            return new TextRecord(languageCode, text);
1127949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
1137949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        } catch (UnsupportedEncodingException e) {
1147949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            // should never happen unless we get a malformed tag.
1157949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            throw new IllegalArgumentException(e);
1167949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        }
1177949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    }
1187949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton
1197949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    public static boolean isText(NdefRecord record) {
1207949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        try {
1217949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            parse(record);
1227949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            return true;
1237949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        } catch (IllegalArgumentException e) {
1247949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton            return false;
1257949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton        }
1267949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton    }
1272ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich
1282ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich    @VisibleForTesting
1292ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich    public static NdefRecord newTextRecord(String text, Locale locale) {
1302ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich        return newTextRecord(text, locale, true);
1312ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich    }
1322ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich
1332ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich    public static NdefRecord newTextRecord(String text, Locale locale, boolean encodeInUtf8) {
1342ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich        Preconditions.checkNotNull(text);
1352ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich        Preconditions.checkNotNull(locale);
1362ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich
137f34e4d5ec99fd7883bbfcdc0f644d9d6e4df6c97Jeff Hamilton        byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
1382ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich
139f34e4d5ec99fd7883bbfcdc0f644d9d6e4df6c97Jeff Hamilton        Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
1402ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich        byte[] textBytes = text.getBytes(utfEncoding);
1412ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich
1422ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich        int utfBit = encodeInUtf8 ? 0 : (1 << 7);
1432ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich        char status = (char) (utfBit + langBytes.length);
1442ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich
1452ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich        byte[] data = Bytes.concat(
1462ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich           new byte[] { (byte) status },
1472ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich           langBytes,
1482ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich           textBytes
1492ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich        );
1502ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich
1512ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich        return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);
1522ef3e3a274141d0fec3e108b2ee8ba02be78c1c2Nick Kralevich    }
1537949534630e1b14cf73a6ad3c0606ec5be2c480cJeff Hamilton}
154