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.CommandApdu;
25 import com.android.nfc.utils.HceUtils;
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     protected static final int STATE_IDLE = 0;
39     protected static final int STATE_IN_PROGRESS = 1;
40     protected 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: " + HceUtils.getHexBytes("", arg0));
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(
118                         TAG,
119                         "Unexpected command APDU. Got: "
120                                 + HceUtils.getHexBytes("", arg0)
121                                 + ", "
122                                 + "expected: "
123                                 + mCommandApdus[mApduIndex].getApdu());
124                 return null;
125             } else {
126                 // Send corresponding response APDU
127                 byte[] responseApdu = HceUtils.hexStringToBytes(mResponseApdus[mApduIndex]);
128                 mApduIndex++;
129                 if (mApduIndex == mCommandApdus.length) {
130                     onApduSequenceComplete();
131                 }
132                 return responseApdu;
133             }
134         }
135     }
136 }
137