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.nfc.ndefpush;
18 
19 import com.android.nfc.DeviceHost.LlcpServerSocket;
20 import com.android.nfc.DeviceHost.LlcpSocket;
21 import com.android.nfc.LlcpException;
22 import com.android.nfc.NfcService;
23 
24 import android.nfc.FormatException;
25 import android.nfc.NdefMessage;
26 import android.nfc.NfcAdapter;
27 import android.util.Log;
28 
29 import java.io.ByteArrayOutputStream;
30 import java.io.IOException;
31 
32 /**
33  * A simple server that accepts NDEF messages pushed to it over an LLCP connection. Those messages
34  * are typically set on the client side by using {@link NfcAdapter#enableForegroundNdefPush}.
35  */
36 public class NdefPushServer {
37     private static final String TAG = "NdefPushServer";
38     private static final boolean DBG = true;
39 
40     private static final int MIU = 248;
41 
42     int mSap;
43 
44     static final String SERVICE_NAME = "com.android.npp";
45 
46     NfcService mService = NfcService.getInstance();
47 
48     final Callback mCallback;
49 
50     /** Protected by 'this', null when stopped, non-null when running */
51     ServerThread mServerThread = null;
52 
53     public interface Callback {
onMessageReceived(NdefMessage msg)54         void onMessageReceived(NdefMessage msg);
55     }
56 
NdefPushServer(final int sap, Callback callback)57     public NdefPushServer(final int sap, Callback callback) {
58         mSap = sap;
59         mCallback = callback;
60     }
61 
62     /** Connection class, used to handle incoming connections */
63     private class ConnectionThread extends Thread {
64         private LlcpSocket mSock;
65 
ConnectionThread(LlcpSocket sock)66         ConnectionThread(LlcpSocket sock) {
67             super(TAG);
68             mSock = sock;
69         }
70 
71         @Override
run()72         public void run() {
73             if (DBG) Log.d(TAG, "starting connection thread");
74             try {
75                 ByteArrayOutputStream buffer = new ByteArrayOutputStream(1024);
76                 byte[] partial = new byte[1024];
77                 int size;
78                 boolean connectionBroken = false;
79 
80                 // Get raw data from remote server
81                 while(!connectionBroken) {
82                     try {
83                         size = mSock.receive(partial);
84                         if (DBG) Log.d(TAG, "read " + size + " bytes");
85                         if (size < 0) {
86                             connectionBroken = true;
87                             break;
88                         } else {
89                             buffer.write(partial, 0, size);
90                         }
91                     } catch (IOException e) {
92                         // Connection broken
93                         connectionBroken = true;
94                         if (DBG) Log.d(TAG, "connection broken by IOException", e);
95                     }
96                 }
97 
98                 // Build NDEF message set from the stream
99                 NdefPushProtocol msg = new NdefPushProtocol(buffer.toByteArray());
100                 if (DBG) Log.d(TAG, "got message " + msg.toString());
101 
102                 // Send the intent for the fake tag
103                 mCallback.onMessageReceived(msg.getImmediate());
104             } catch (FormatException e) {
105                 Log.e(TAG, "badly formatted NDEF message, ignoring", e);
106             } finally {
107                 try {
108                     if (DBG) Log.d(TAG, "about to close");
109                     mSock.close();
110                 } catch (IOException e) {
111                     // ignore
112                 }
113             }
114             if (DBG) Log.d(TAG, "finished connection thread");
115         }
116     }
117 
118     /** Server class, used to listen for incoming connection request */
119     class ServerThread extends Thread {
120         // Variables below synchronized on NdefPushServer.this
121         boolean mRunning = true;
122         LlcpServerSocket mServerSocket;
123 
124         @Override
run()125         public void run() {
126             boolean threadRunning;
127             synchronized (NdefPushServer.this) {
128                 threadRunning = mRunning;
129             }
130             while (threadRunning) {
131                 if (DBG) Log.d(TAG, "about create LLCP service socket");
132                 try {
133                     synchronized (NdefPushServer.this) {
134                         mServerSocket = mService.createLlcpServerSocket(mSap, SERVICE_NAME,
135                                 MIU, 1, 1024);
136                     }
137                     if (mServerSocket == null) {
138                         if (DBG) Log.d(TAG, "failed to create LLCP service socket");
139                         return;
140                     }
141                     if (DBG) Log.d(TAG, "created LLCP service socket");
142                     synchronized (NdefPushServer.this) {
143                         threadRunning = mRunning;
144                     }
145 
146                     while (threadRunning) {
147                         LlcpServerSocket serverSocket;
148                         synchronized (NdefPushServer.this) {
149                             serverSocket = mServerSocket;
150                         }
151                         if (serverSocket == null) return;
152 
153                         if (DBG) Log.d(TAG, "about to accept");
154                         LlcpSocket communicationSocket = serverSocket.accept();
155                         if (DBG) Log.d(TAG, "accept returned " + communicationSocket);
156                         if (communicationSocket != null) {
157                             new ConnectionThread(communicationSocket).start();
158                         }
159 
160                         synchronized (NdefPushServer.this) {
161                             threadRunning = mRunning;
162                         }
163                     }
164                     if (DBG) Log.d(TAG, "stop running");
165                 } catch (LlcpException e) {
166                     Log.e(TAG, "llcp error", e);
167                 } catch (IOException e) {
168                     if (DBG) Log.d(TAG, "IO error");
169                 } finally {
170                     synchronized (NdefPushServer.this) {
171                         if (mServerSocket != null) {
172                             if (DBG) Log.d(TAG, "about to close");
173                             try {
174                                 mServerSocket.close();
175                             } catch (IOException e) {
176                                 // ignore
177                             }
178                             mServerSocket = null;
179                         }
180                     }
181                 }
182 
183                 synchronized (NdefPushServer.this) {
184                     threadRunning = mRunning;
185                 }
186             }
187         }
188 
shutdown()189         public void shutdown() {
190             synchronized (NdefPushServer.this) {
191                 mRunning = false;
192                 if (mServerSocket != null) {
193                     try {
194                         mServerSocket.close();
195                     } catch (IOException e) {
196                         // ignore
197                     }
198                     mServerSocket = null;
199                 }
200             }
201         }
202     }
203 
start()204     public void start() {
205         synchronized (this) {
206             if (DBG) Log.d(TAG, "start, thread = " + mServerThread);
207             if (mServerThread == null) {
208                 if (DBG) Log.d(TAG, "starting new server thread");
209                 mServerThread = new ServerThread();
210                 mServerThread.start();
211             }
212         }
213     }
214 
stop()215     public void stop() {
216         synchronized (this) {
217             if (DBG) Log.d(TAG, "stop, thread = " + mServerThread);
218             if (mServerThread != null) {
219                 if (DBG) Log.d(TAG, "shuting down server thread");
220                 mServerThread.shutdown();
221                 mServerThread = null;
222             }
223         }
224     }
225 }
226