1/*
2 * Copyright (C) 2015 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.nfc.cardemulation;
18
19import android.content.ComponentName;
20import android.content.Context;
21import android.content.Intent;
22import android.content.ServiceConnection;
23import android.nfc.cardemulation.NfcFServiceInfo;
24import android.nfc.cardemulation.HostNfcFService;
25import android.os.Bundle;
26import android.os.Handler;
27import android.os.IBinder;
28import android.os.Message;
29import android.os.Messenger;
30import android.os.RemoteException;
31import android.os.UserHandle;
32import android.util.Log;
33
34import com.android.nfc.NfcService;
35
36import java.io.FileDescriptor;
37import java.io.PrintWriter;
38
39public class HostNfcFEmulationManager {
40    static final String TAG = "HostNfcFEmulationManager";
41    static final boolean DBG = false;
42
43    static final int STATE_IDLE = 0;
44    static final int STATE_W4_SERVICE = 1;
45    static final int STATE_XFER = 2;
46
47    /** NFCID2 length */
48    static final int NFCID2_LENGTH = 8;
49
50    /** Minimum NFC-F packets including length, command code and NFCID2 */
51    static final int MINIMUM_NFCF_PACKET_LENGTH = 10;
52
53    final Context mContext;
54    final RegisteredT3tIdentifiersCache mT3tIdentifiersCache;
55    final Messenger mMessenger = new Messenger (new MessageHandler());
56    final Object mLock;
57
58    // All variables below protected by mLock
59    ComponentName mEnabledFgServiceName;
60
61    Messenger mService;
62    boolean mServiceBound;
63    ComponentName mServiceName;
64
65    // mActiveService denotes the service interface
66    // that is the current active one, until a new packet
67    // comes in that may be resolved to a different service.
68    // On deactivation, mActiveService stops being valid.
69    Messenger mActiveService;
70    ComponentName mActiveServiceName;
71
72    int mState;
73    byte[] mPendingPacket;
74
75    public HostNfcFEmulationManager(Context context,
76            RegisteredT3tIdentifiersCache t3tIdentifiersCache) {
77        mContext = context;
78        mLock = new Object();
79        mEnabledFgServiceName = null;
80        mT3tIdentifiersCache = t3tIdentifiersCache;
81        mState = STATE_IDLE;
82    }
83
84    public void onEnabledForegroundNfcFServiceChanged(ComponentName service) {
85        synchronized (mLock) {
86            mEnabledFgServiceName = service;
87            if (service == null) {
88                sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
89                unbindServiceIfNeededLocked();
90            }
91        }
92    }
93
94    public void onHostEmulationActivated() {
95        if (DBG) Log.d(TAG, "notifyHostEmulationActivated");
96    }
97
98    public void onHostEmulationData(byte[] data) {
99        if (DBG) Log.d(TAG, "notifyHostEmulationData");
100        String nfcid2 = findNfcid2(data);
101        ComponentName resolvedServiceName = null;
102        synchronized (mLock) {
103            if (nfcid2 != null) {
104                NfcFServiceInfo resolvedService = mT3tIdentifiersCache.resolveNfcid2(nfcid2);
105                if (resolvedService != null) {
106                    resolvedServiceName = resolvedService.getComponent();
107                }
108            }
109            if (resolvedServiceName == null) {
110                if (mActiveServiceName == null) {
111                    return;
112                }
113                resolvedServiceName = mActiveServiceName;
114            }
115            // Check if resolvedService is actually currently enabled
116            if (mEnabledFgServiceName == null ||
117                    !mEnabledFgServiceName.equals(resolvedServiceName)) {
118                return;
119            }
120            if (DBG) Log.d(TAG, "resolvedServiceName: " + resolvedServiceName.toString() +
121                    "mState: " + String.valueOf(mState));
122            switch (mState) {
123            case STATE_IDLE:
124                Messenger existingService = bindServiceIfNeededLocked(resolvedServiceName);
125                if (existingService != null) {
126                    Log.d(TAG, "Binding to existing service");
127                    mState = STATE_XFER;
128                    sendDataToServiceLocked(existingService, data);
129                } else {
130                    // Waiting for service to be bound
131                    Log.d(TAG, "Waiting for new service.");
132                    // Queue packet to be used
133                    mPendingPacket = data;
134                    mState = STATE_W4_SERVICE;
135                }
136                break;
137            case STATE_W4_SERVICE:
138                Log.d(TAG, "Unexpected packet in STATE_W4_SERVICE");
139                break;
140            case STATE_XFER:
141                // Regular packet data
142                sendDataToServiceLocked(mActiveService, data);
143                break;
144            }
145        }
146    }
147
148    public void onHostEmulationDeactivated() {
149        if (DBG) Log.d(TAG, "notifyHostEmulationDeactivated");
150        synchronized (mLock) {
151            sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
152            mActiveService = null;
153            mActiveServiceName = null;
154            unbindServiceIfNeededLocked();
155            mState = STATE_IDLE;
156        }
157    }
158
159    public void onNfcDisabled() {
160        synchronized (mLock) {
161            sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
162            mEnabledFgServiceName = null;
163            mActiveService = null;
164            mActiveServiceName = null;
165            unbindServiceIfNeededLocked();
166            mState = STATE_IDLE;
167        }
168    }
169
170    public void onUserSwitched() {
171        synchronized (mLock) {
172            sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
173            mEnabledFgServiceName = null;
174            mActiveService = null;
175            mActiveServiceName = null;
176            unbindServiceIfNeededLocked();
177            mState = STATE_IDLE;
178        }
179    }
180
181    void sendDataToServiceLocked(Messenger service, byte[] data) {
182        if (DBG) Log.d(TAG, "sendDataToServiceLocked");
183        if (DBG) {
184            Log.d(TAG, "service: " +
185                    (service != null ? service.toString() : "null"));
186            Log.d(TAG, "mActiveService: " +
187                    (mActiveService != null ? mActiveService.toString() : "null"));
188        }
189        if (service != mActiveService) {
190            sendDeactivateToActiveServiceLocked(HostNfcFService.DEACTIVATION_LINK_LOSS);
191            mActiveService = service;
192            mActiveServiceName = mServiceName;
193        }
194        Message msg = Message.obtain(null, HostNfcFService.MSG_COMMAND_PACKET);
195        Bundle dataBundle = new Bundle();
196        dataBundle.putByteArray("data", data);
197        msg.setData(dataBundle);
198        msg.replyTo = mMessenger;
199        try {
200            Log.d(TAG, "Sending data to service");
201            if (DBG) Log.d(TAG, "data: " + getByteDump(data));
202            mActiveService.send(msg);
203        } catch (RemoteException e) {
204            Log.e(TAG, "Remote service has died, dropping packet");
205        }
206    }
207
208    void sendDeactivateToActiveServiceLocked(int reason) {
209        if (DBG) Log.d(TAG, "sendDeactivateToActiveServiceLocked");
210        if (mActiveService == null) return;
211        Message msg = Message.obtain(null, HostNfcFService.MSG_DEACTIVATED);
212        msg.arg1 = reason;
213        try {
214            mActiveService.send(msg);
215        } catch (RemoteException e) {
216            // Don't care
217        }
218    }
219
220    Messenger bindServiceIfNeededLocked(ComponentName service) {
221        if (DBG) Log.d(TAG, "bindServiceIfNeededLocked");
222        if (mServiceBound && mServiceName.equals(service)) {
223            Log.d(TAG, "Service already bound.");
224            return mService;
225        } else {
226            Log.d(TAG, "Binding to service " + service);
227            unbindServiceIfNeededLocked();
228            Intent bindIntent = new Intent(HostNfcFService.SERVICE_INTERFACE);
229            bindIntent.setComponent(service);
230            if (mContext.bindServiceAsUser(bindIntent, mConnection,
231                    Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {
232            } else {
233                Log.e(TAG, "Could not bind service.");
234            }
235            return null;
236        }
237    }
238
239    void unbindServiceIfNeededLocked() {
240        if (DBG) Log.d(TAG, "unbindServiceIfNeededLocked");
241        if (mServiceBound) {
242            Log.d(TAG, "Unbinding from service " + mServiceName);
243            mContext.unbindService(mConnection);
244            mServiceBound = false;
245            mService = null;
246            mServiceName = null;
247        }
248    }
249
250    String findNfcid2(byte[] data) {
251        if (DBG) Log.d(TAG, "findNfcid2");
252        if (data == null || data.length < MINIMUM_NFCF_PACKET_LENGTH) {
253            if (DBG) Log.d(TAG, "Data size too small");
254            return null;
255        }
256        int nfcid2Offset = 2;
257        return bytesToString(data, nfcid2Offset, NFCID2_LENGTH);
258    }
259
260    private ServiceConnection mConnection = new ServiceConnection() {
261        @Override
262        public void onServiceConnected(ComponentName name, IBinder service) {
263            synchronized (mLock) {
264                mService = new Messenger(service);
265                mServiceBound = true;
266                mServiceName = name;
267                Log.d(TAG, "Service bound");
268                mState = STATE_XFER;
269                // Send pending packet
270                if (mPendingPacket != null) {
271                    sendDataToServiceLocked(mService, mPendingPacket);
272                    mPendingPacket = null;
273                }
274            }
275        }
276
277        @Override
278        public void onServiceDisconnected(ComponentName name) {
279            synchronized (mLock) {
280                Log.d(TAG, "Service unbound");
281                mService = null;
282                mServiceBound = false;
283                mServiceName = null;
284            }
285        }
286    };
287
288    class MessageHandler extends Handler {
289        @Override
290        public void handleMessage(Message msg) {
291            synchronized(mLock) {
292                if (mActiveService == null) {
293                    Log.d(TAG, "Dropping service response message; service no longer active.");
294                    return;
295                } else if (!msg.replyTo.getBinder().equals(mActiveService.getBinder())) {
296                    Log.d(TAG, "Dropping service response message; service no longer bound.");
297                    return;
298                }
299            }
300            if (msg.what == HostNfcFService.MSG_RESPONSE_PACKET) {
301                Bundle dataBundle = msg.getData();
302                if (dataBundle == null) {
303                    return;
304                }
305                byte[] data = dataBundle.getByteArray("data");
306                if (data == null) {
307                    return;
308                }
309                if (data.length == 0) {
310                    Log.e(TAG, "Invalid response packet");
311                    return;
312                }
313                if (data.length != (data[0] & 0xff)) {
314                    Log.e(TAG, "Invalid response packet");
315                    return;
316                }
317                int state;
318                synchronized(mLock) {
319                    state = mState;
320                }
321                if (state == STATE_XFER) {
322                    Log.d(TAG, "Sending data");
323                    if (DBG) Log.d(TAG, "data:" + getByteDump(data));
324                    NfcService.getInstance().sendData(data);
325                } else {
326                    Log.d(TAG, "Dropping data, wrong state " + Integer.toString(state));
327                }
328            }
329        }
330    }
331
332    static String bytesToString(byte[] bytes, int offset, int length) {
333        final char[] hexChars = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F'};
334        char[] chars = new char[length * 2];
335        int byteValue;
336        for (int j = 0; j < length; j++) {
337            byteValue = bytes[offset + j] & 0xFF;
338            chars[j * 2] = hexChars[byteValue >>> 4];
339            chars[j * 2 + 1] = hexChars[byteValue & 0x0F];
340        }
341        return new String(chars);
342    }
343
344    private String getByteDump(final byte[] cmd) {
345        StringBuffer str = new StringBuffer("");
346        int letters = 8;
347        int i = 0;
348
349        if (cmd == null) {
350            str.append(" null\n");
351            return str.toString();
352        }
353
354        for (; i < cmd.length; i++) {
355            str.append(String.format(" %02X", cmd[i]));
356            if ((i % letters == letters - 1) || (i + 1 == cmd.length)) {
357                str.append("\n");
358            }
359        }
360
361        return str.toString();
362    }
363
364    public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
365        pw.println("Bound HCE-F services: ");
366        if (mServiceBound) {
367            pw.println("    service: " + mServiceName);
368        }
369    }
370}
371