public class

NdefFactory

extends Object
/*
 * Copyright (C) 2011 Stanford University MobiSocial Lab
 * http://mobisocial.stanford.edu
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package mobisocial.nfc;

import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URL;
import java.util.Arrays;

import android.net.Uri;
import android.nfc.FormatException;
import android.nfc.NdefMessage;
import android.nfc.NdefRecord;
import android.util.Base64;

/**
 * A utility class for generating NDEF messages.
 * @see NdefMessage
 */
public class NdefFactory {
    /**
     * An RTD indicating an Android Application Record.
     */
    public static final byte[] RTD_ANDROID_APP = "android.com:pkg".getBytes();

    private static final String[] URI_PREFIXES = new String[] {
        "",
        "http://www.",
        "https://www.",
        "http://",
        "https://",
        "tel:",
        "mailto:",
        "ftp://anonymous:anonymous@",
        "ftp://ftp.",
        "ftps://",
        "sftp://",
        "smb://",
        "nfs://",
        "ftp://",
        "dav://",
        "news:",
        "telnet://",
        "imap:",
        "rtsp://",
        "urn:",
        "pop:",
        "sip:",
        "sips:",
        "tftp:",
        "btspp://",
        "btl2cap://",
        "btgoep://",
        "tcpobex://",
        "irdaobex://",
        "file://",
        "urn:epc:id:",
        "urn:epc:tag:",
        "urn:epc:pat:",
        "urn:epc:raw:",
        "urn:epc:",
        "urn:nfc:",
    };

    public static NdefMessage fromUri(Uri uri) {
        return fromUri(uri.toString());
    }

    public static NdefMessage fromUri(URI uri) {
        return fromUri(uri.toString());
    }

