1 /*
2  * Copyright (C) 2014 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 package com.android.nfc.service;
17 
18 import android.content.ComponentName;
19 import android.content.Intent;
20 import android.nfc.cardemulation.HostApduService;
21 import android.os.Bundle;
22 import android.util.Log;
23 
24 import com.android.nfc.utils.HceUtils;
25 import com.android.nfc.utils.CommandApdu;
26 
27 import java.util.Arrays;
28 
29 public abstract class HceService extends HostApduService {
30     private static final String TAG = "HceService";
31 
32     // Intent action that's received when complete APDU sequence is received from an HCE service.
33     public static final String ACTION_APDU_SEQUENCE_COMPLETE =
34             "com.android.nfc.service.ACTION_APDU_SEQUENCE_COMPLETE";
35     public static final String EXTRA_COMPONENT = "component";
36     public static final String EXTRA_DURATION = "duration";
37 
38     private static final int STATE_IDLE = 0;
39     private static final int STATE_IN_PROGRESS = 1;
40     private static final int STATE_FAILED = 2;
41 
42     // Variables below only used on main thread
43     CommandApdu[] mCommandApdus = null;
44     String[] mResponseApdus = null;
45     int mApduIndex = 0;
46     int mState = STATE_IDLE;
47     long mStartTime;
48 
getComponent()49     public abstract ComponentName getComponent();
50 
51     /**
52      * Initializes the service
53      *
54      * @param commandApdus - list of expected command APDUs
55      * @param responseApdus - corresponding list of response APDUs to send
56      */
HceService(CommandApdu[] commandApdus, String[] responseApdus)57     public HceService(CommandApdu[] commandApdus, String[] responseApdus) {
58         mCommandApdus = commandApdus;
59         mResponseApdus = responseApdus;
60     }
61 
62     /** Called when service is deactivated */
63     @Override
onDeactivated(int arg0)64     public void onDeactivated(int arg0) {
65         Log.d(TAG, "onDeactivated");
66         mApduIndex = 0;
67         mState = STATE_IDLE;
68     }
69 
70     /** Callback when entire apdu sequence is successfully completed. */
onApduSequenceComplete()71     public void onApduSequenceComplete() {
72         Intent completionIntent = new Intent(ACTION_APDU_SEQUENCE_COMPLETE);
73         completionIntent.putExtra(EXTRA_COMPONENT, getComponent());
74         completionIntent.putExtra(EXTRA_DURATION, System.currentTimeMillis() - mStartTime);
75         sendBroadcast(completionIntent);
76         Log.d(TAG, "Successful APDU sequence. Sent broadcast");
77     }
78 
79     /**
80      * Implementation of processCommandApdu. Verifies correct APDU command is received and sends
81      * response. Triggers onApduSequenceComplete if all APDUs are received.
82      */
83     @Override
processCommandApdu(byte[] arg0, Bundle arg1)84     public byte[] processCommandApdu(byte[] arg0, Bundle arg1) {
85         Log.d(TAG, "processCommandApdu called");
86         if (mState == STATE_FAILED) {
87             // Don't accept any more APDUs until deactivated
88             return null;
89         }
90 
91         if (mState == STATE_IDLE) {
92             mState = STATE_IN_PROGRESS;
93             mStartTime = System.currentTimeMillis();
94         }
95 
96         if (mApduIndex >= mCommandApdus.length) {
97             // Skip all APDUs which aren't supposed to reach us
98             return null;
99         }
100 
101         do {
102             if (!mCommandApdus[mApduIndex].isReachable()) {
103                 mApduIndex++;
104             } else {
105                 break;
106             }
107         } while (mApduIndex < mCommandApdus.length);
108 
109         if (mApduIndex >= mCommandApdus.length) {
110             Log.d(TAG, "Ignoring command APDU; protocol complete.");
111             // Ignore new APDUs after completion
112             return null;
113         } else {
114 
115             if (!Arrays.equals(
116                     HceUtils.hexStringToBytes(mCommandApdus[mApduIndex].getApdu()), arg0)) {
117                 Log.d(TAG, "Unexpected command APDU: " + HceUtils.getHexBytes("", arg0));
118                 return null;
119             } else {
120                 // Send corresponding response APDU
121                 byte[] responseApdu = HceUtils.hexStringToBytes(mResponseApdus[mApduIndex]);
122                 mApduIndex++;
123                 if (mApduIndex == mCommandApdus.length) {
124                     onApduSequenceComplete();
125                 }
126                 return responseApdu;
127             }
128         }
129     }
130 }
131