1/*
2 * Copyright (C) 2010 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.apps.tag.record;
18
19import com.android.apps.tag.R;
20import com.google.common.annotations.VisibleForTesting;
21import com.google.common.base.Preconditions;
22import com.google.common.primitives.Bytes;
23
24import android.app.Activity;
25import android.content.Context;
26import android.nfc.NdefRecord;
27import android.view.LayoutInflater;
28import android.view.View;
29import android.view.ViewGroup;
30import android.widget.TextView;
31
32import java.io.UnsupportedEncodingException;
33import java.nio.charset.Charset;
34import java.util.Arrays;
35import java.util.Locale;
36
37/**
38 * An NFC Text Record
39 */
40public class TextRecord extends ParsedNdefRecord {
41
42    public static final String RECORD_TYPE = "TextRecord";
43
44    /** ISO/IANA language code */
45    private final String mLanguageCode;
46    private final String mText;
47
48    private TextRecord(String languageCode, String text) {
49        mLanguageCode = Preconditions.checkNotNull(languageCode);
50        mText = Preconditions.checkNotNull(text);
51    }
52
53    @Override
54    public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) {
55        TextView text = (TextView) inflater.inflate(R.layout.tag_text, parent, false);
56        text.setText(mText);
57        return text;
58    }
59
60    @Override
61    public String getSnippet(Context context, Locale locale) {
62        return mText;
63    }
64
65    public String getText() {
66        return mText;
67    }
68
69    /**
70     * Returns the ISO/IANA language code associated with this text element.
71     *
72     * TODO: this should return a {@link java.util.Locale}
73     */
74    @VisibleForTesting
75    public String getLanguageCode() {
76        return mLanguageCode;
77    }
78
79    // TODO: deal with text fields which span multiple NdefRecords
80    public static TextRecord parse(NdefRecord record) {
81        Preconditions.checkArgument(record.getTnf() == NdefRecord.TNF_WELL_KNOWN);
82        Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_TEXT));
83        try {
84
85            byte[] payload = record.getPayload();
86            Preconditions.checkArgument(payload.length > 0);
87
88            /*
89             * payload[0] contains the "Status Byte Encodings" field, per
90             * the NFC Forum "Text Record Type Definition" section 3.2.1.
91             *
92             * bit7 is the Text Encoding Field.
93             *
94             * if (Bit_7 == 0): The text is encoded in UTF-8
95             * if (Bit_7 == 1): The text is encoded in UTF16
96             *
97             * Bit_6 is reserved for future use and must be set to zero.
98             *
99             * Bits 5 to 0 are the length of the IANA language code.
100             */
101
102            String textEncoding = ((payload[0] & 0200) == 0) ? "UTF-8" : "UTF-16";
103            int languageCodeLength = payload[0] & 0077;
104            Preconditions.checkArgument(payload.length - languageCodeLength - 1 >= 0);
105
106            String languageCode = new String(payload, 1, languageCodeLength, "US-ASCII");
107            String text = new String(payload,
108                    languageCodeLength + 1,
109                    payload.length - languageCodeLength - 1,
110                    textEncoding);
111            return new TextRecord(languageCode, text);
112
113        } catch (UnsupportedEncodingException e) {
114            // should never happen unless we get a malformed tag.
115            throw new IllegalArgumentException(e);
116        }
117    }
118
119    public static boolean isText(NdefRecord record) {
120        try {
121            parse(record);
122            return true;
123        } catch (IllegalArgumentException e) {
124            return false;
125        }
126    }
127
128    @VisibleForTesting
129    public static NdefRecord newTextRecord(String text, Locale locale) {
130        return newTextRecord(text, locale, true);
131    }
132
133    public static NdefRecord newTextRecord(String text, Locale locale, boolean encodeInUtf8) {
134        Preconditions.checkNotNull(text);
135        Preconditions.checkNotNull(locale);
136
137        byte[] langBytes = locale.getLanguage().getBytes(Charset.forName("US-ASCII"));
138
139        Charset utfEncoding = encodeInUtf8 ? Charset.forName("UTF-8") : Charset.forName("UTF-16");
140        byte[] textBytes = text.getBytes(utfEncoding);
141
142        int utfBit = encodeInUtf8 ? 0 : (1 << 7);
143        char status = (char) (utfBit + langBytes.length);
144
145        byte[] data = Bytes.concat(
146           new byte[] { (byte) status },
147           langBytes,
148           textBytes
149        );
150
151        return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_TEXT, new byte[0], data);
152    }
153}
154