    public static NdefMessage fromUri(String uri) {
        try {
            int prefix = 0;
            for (int i = 1; i < URI_PREFIXES.length; i++) {
                if (uri.startsWith(URI_PREFIXES[i])) {
                    prefix = i;
                    break;
                }
            }
            if (prefix > 0) uri = uri.substring(URI_PREFIXES[prefix].length());
            int len = uri.length();
            byte[] payload = new byte[len + 1];
            payload[0] = (byte) prefix;
            System.arraycopy(uri.getBytes("UTF-8"), 0, payload, 1, len);
            NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, NdefRecord.RTD_URI,
                    new byte[0], payload);
            NdefRecord[] records = new NdefRecord[] { record };
            return new NdefMessage(records);
        } catch (NoClassDefFoundError e) {
            return null;
        } catch (UnsupportedEncodingException e) {
            // no UTF-8? really?
            return null;
        }
    }

    public static NdefMessage fromUrl(URL url) {
        return fromUri(url.toString());
    }

    public static NdefMessage fromMime(String mimeType, byte[] data) {
        try {
            NdefRecord record = new NdefRecord(NdefRecord.TNF_MIME_MEDIA,
                    mimeType.getBytes(), new byte[0], data);
            NdefRecord[] records = new NdefRecord[] { record };
            return new NdefMessage(records);
        } catch (NoClassDefFoundError e) {
            return null;
        }
    }

    /**
     * Creates an NDEF message with a single text record, with language
     * code "en" and the given text, encoded using UTF-8.
     */
    public static NdefMessage fromText(String text) {
        try {
            byte[] textBytes = text.getBytes();
            byte[] textPayload = new byte[textBytes.length + 3];
            textPayload[0] = 0x02; // Status byte; UTF-8 and "en" encoding.
            textPayload[1] = 'e';
            textPayload[2] = 'n';
            System.arraycopy(textBytes, 0, textPayload, 3, textBytes.length);
            NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
                    NdefRecord.RTD_TEXT, new byte[0], textPayload);
            NdefRecord[] records = new NdefRecord[] { record };
            return new NdefMessage(records);
        } catch (NoClassDefFoundError e) {
            return null;
        }
    }

    /**
     * Creates an NDEF message with a single text record, with the given
     * text content (UTF-8 encoded) and language code.
     */
    public static NdefMessage fromText(String text, String languageCode) {
        try {
            int languageCodeLength = languageCode.length();
            int textLength = text.length();
            byte[] textPayload = new byte[textLength + 1 + languageCodeLength];
            textPayload[0] = (byte)(0x3F & languageCodeLength); // UTF-8 with the given language code length.
            System.arraycopy(languageCode.getBytes(), 0, textPayload, 1, languageCodeLength);
            System.arraycopy(text.getBytes(), 0, textPayload, 1 + languageCodeLength, textLength);
            NdefRecord record = new NdefRecord(NdefRecord.TNF_WELL_KNOWN,
                    NdefRecord.RTD_TEXT, new byte[0], textPayload);
            NdefRecord[] records = new NdefRecord[] { record };
            return new NdefMessage(records);
        } catch (NoClassDefFoundError e) {
            return null;
        }
    }

    public static final NdefMessage getEmptyNdef() {
        byte[] empty = new byte[] {};
        NdefRecord[] records = new NdefRecord[1];
        records[0] = new NdefRecord(NdefRecord.TNF_WELL_KNOWN, empty, empty, empty);
        NdefMessage ndef = new NdefMessage(records);
        return ndef;
    }

    public static final boolean isEmpty(NdefMessage ndef) {
        return  (ndef == null || ndef.equals(getEmptyNdef()));
    }

    /**
     * Converts an Ndef message encoded in uri format to an NdefMessage.
     * {@hide}
     */
    public static final NdefMessage fromNdefUri(Uri uri) {
        if (!"ndef".equals(uri.getScheme())) {
            throw new IllegalArgumentException("Not an ndef:// uri. did you mean NdefFactory.fromUri()?");
        }
        NdefMessage wrappedNdef;
        try {
            wrappedNdef = new NdefMessage(android.util.Base64.decode(
                uri.getPath().substring(1), Base64.URL_SAFE));
        } catch (FormatException e) {
            throw new IllegalArgumentException("Format error.");
        }
        return wrappedNdef;
    }

    public static final Uri parseUri(NdefRecord record) throws FormatException {
        int tnf = record.getTnf();
        if (tnf == NdefRecord.TNF_ABSOLUTE_URI) {
            return Uri.parse(new String(record.getType()));
        }
        if (tnf == NdefRecord.TNF_WELL_KNOWN &&
                Arrays.equals(NdefRecord.RTD_URI, record.getType())) {
            byte[] payload = record.getPayload();
            int pre = (int)payload[0];
            if (!(pre >= 0 && pre < URI_PREFIXES.length)) {
                throw new FormatException("Unknown uri prefix: " + pre);
            }
            String prefix = URI_PREFIXES[pre];
            String uriStr = new StringBuilder()
                .append(prefix).append(new String(payload, 1, payload.length - 1))
                .toString();
            return Uri.parse(uriStr);
        }
        throw new FormatException("NdefRecord is not a uri.");
    }

    /**
     * Converts an Ndef message to its uri encoding, using the
     * {code ndef://} scheme.
     */
    // TODO Switch for the appropriate type
    /*
    public static final Uri toNdefUri(NdefMessage ndef) {
        return Uri.parse("ndef://url/" + Base64.encodeToString(ndef.toByteArray(), Base64.URL_SAFE));
    }*/

    /**
     * <p>
     * From Android SDK, version 14. (C) 2010 The Android Open Source Project
     * <p>
     * Creates an Android application NDEF record.
     * <p>
     * This record indicates to other Android devices the package that should be
     * used to handle the rest of the NDEF message. You can embed this record
     * anywhere into your NDEF message to ensure that the intended package
     * receives the message.
     * <p>
     * When an Android device dispatches an {@link NdefMessage} containing one
     * or more Android application records, the applications contained in those
     * records will be the preferred target for the NDEF_DISCOVERED intent, in
     * the order in which they appear in the {@link NdefMessage}. This dispatch
     * behavior was first added to Android in Ice Cream Sandwich.
     * <p>
     * If none of the applications are installed on the device, a Market link
     * will be opened to the first application.
     * <p>
     * Note that Android application records do not overrule applications that
     * have called {@link NfcAdapter#enableForegroundDispatch}.
     * 
     * @param packageName Android package name
     * @return Android application NDEF record
     */
    public static NdefRecord createApplicationRecord(String packageName) {
        try {
            return new NdefRecord(NdefRecord.TNF_EXTERNAL_TYPE, RTD_ANDROID_APP, new byte[] {},
                    packageName.getBytes("US-ASCII"));
        } catch (UnsupportedEncodingException e) {
            return null;
        }
    }
}