blob: 9fe0bedfe358c405c5f8d2cd6bfead9443c368c8 [file] [log] [blame]
Nick Pelly590b73b2010-10-12 13:00:50 -07001/*
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 android.nfc;
18
Nick Pelly74fe6c62011-02-02 22:37:40 -080019import android.content.Context;
Jeff Hamiltond88e9aa2011-01-24 14:53:00 -060020import android.nfc.tech.IsoDep;
21import android.nfc.tech.MifareClassic;
22import android.nfc.tech.MifareUltralight;
23import android.nfc.tech.Ndef;
24import android.nfc.tech.NdefFormatable;
25import android.nfc.tech.NfcA;
26import android.nfc.tech.NfcB;
27import android.nfc.tech.NfcF;
28import android.nfc.tech.NfcV;
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -060029import android.nfc.tech.TagTechnology;
Jeff Hamilton6be655c2010-11-12 12:28:16 -060030import android.os.Bundle;
Nick Pelly590b73b2010-10-12 13:00:50 -070031import android.os.Parcel;
32import android.os.Parcelable;
Martijn Coenen2dcae5672011-06-02 16:02:00 -070033import android.os.RemoteException;
Jeff Hamilton6be655c2010-11-12 12:28:16 -060034
Martijn Coenen2dcae5672011-06-02 16:02:00 -070035import java.io.IOException;
Jeff Hamilton6be655c2010-11-12 12:28:16 -060036import java.util.Arrays;
Nick Pelly590b73b2010-10-12 13:00:50 -070037
38/**
Nick Pelly74fe6c62011-02-02 22:37:40 -080039 * Represents an NFC tag that has been discovered.
Nick Pelly590b73b2010-10-12 13:00:50 -070040 * <p>
Nick Pelly74fe6c62011-02-02 22:37:40 -080041 * {@link Tag} is an immutable object that represents the state of a NFC tag at
42 * the time of discovery. It can be used as a handle to {@link TagTechnology} classes
Jeff Hamilton28319c02011-02-09 17:26:47 +090043 * to perform advanced operations, or directly queried for its ID via {@link #getId} and the
44 * set of technologies it contains via {@link #getTechList}. Arrays passed to and
45 * returned by this class are <em>not</em> cloned, so be careful not to modify them.
Nick Pelly74fe6c62011-02-02 22:37:40 -080046 * <p>
47 * A new tag object is created every time a tag is discovered (comes into range), even
48 * if it is the same physical tag. If a tag is removed and then returned into range, then
49 * only the most recent tag object can be successfully used to create a {@link TagTechnology}.
50 *
51 * <h3>Tag Dispatch</h3>
52 * When a tag is discovered, a {@link Tag} object is created and passed to a
Jeff Hamilton28319c02011-02-09 17:26:47 +090053 * single activity via the {@link NfcAdapter#EXTRA_TAG} extra in an
54 * {@link android.content.Intent} via {@link Context#startActivity}. A four stage dispatch is used
55 * to select the
56 * most appropriate activity to handle the tag. The Android OS executes each stage in order,
57 * and completes dispatch as soon as a single matching activity is found. If there are multiple
58 * matching activities found at any one stage then the Android activity chooser dialog is shown
59 * to allow the user to select the activity to receive the tag.
60 *
61 * <p>The Tag dispatch mechanism was designed to give a high probability of dispatching
62 * a tag to the correct activity without showing the user an activity chooser dialog.
63 * This is important for NFC interactions because they are very transient -- if a user has to
64 * move the Android device to choose an application then the connection will likely be broken.
65 *
Nick Pelly74fe6c62011-02-02 22:37:40 -080066 * <h4>1. Foreground activity dispatch</h4>
Jeff Hamilton28319c02011-02-09 17:26:47 +090067 * A foreground activity that has called
68 * {@link NfcAdapter#enableForegroundDispatch NfcAdapter.enableForegroundDispatch()} is
69 * given priority. See the documentation on
70 * {@link NfcAdapter#enableForegroundDispatch NfcAdapter.enableForegroundDispatch()} for
Nick Pelly74fe6c62011-02-02 22:37:40 -080071 * its usage.
72 * <h4>2. NDEF data dispatch</h4>
Jeff Hamilton28319c02011-02-09 17:26:47 +090073 * If the tag contains NDEF data the system inspects the first {@link NdefRecord} in the first
74 * {@link NdefMessage}. If the record is a URI, SmartPoster, or MIME data
75 * {@link Context#startActivity} is called with {@link NfcAdapter#ACTION_NDEF_DISCOVERED}. For URI
76 * and SmartPoster records the URI is put into the intent's data field. For MIME records the MIME
77 * type is put in the intent's type field. This allows activities to register to be launched only
78 * when data they know how to handle is present on a tag. This is the preferred method of handling
79 * data on a tag since NDEF data can be stored on many types of tags and doesn't depend on a
80 * specific tag technology.
Nick Pelly74fe6c62011-02-02 22:37:40 -080081 * See {@link NfcAdapter#ACTION_NDEF_DISCOVERED} for more detail. If the tag does not contain
Jeff Hamilton28319c02011-02-09 17:26:47 +090082 * NDEF data, or if no activity is registered
83 * for {@link NfcAdapter#ACTION_NDEF_DISCOVERED} with a matching data URI or MIME type then dispatch
84 * moves to stage 3.
Nick Pelly74fe6c62011-02-02 22:37:40 -080085 * <h4>3. Tag Technology dispatch</h4>
86 * {@link Context#startActivity} is called with {@link NfcAdapter#ACTION_TECH_DISCOVERED} to
Jeff Hamilton28319c02011-02-09 17:26:47 +090087 * dispatch the tag to an activity that can handle the technologies present on the tag.
Nick Pelly74fe6c62011-02-02 22:37:40 -080088 * Technologies are defined as sub-classes of {@link TagTechnology}, see the package
Jeff Hamilton28319c02011-02-09 17:26:47 +090089 * {@link android.nfc.tech}. The Android OS looks for an activity that can handle one or
90 * more technologies in the tag. See {@link NfcAdapter#ACTION_TECH_DISCOVERED} for more detail.
Nick Pelly74fe6c62011-02-02 22:37:40 -080091 * <h4>4. Fall-back dispatch</h4>
Jeff Hamilton28319c02011-02-09 17:26:47 +090092 * If no activity has been matched then {@link Context#startActivity} is called with
Nick Pelly74fe6c62011-02-02 22:37:40 -080093 * {@link NfcAdapter#ACTION_TAG_DISCOVERED}. This is intended as a fall-back mechanism.
94 * See {@link NfcAdapter#ACTION_TAG_DISCOVERED}.
95 *
Nick Pelly74fe6c62011-02-02 22:37:40 -080096 * <h3>NFC Tag Background</h3>
97 * An NFC tag is a passive NFC device, powered by the NFC field of this Android device while
Jeff Hamilton28319c02011-02-09 17:26:47 +090098 * it is in range. Tag's can come in many forms, such as stickers, cards, key fobs, or
Nick Pelly74fe6c62011-02-02 22:37:40 -080099 * even embedded in a more sophisticated device.
100 * <p>
101 * Tags can have a wide range of capabilities. Simple tags just offer read/write semantics,
102 * and contain some one time
103 * programmable areas to make read-only. More complex tags offer math operations
104 * and per-sector access control and authentication. The most sophisticated tags
Jeff Hamilton28319c02011-02-09 17:26:47 +0900105 * contain operating environments allowing complex interactions with the
106 * code executing on the tag. Use {@link TagTechnology} classes to access a broad
Nick Pelly74fe6c62011-02-02 22:37:40 -0800107 * range of capabilities available in NFC tags.
108 * <p>
Nick Pelly590b73b2010-10-12 13:00:50 -0700109 */
Nick Pellyf003e262011-01-31 23:27:37 -0800110public final class Tag implements Parcelable {
Nick Pelly07f3bee2010-10-21 21:42:24 -0700111 /*package*/ final byte[] mId;
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600112 /*package*/ final int[] mTechList;
Jeff Hamiltond88e9aa2011-01-24 14:53:00 -0600113 /*package*/ final String[] mTechStringList;
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600114 /*package*/ final Bundle[] mTechExtras;
Nick Pelly07f3bee2010-10-21 21:42:24 -0700115 /*package*/ final int mServiceHandle; // for use by NFC service, 0 indicates a mock
Martijn Coenen23fc93a2011-11-28 10:43:14 -0800116 /*package*/ final INfcTag mTagService; // interface to NFC service, will be null if mock tag
Sylvain Fonteneaua9265402010-10-15 14:41:22 -0700117
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100118 /*package*/ int mConnectedTechnology;
119
Nick Pelly590b73b2010-10-12 13:00:50 -0700120 /**
Nick Pelly07f3bee2010-10-21 21:42:24 -0700121 * Hidden constructor to be used by NFC service and internal classes.
Nick Pelly590b73b2010-10-12 13:00:50 -0700122 * @hide
123 */
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600124 public Tag(byte[] id, int[] techList, Bundle[] techListExtras, int serviceHandle,
125 INfcTag tagService) {
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600126 if (techList == null) {
Nick Pelly07f3bee2010-10-21 21:42:24 -0700127 throw new IllegalArgumentException("rawTargets cannot be null");
128 }
Nick Pelly07f3bee2010-10-21 21:42:24 -0700129 mId = id;
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600130 mTechList = Arrays.copyOf(techList, techList.length);
Jeff Hamiltond88e9aa2011-01-24 14:53:00 -0600131 mTechStringList = generateTechStringList(techList);
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600132 // Ensure mTechExtras is as long as mTechList
133 mTechExtras = Arrays.copyOf(techListExtras, techList.length);
Nick Pelly07f3bee2010-10-21 21:42:24 -0700134 mServiceHandle = serviceHandle;
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600135 mTagService = tagService;
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100136
137 mConnectedTechnology = -1;
Nick Pelly07f3bee2010-10-21 21:42:24 -0700138 }
139
140 /**
141 * Construct a mock Tag.
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600142 * <p>This is an application constructed tag, so NfcAdapter methods on this Tag may fail
143 * with {@link IllegalArgumentException} since it does not represent a physical Tag.
Nick Pelly07f3bee2010-10-21 21:42:24 -0700144 * <p>This constructor might be useful for mock testing.
145 * @param id The tag identifier, can be null
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600146 * @param techList must not be null
Nick Pelly07f3bee2010-10-21 21:42:24 -0700147 * @return freshly constructed tag
Nick Pellyf003e262011-01-31 23:27:37 -0800148 * @hide
Nick Pelly07f3bee2010-10-21 21:42:24 -0700149 */
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600150 public static Tag createMockTag(byte[] id, int[] techList, Bundle[] techListExtras) {
Martijn Coenen23fc93a2011-11-28 10:43:14 -0800151 // set serviceHandle to 0 and tagService to null to indicate mock tag
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600152 return new Tag(id, techList, techListExtras, 0, null);
Nick Pelly590b73b2010-10-12 13:00:50 -0700153 }
154
Jeff Hamiltond88e9aa2011-01-24 14:53:00 -0600155 private String[] generateTechStringList(int[] techList) {
156 final int size = techList.length;
157 String[] strings = new String[size];
158 for (int i = 0; i < size; i++) {
159 switch (techList[i]) {
160 case TagTechnology.ISO_DEP:
161 strings[i] = IsoDep.class.getName();
162 break;
163 case TagTechnology.MIFARE_CLASSIC:
164 strings[i] = MifareClassic.class.getName();
165 break;
166 case TagTechnology.MIFARE_ULTRALIGHT:
167 strings[i] = MifareUltralight.class.getName();
168 break;
169 case TagTechnology.NDEF:
170 strings[i] = Ndef.class.getName();
171 break;
172 case TagTechnology.NDEF_FORMATABLE:
173 strings[i] = NdefFormatable.class.getName();
174 break;
175 case TagTechnology.NFC_A:
176 strings[i] = NfcA.class.getName();
177 break;
178 case TagTechnology.NFC_B:
179 strings[i] = NfcB.class.getName();
180 break;
181 case TagTechnology.NFC_F:
182 strings[i] = NfcF.class.getName();
183 break;
184 case TagTechnology.NFC_V:
185 strings[i] = NfcV.class.getName();
186 break;
187 default:
188 throw new IllegalArgumentException("Unknown tech type " + techList[i]);
189 }
190 }
191 return strings;
192 }
193
Nick Pelly590b73b2010-10-12 13:00:50 -0700194 /**
195 * For use by NfcService only.
196 * @hide
197 */
Nick Pelly07f3bee2010-10-21 21:42:24 -0700198 public int getServiceHandle() {
199 return mServiceHandle;
Nick Pelly590b73b2010-10-12 13:00:50 -0700200 }
201
202 /**
Nick Pelly590b73b2010-10-12 13:00:50 -0700203 * Get the Tag Identifier (if it has one).
Nick Pelly74fe6c62011-02-02 22:37:40 -0800204 * <p>The tag identifier is a low level serial number, used for anti-collision
205 * and identification.
206 * <p> Most tags have a stable unique identifier
207 * (UID), but some tags will generate a random ID every time they are discovered
208 * (RID), and there are some tags with no ID at all (the byte array will be zero-sized).
209 * <p> The size and format of an ID is specific to the RF technology used by the tag.
210 * <p> This function retrieves the ID as determined at discovery time, and does not
211 * perform any further RF communication or block.
212 * @return ID as byte array, never null
Nick Pelly590b73b2010-10-12 13:00:50 -0700213 */
214 public byte[] getId() {
Nick Pelly07f3bee2010-10-21 21:42:24 -0700215 return mId;
216 }
217
218 /**
Nick Pelly74fe6c62011-02-02 22:37:40 -0800219 * Get the technologies available in this tag, as fully qualified class names.
220 * <p>
221 * A technology is an implementation of the {@link TagTechnology} interface,
222 * and can be instantiated by calling the static <code>get(Tag)</code>
223 * method on the implementation with this Tag. The {@link TagTechnology}
224 * object can then be used to perform advanced, technology-specific operations on a tag.
225 * <p>
226 * Android defines a mandatory set of technologies that must be correctly
227 * enumerated by all Android NFC devices, and an optional
228 * set of proprietary technologies.
229 * See {@link TagTechnology} for more details.
230 * <p>
Jeff Hamiltonc1576ad2010-12-05 21:45:22 -0600231 * The ordering of the returned array is undefined and should not be relied upon.
Nick Pelly74fe6c62011-02-02 22:37:40 -0800232 * @return an array of fully-qualified {@link TagTechnology} class-names.
Nick Pelly07f3bee2010-10-21 21:42:24 -0700233 */
Jeff Hamiltond88e9aa2011-01-24 14:53:00 -0600234 public String[] getTechList() {
235 return mTechStringList;
Nick Pelly07f3bee2010-10-21 21:42:24 -0700236 }
237
Martijn Coenen2dcae5672011-06-02 16:02:00 -0700238 /**
239 * Rediscover the technologies available on this tag.
240 * <p>
241 * The technologies that are available on a tag may change due to
242 * operations being performed on a tag. For example, formatting a
243 * tag as NDEF adds the {@link Ndef} technology. The {@link rediscover}
244 * method reenumerates the available technologies on the tag
245 * and returns a new {@link Tag} object containing these technologies.
246 * <p>
247 * You may not be connected to any of this {@link Tag}'s technologies
248 * when calling this method.
249 * This method guarantees that you will be returned the same Tag
250 * if it is still in the field.
251 * <p>May cause RF activity and may block. Must not be called
252 * from the main application thread. A blocked call will be canceled with
253 * {@link IOException} by calling {@link #close} from another thread.
254 * <p>Does not remove power from the RF field, so a tag having a random
255 * ID should not change its ID.
256 * @return the rediscovered tag object.
257 * @throws IOException if the tag cannot be rediscovered
258 * @hide
259 */
260 // TODO See if we need TagLostException
261 // TODO Unhide for ICS
262 // TODO Update documentation to make sure it matches with the final
263 // implementation.
264 public Tag rediscover() throws IOException {
265 if (getConnectedTechnology() != -1) {
266 throw new IllegalStateException("Close connection to the technology first!");
267 }
268
Martijn Coenen23fc93a2011-11-28 10:43:14 -0800269 if (mTagService == null) {
270 throw new IOException("Mock tags don't support this operation.");
271 }
Martijn Coenen2dcae5672011-06-02 16:02:00 -0700272 try {
273 Tag newTag = mTagService.rediscover(getServiceHandle());
274 if (newTag != null) {
275 return newTag;
276 } else {
277 throw new IOException("Failed to rediscover tag");
278 }
279 } catch (RemoteException e) {
280 throw new IOException("NFC service dead");
281 }
282 }
283
284
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600285 /** @hide */
286 public boolean hasTech(int techType) {
287 for (int tech : mTechList) {
288 if (tech == techType) return true;
289 }
290 return false;
291 }
Nick Pelly74fe6c62011-02-02 22:37:40 -0800292
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600293 /** @hide */
294 public Bundle getTechExtras(int tech) {
Martijn Coenen1253ebc2010-11-24 11:13:57 +0100295 int pos = -1;
296 for (int idx = 0; idx < mTechList.length; idx++) {
297 if (mTechList[idx] == tech) {
298 pos = idx;
299 break;
300 }
301 }
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600302 if (pos < 0) {
303 return null;
304 }
305
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600306 return mTechExtras[pos];
307 }
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600308
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600309 /** @hide */
310 public INfcTag getTagService() {
311 return mTagService;
Nick Pelly07f3bee2010-10-21 21:42:24 -0700312 }
313
Nick Pelly74fe6c62011-02-02 22:37:40 -0800314 /**
315 * Human-readable description of the tag, for debugging.
316 */
Nick Pelly07f3bee2010-10-21 21:42:24 -0700317 @Override
318 public String toString() {
Jeff Hamilton8856c422011-06-30 17:35:05 -0500319 StringBuilder sb = new StringBuilder("TAG: Tech [");
320 String[] techList = getTechList();
321 int length = techList.length;
322 for (int i = 0; i < length; i++) {
323 sb.append(techList[i]);
324 if (i < length - 1) {
325 sb.append(", ");
326 }
Nick Pelly590b73b2010-10-12 13:00:50 -0700327 }
Jeff Hamilton8856c422011-06-30 17:35:05 -0500328 sb.append("]");
Nick Pelly07f3bee2010-10-21 21:42:24 -0700329 return sb.toString();
330 }
331
332 /*package*/ static byte[] readBytesWithNull(Parcel in) {
333 int len = in.readInt();
334 byte[] result = null;
Sylvain Fonteneaue2e4ea92010-10-25 07:28:03 -0700335 if (len >= 0) {
Nick Pelly07f3bee2010-10-21 21:42:24 -0700336 result = new byte[len];
337 in.readByteArray(result);
338 }
339 return result;
340 }
341
342 /*package*/ static void writeBytesWithNull(Parcel out, byte[] b) {
343 if (b == null) {
344 out.writeInt(-1);
345 return;
346 }
347 out.writeInt(b.length);
348 out.writeByteArray(b);
Nick Pelly590b73b2010-10-12 13:00:50 -0700349 }
350
351 @Override
352 public int describeContents() {
353 return 0;
354 }
355
356 @Override
357 public void writeToParcel(Parcel dest, int flags) {
Sylvain Fonteneauc5a418e2011-01-24 10:23:43 +0100358 // Null mTagService means this is a mock tag
359 int isMock = (mTagService == null)?1:0;
360
Nick Pelly07f3bee2010-10-21 21:42:24 -0700361 writeBytesWithNull(dest, mId);
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600362 dest.writeInt(mTechList.length);
363 dest.writeIntArray(mTechList);
364 dest.writeTypedArray(mTechExtras, 0);
Nick Pelly07f3bee2010-10-21 21:42:24 -0700365 dest.writeInt(mServiceHandle);
Sylvain Fonteneauc5a418e2011-01-24 10:23:43 +0100366 dest.writeInt(isMock);
367 if (isMock == 0) {
368 dest.writeStrongBinder(mTagService.asBinder());
369 }
Nick Pelly590b73b2010-10-12 13:00:50 -0700370 }
371
372 public static final Parcelable.Creator<Tag> CREATOR =
373 new Parcelable.Creator<Tag>() {
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600374 @Override
Nick Pelly590b73b2010-10-12 13:00:50 -0700375 public Tag createFromParcel(Parcel in) {
Sylvain Fonteneauc5a418e2011-01-24 10:23:43 +0100376 INfcTag tagService;
377
Nick Pelly07f3bee2010-10-21 21:42:24 -0700378 // Tag fields
379 byte[] id = Tag.readBytesWithNull(in);
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600380 int[] techList = new int[in.readInt()];
381 in.readIntArray(techList);
382 Bundle[] techExtras = in.createTypedArray(Bundle.CREATOR);
Nick Pelly07f3bee2010-10-21 21:42:24 -0700383 int serviceHandle = in.readInt();
Sylvain Fonteneauc5a418e2011-01-24 10:23:43 +0100384 int isMock = in.readInt();
385 if (isMock == 0) {
386 tagService = INfcTag.Stub.asInterface(in.readStrongBinder());
387 }
388 else {
389 tagService = null;
390 }
Nick Pelly590b73b2010-10-12 13:00:50 -0700391
Jeff Hamilton4e21e1d2011-01-21 01:13:06 -0600392 return new Tag(id, techList, techExtras, serviceHandle, tagService);
Nick Pelly590b73b2010-10-12 13:00:50 -0700393 }
Jeff Hamilton6be655c2010-11-12 12:28:16 -0600394
395 @Override
Nick Pelly590b73b2010-10-12 13:00:50 -0700396 public Tag[] newArray(int size) {
397 return new Tag[size];
398 }
399 };
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100400
Jeff Hamiltonbe372d62010-12-22 19:20:26 -0600401 /**
402 * For internal use only.
403 *
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100404 * @hide
405 */
406 public synchronized void setConnectedTechnology(int technology) {
407 if (mConnectedTechnology == -1) {
408 mConnectedTechnology = technology;
409 } else {
410 throw new IllegalStateException("Close other technology first!");
411 }
412 }
413
Jeff Hamiltonbe372d62010-12-22 19:20:26 -0600414 /**
415 * For internal use only.
416 *
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100417 * @hide
418 */
419 public int getConnectedTechnology() {
420 return mConnectedTechnology;
421 }
422
Jeff Hamiltonbe372d62010-12-22 19:20:26 -0600423 /**
424 * For internal use only.
425 *
Martijn Coenen4049f9d02010-12-14 16:58:27 +0100426 * @hide
427 */
428 public void setTechnologyDisconnected() {
429 mConnectedTechnology = -1;
430 }
Martijn Coenen1253ebc2010-11-24 11:13:57 +0100431}