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 
17 package com.android.apps.tag.record;
18 
19 import com.android.apps.tag.R;
20 import com.google.common.annotations.VisibleForTesting;
21 import com.google.common.base.Preconditions;
22 import com.google.common.primitives.Bytes;
23 
24 import android.app.Activity;
25 import android.content.Context;
26 import android.nfc.NdefRecord;
27 import android.view.LayoutInflater;
28 import android.view.View;
29 import android.view.ViewGroup;
30 import android.widget.TextView;
31 
32 import java.io.UnsupportedEncodingException;
33 import java.nio.charset.Charset;
34 import java.util.Arrays;
35 import java.util.Locale;
36 
37 /**
38  * An NFC Text Record
39  */
40 public 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 
TextRecord(String languageCode, String text)48     private TextRecord(String languageCode, String text) {
49         mLanguageCode = Preconditions.checkNotNull(languageCode);
50         mText = Preconditions.checkNotNull(text);
51     }
52 
53     @Override
getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset)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
getSnippet(Context context, Locale locale)61     public String getSnippet(Context context, Locale locale) {
62         return mText;
63     }
64 
getText()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
getLanguageCode()75     public String getLanguageCode() {
76         return mLanguageCode;
77     }
78 
79     // TODO: deal with text fields which span multiple NdefRecords
parse(NdefRecord record)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 
isText(NdefRecord record)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
newTextRecord(String text, Locale locale)129     public static NdefRecord newTextRecord(String text, Locale locale) {
130         return newTextRecord(text, locale, true);
131     }
132 
newTextRecord(String text, Locale locale, boolean encodeInUtf8)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