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.collect.BiMap; 23import com.google.common.collect.ImmutableBiMap; 24import com.google.common.primitives.Bytes; 25 26import android.app.Activity; 27import android.content.ActivityNotFoundException; 28import android.content.Context; 29import android.content.Intent; 30import android.content.pm.PackageManager; 31import android.content.pm.ResolveInfo; 32import android.net.Uri; 33import android.nfc.NdefRecord; 34import android.os.Parcel; 35import android.os.Parcelable; 36import android.telephony.PhoneNumberUtils; 37import android.text.TextUtils; 38import android.util.Log; 39import android.view.LayoutInflater; 40import android.view.View; 41import android.view.View.OnClickListener; 42import android.view.ViewGroup; 43import android.widget.ImageView; 44import android.widget.TextView; 45 46import java.nio.charset.Charset; 47import java.util.Arrays; 48import java.util.List; 49import java.util.Locale; 50 51/** 52 * A parsed record containing a Uri. 53 */ 54public class UriRecord extends ParsedNdefRecord implements OnClickListener { 55 private static final String TAG = "UriRecord"; 56 57 public static final String RECORD_TYPE = "UriRecord"; 58 59 /** 60 * NFC Forum "URI Record Type Definition" 61 * 62 * This is a mapping of "URI Identifier Codes" to URI string prefixes, 63 * per section 3.2.2 of the NFC Forum URI Record Type Definition document. 64 */ 65 private static final BiMap<Byte, String> URI_PREFIX_MAP = ImmutableBiMap.<Byte, String>builder() 66 .put((byte) 0x00, "") 67 .put((byte) 0x01, "http://www.") 68 .put((byte) 0x02, "https://www.") 69 .put((byte) 0x03, "http://") 70 .put((byte) 0x04, "https://") 71 .put((byte) 0x05, "tel:") 72 .put((byte) 0x06, "mailto:") 73 .put((byte) 0x07, "ftp://anonymous:anonymous@") 74 .put((byte) 0x08, "ftp://ftp.") 75 .put((byte) 0x09, "ftps://") 76 .put((byte) 0x0A, "sftp://") 77 .put((byte) 0x0B, "smb://") 78 .put((byte) 0x0C, "nfs://") 79 .put((byte) 0x0D, "ftp://") 80 .put((byte) 0x0E, "dav://") 81 .put((byte) 0x0F, "news:") 82 .put((byte) 0x10, "telnet://") 83 .put((byte) 0x11, "imap:") 84 .put((byte) 0x12, "rtsp://") 85 .put((byte) 0x13, "urn:") 86 .put((byte) 0x14, "pop:") 87 .put((byte) 0x15, "sip:") 88 .put((byte) 0x16, "sips:") 89 .put((byte) 0x17, "tftp:") 90 .put((byte) 0x18, "btspp://") 91 .put((byte) 0x19, "btl2cap://") 92 .put((byte) 0x1A, "btgoep://") 93 .put((byte) 0x1B, "tcpobex://") 94 .put((byte) 0x1C, "irdaobex://") 95 .put((byte) 0x1D, "file://") 96 .put((byte) 0x1E, "urn:epc:id:") 97 .put((byte) 0x1F, "urn:epc:tag:") 98 .put((byte) 0x20, "urn:epc:pat:") 99 .put((byte) 0x21, "urn:epc:raw:") 100 .put((byte) 0x22, "urn:epc:") 101 .put((byte) 0x23, "urn:nfc:") 102 .build(); 103 104 private final Uri mUri; 105 106 private UriRecord(Uri uri) { 107 this.mUri = Preconditions.checkNotNull(uri); 108 } 109 110 public Intent getIntentForUri() { 111 String scheme = mUri.getScheme(); 112 if ("tel".equals(scheme)) { 113 return new Intent(Intent.ACTION_CALL, mUri); 114 } else if ("sms".equals(scheme) || "smsto".equals(scheme)) { 115 return new Intent(Intent.ACTION_SENDTO, mUri); 116 } else { 117 return new Intent(Intent.ACTION_VIEW, mUri); 118 } 119 } 120 121 public String getPrettyUriString(Context context) { 122 String scheme = mUri.getScheme(); 123 boolean tel = "tel".equals(scheme); 124 boolean sms = "sms".equals(scheme) || "smsto".equals(scheme); 125 if (tel || sms) { 126 String ssp = mUri.getSchemeSpecificPart(); 127 int offset = ssp.indexOf('?'); 128 if (offset >= 0) { 129 ssp = ssp.substring(0, offset); 130 } 131 if (tel) { 132 return context.getString(R.string.action_call, PhoneNumberUtils.formatNumber(ssp)); 133 } else { 134 return context.getString(R.string.action_text, PhoneNumberUtils.formatNumber(ssp)); 135 } 136 } else { 137 return mUri.toString(); 138 } 139 } 140 141 @Override 142 public View getView(Activity activity, LayoutInflater inflater, ViewGroup parent, int offset) { 143 return RecordUtils.getViewsForIntent(activity, inflater, parent, this, getIntentForUri(), 144 getPrettyUriString(activity)); 145 } 146 147 @Override 148 public String getSnippet(Context context, Locale locale) { 149 return getPrettyUriString(context); 150 } 151 152 @Override 153 public void onClick(View view) { 154 RecordUtils.ClickInfo info = (RecordUtils.ClickInfo) view.getTag(); 155 try { 156 info.activity.startActivity(info.intent); 157 info.activity.finish(); 158 } catch (ActivityNotFoundException e) { 159 // The activity wansn't found for some reason. Don't crash, but don't do anything. 160 Log.e(TAG, "Failed to launch activity for intent " + info.intent, e); 161 } 162 } 163 164 @VisibleForTesting 165 public Uri getUri() { 166 return mUri; 167 } 168 169 /** 170 * Convert {@link android.nfc.NdefRecord} into a {@link android.net.Uri}. This will handle 171 * both TNF_WELL_KNOWN / RTD_URI and TNF_ABSOLUTE_URI. 172 * 173 * @throws IllegalArgumentException if the NdefRecord is not a 174 * record containing a URI. 175 */ 176 public static UriRecord parse(NdefRecord record) { 177 short tnf = record.getTnf(); 178 if (tnf == NdefRecord.TNF_WELL_KNOWN) { 179 return parseWellKnown(record); 180 } else if (tnf == NdefRecord.TNF_ABSOLUTE_URI) { 181 return parseAbsolute(record); 182 } 183 throw new IllegalArgumentException("Unknown TNF " + tnf); 184 } 185 186 /** Parse and absolute URI record */ 187 private static UriRecord parseAbsolute(NdefRecord record) { 188 byte[] payload = record.getPayload(); 189 Uri uri = Uri.parse(new String(payload, Charset.forName("UTF-8"))); 190 return new UriRecord(uri); 191 } 192 193 /** Parse an well known URI record */ 194 private static UriRecord parseWellKnown(NdefRecord record) { 195 Preconditions.checkArgument(Arrays.equals(record.getType(), NdefRecord.RTD_URI)); 196 197 byte[] payload = record.getPayload(); 198 Preconditions.checkArgument(payload.length > 0); 199 200 /* 201 * payload[0] contains the URI Identifier Code, per the 202 * NFC Forum "URI Record Type Definition" section 3.2.2. 203 * 204 * payload[1]...payload[payload.length - 1] contains the rest of 205 * the URI. 206 */ 207 208 String prefix = URI_PREFIX_MAP.get(payload[0]); 209 Preconditions.checkArgument(prefix != null); 210 211 byte[] fullUri = Bytes.concat( 212 prefix.getBytes(Charset.forName("UTF-8")), 213 Arrays.copyOfRange(payload, 1, payload.length)); 214 215 Uri uri = Uri.parse(new String(fullUri, Charset.forName("UTF-8"))); 216 return new UriRecord(uri); 217 } 218 219 public static boolean isUri(NdefRecord record) { 220 try { 221 parse(record); 222 return true; 223 } catch (IllegalArgumentException e) { 224 return false; 225 } 226 } 227 228 private static final byte[] EMPTY = new byte[0]; 229 230 /** 231 * Convert a {@link Uri} to an {@link NdefRecord} 232 */ 233 public static NdefRecord newUriRecord(Uri uri) { 234 byte[] uriBytes = uri.toString().getBytes(Charset.forName("UTF-8")); 235 236 /* 237 * We prepend 0x00 to the bytes of the URI to indicate that this 238 * is the entire URI, and we are not taking advantage of the 239 * URI shortening rules in the NFC Forum URI spec section 3.2.2. 240 * This produces a NdefRecord which is slightly larger than 241 * necessary. 242 * 243 * In the future, we should use the URI shortening rules in 3.2.2 244 * to create a smaller NdefRecord. 245 */ 246 byte[] payload = Bytes.concat(new byte[] { 0x00 }, uriBytes); 247 248 return new NdefRecord(NdefRecord.TNF_WELL_KNOWN, 249 NdefRecord.RTD_URI, EMPTY, payload); 250 } 251} 252