157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton/*
257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * Copyright (C) 2010 The Android Open Source Project
357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton *
457d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * Licensed under the Apache License, Version 2.0 (the "License");
557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * you may not use this file except in compliance with the License.
657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * You may obtain a copy of the License at
757d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton *
857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton *      http://www.apache.org/licenses/LICENSE-2.0
957d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton *
1057d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * Unless required by applicable law or agreed to in writing, software
1157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * distributed under the License is distributed on an "AS IS" BASIS,
1257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * See the License for the specific language governing permissions and
1457d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * limitations under the License.
1557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton */
1657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
17ca1a86ecb8edce740a232c3439355e8d5b706e7aJeff Hamiltonpackage com.android.nfc.ndefpush;
1857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
194a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamiltonimport com.android.nfc.DeviceHost.LlcpServerSocket;
204a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamiltonimport com.android.nfc.DeviceHost.LlcpSocket;
214a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamiltonimport com.android.nfc.LlcpException;
2257d376f1ee1a3939977b95759525585abb9601fbJeff Hamiltonimport com.android.nfc.NfcService;
2357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
2457d376f1ee1a3939977b95759525585abb9601fbJeff Hamiltonimport android.nfc.FormatException;
252429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenenimport android.nfc.NdefMessage;
2657d376f1ee1a3939977b95759525585abb9601fbJeff Hamiltonimport android.nfc.NfcAdapter;
2757d376f1ee1a3939977b95759525585abb9601fbJeff Hamiltonimport android.util.Log;
2857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
2957d376f1ee1a3939977b95759525585abb9601fbJeff Hamiltonimport java.io.ByteArrayOutputStream;
3057d376f1ee1a3939977b95759525585abb9601fbJeff Hamiltonimport java.io.IOException;
3157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
3257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton/**
3357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton * A simple server that accepts NDEF messages pushed to it over an LLCP connection. Those messages
344a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton * are typically set on the client side by using {@link NfcAdapter#enableForegroundNdefPush}.
3557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton */
36ca1a86ecb8edce740a232c3439355e8d5b706e7aJeff Hamiltonpublic class NdefPushServer {
37ca1a86ecb8edce740a232c3439355e8d5b706e7aJeff Hamilton    private static final String TAG = "NdefPushServer";
3857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    private static final boolean DBG = true;
393fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton
4073a6ef05557f78d20e291ef34aec013770644da0Sylvain Fonteneau    private static final int MIU = 248;
414a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton
424a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton    int mSap;
4357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
44ca1a86ecb8edce740a232c3439355e8d5b706e7aJeff Hamilton    static final String SERVICE_NAME = "com.android.npp";
4557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
4657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    NfcService mService = NfcService.getInstance();
473fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton
482429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenen    final Callback mCallback;
492429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenen
5057d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    /** Protected by 'this', null when stopped, non-null when running */
5157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    ServerThread mServerThread = null;
5257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
532429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenen    public interface Callback {
542429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenen        void onMessageReceived(NdefMessage msg);
552429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenen    }
562429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenen
572429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenen    public NdefPushServer(final int sap, Callback callback) {
5832cdff503e206b6753c6cf020c545163a43bcaa1Martijn Coenen        mSap = sap;
592429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenen        mCallback = callback;
6032cdff503e206b6753c6cf020c545163a43bcaa1Martijn Coenen    }
6132cdff503e206b6753c6cf020c545163a43bcaa1Martijn Coenen
6257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    /** Connection class, used to handle incoming connections */
6357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    private class ConnectionThread extends Thread {
6457d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        private LlcpSocket mSock;
6557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
6657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        ConnectionThread(LlcpSocket sock) {
67ca1a86ecb8edce740a232c3439355e8d5b706e7aJeff Hamilton            super(TAG);
6857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            mSock = sock;
6957d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        }
7057d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
7157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        @Override
7257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        public void run() {
733fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton            if (DBG) Log.d(TAG, "starting connection thread");
7457d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            try {
7557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
7657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                byte[] partial = new byte[1024];
7757d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                int size;
7857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                boolean connectionBroken = false;
7957d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
8057d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                // Get raw data from remote server
8157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                while(!connectionBroken) {
8257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                    try {
8357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                        size = mSock.receive(partial);
843fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                        if (DBG) Log.d(TAG, "read " + size + " bytes");
8557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                        if (size < 0) {
8657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                            connectionBroken = true;
8757d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                            break;
8857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                        } else {
8957d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                            buffer.write(partial, 0, size);
9057d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                        }
9157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                    } catch (IOException e) {
9257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                        // Connection broken
9357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                        connectionBroken = true;
943fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                        if (DBG) Log.d(TAG, "connection broken by IOException", e);
9557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                    }
9657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                }
9757d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
98ca1a86ecb8edce740a232c3439355e8d5b706e7aJeff Hamilton                // Build NDEF message set from the stream
99ca1a86ecb8edce740a232c3439355e8d5b706e7aJeff Hamilton                NdefPushProtocol msg = new NdefPushProtocol(buffer.toByteArray());
1003fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                if (DBG) Log.d(TAG, "got message " + msg.toString());
10157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
10257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                // Send the intent for the fake tag
1032429c20351e7a660b1fcb0b6c3d2558c05f6f5deMartijn Coenen                mCallback.onMessageReceived(msg.getImmediate());
10457d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            } catch (FormatException e) {
1053fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                Log.e(TAG, "badly formatted NDEF message, ignoring", e);
106b40b1d6ff2b05f8629d0cf62e987658f6c1e4731Jeff Hamilton            } finally {
107b40b1d6ff2b05f8629d0cf62e987658f6c1e4731Jeff Hamilton                try {
1083fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                    if (DBG) Log.d(TAG, "about to close");
109b40b1d6ff2b05f8629d0cf62e987658f6c1e4731Jeff Hamilton                    mSock.close();
110b40b1d6ff2b05f8629d0cf62e987658f6c1e4731Jeff Hamilton                } catch (IOException e) {
111b40b1d6ff2b05f8629d0cf62e987658f6c1e4731Jeff Hamilton                    // ignore
112b40b1d6ff2b05f8629d0cf62e987658f6c1e4731Jeff Hamilton                }
11357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            }
1143fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton            if (DBG) Log.d(TAG, "finished connection thread");
11557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        }
116c9342fef947c49e247495b83f94f16d43cd3562cmike wakerly    }
11757d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
11857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    /** Server class, used to listen for incoming connection request */
11957d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    class ServerThread extends Thread {
120525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project        // Variables below synchronized on NdefPushServer.this
12157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        boolean mRunning = true;
1224a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton        LlcpServerSocket mServerSocket;
12357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
12457d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        @Override
12557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        public void run() {
126525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project            boolean threadRunning;
127525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project            synchronized (NdefPushServer.this) {
128525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                threadRunning = mRunning;
129525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project            }
130525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project            while (threadRunning) {
1313fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                if (DBG) Log.d(TAG, "about create LLCP service socket");
13289c67765e6df5925a7799176a18b17171cbf9e03Sylvain Fonteneau                try {
133525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    synchronized (NdefPushServer.this) {
134525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        mServerSocket = mService.createLlcpServerSocket(mSap, SERVICE_NAME,
135525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                                MIU, 1, 1024);
136525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    }
1374a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton                    if (mServerSocket == null) {
1384a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton                        if (DBG) Log.d(TAG, "failed to create LLCP service socket");
1394a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton                        return;
1404a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton                    }
1414a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton                    if (DBG) Log.d(TAG, "created LLCP service socket");
142525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    synchronized (NdefPushServer.this) {
143525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        threadRunning = mRunning;
144525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    }
145525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project
146525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    while (threadRunning) {
147525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        LlcpServerSocket serverSocket;
148525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        synchronized (NdefPushServer.this) {
149525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                            serverSocket = mServerSocket;
150525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        }
151525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        if (serverSocket == null) return;
152525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project
1533fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                        if (DBG) Log.d(TAG, "about to accept");
154525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        LlcpSocket communicationSocket = serverSocket.accept();
1553fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                        if (DBG) Log.d(TAG, "accept returned " + communicationSocket);
15689c67765e6df5925a7799176a18b17171cbf9e03Sylvain Fonteneau                        if (communicationSocket != null) {
15789c67765e6df5925a7799176a18b17171cbf9e03Sylvain Fonteneau                            new ConnectionThread(communicationSocket).start();
15889c67765e6df5925a7799176a18b17171cbf9e03Sylvain Fonteneau                        }
159525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project
160525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        synchronized (NdefPushServer.this) {
161525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                            threadRunning = mRunning;
162525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        }
16389c67765e6df5925a7799176a18b17171cbf9e03Sylvain Fonteneau                    }
1643fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                    if (DBG) Log.d(TAG, "stop running");
16589c67765e6df5925a7799176a18b17171cbf9e03Sylvain Fonteneau                } catch (LlcpException e) {
1663fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                    Log.e(TAG, "llcp error", e);
16789c67765e6df5925a7799176a18b17171cbf9e03Sylvain Fonteneau                } catch (IOException e) {
1683fb30ae5bf51d9ffe6271a345d55905dade8040dJeff Hamilton                    Log.e(TAG, "IO error", e);
16989c67765e6df5925a7799176a18b17171cbf9e03Sylvain Fonteneau                } finally {
170525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    synchronized (NdefPushServer.this) {
171525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        if (mServerSocket != null) {
172525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                            if (DBG) Log.d(TAG, "about to close");
173525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                            try {
174525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                                mServerSocket.close();
175525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                            } catch (IOException e) {
176525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                                // ignore
177525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                            }
178525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                            mServerSocket = null;
1794a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton                        }
18089c67765e6df5925a7799176a18b17171cbf9e03Sylvain Fonteneau                    }
181e9848c7a0ab1162e7355683f0cc12e2455d7c939Sylvain Fonteneau                }
182525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project
183525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                synchronized (NdefPushServer.this) {
184525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    threadRunning = mRunning;
185525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                }
18657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            }
18757d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        }
18857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
18957d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        public void shutdown() {
190525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project            synchronized (NdefPushServer.this) {
191525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                mRunning = false;
192525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                if (mServerSocket != null) {
193525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    try {
194525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        mServerSocket.close();
195525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    } catch (IOException e) {
196525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                        // ignore
197525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    }
198525c260303268a83da4c3413b953d13c9084e834The Android Open Source Project                    mServerSocket = null;
1994a61d3b45e81c0070538f94747a70a49c78f12faJeff Hamilton                }
20057d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            }
20157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        }
202c9342fef947c49e247495b83f94f16d43cd3562cmike wakerly    }
20357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
20457d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    public void start() {
20557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        synchronized (this) {
20657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            if (DBG) Log.d(TAG, "start, thread = " + mServerThread);
20757d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            if (mServerThread == null) {
20857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                if (DBG) Log.d(TAG, "starting new server thread");
20957d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                mServerThread = new ServerThread();
21057d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                mServerThread.start();
21157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            }
21257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        }
21357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    }
21457d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton
21557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    public void stop() {
21657d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        synchronized (this) {
21757d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            if (DBG) Log.d(TAG, "stop, thread = " + mServerThread);
21857d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            if (mServerThread != null) {
21957d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                if (DBG) Log.d(TAG, "shuting down server thread");
22057d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                mServerThread.shutdown();
22157d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton                mServerThread = null;
22257d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton            }
22357d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton        }
22457d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton    }
22557d376f1ee1a3939977b95759525585abb9601fbJeff Hamilton}
226