@icedevml/react-native-host-card-emulation 0.9.1 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,53 +6,19 @@
6
6
 
7
7
  package com.itsecrnd.rtnhceandroid;
8
8
 
9
- import static com.facebook.react.jstasks.HeadlessJsTaskContext.Companion;
10
-
11
- import android.app.ActivityManager;
12
9
  import android.app.KeyguardManager;
13
10
  import android.content.Context;
14
11
  import android.nfc.cardemulation.HostApduService;
15
- import android.os.Build;
16
12
  import android.os.Bundle;
17
13
  import android.util.Log;
18
14
 
19
- import androidx.annotation.NonNull;
20
- import androidx.annotation.RequiresApi;
21
-
22
- import com.facebook.react.ReactApplication;
23
- import com.facebook.react.ReactHost;
24
- import com.facebook.react.bridge.Arguments;
25
- import com.facebook.react.bridge.ReactContext;
26
- import com.facebook.react.bridge.UiThreadUtil;
27
- import com.facebook.react.jstasks.HeadlessJsTaskConfig;
28
- import com.facebook.react.jstasks.HeadlessJsTaskContext;
29
- import com.facebook.react.jstasks.HeadlessJsTaskEventListener;
30
-
31
- import java.util.HashMap;
32
- import java.util.List;
33
- import java.util.Locale;
34
- import java.util.UUID;
35
-
36
- @RequiresApi(api = Build.VERSION_CODES.N)
37
- public class HCEService extends HostApduService implements HCEServiceCallback {
15
+ /**
16
+ * Thin adapter between the Android NFC stack and HCEManager, which holds all the state.
17
+ * None of the callbacks is allowed to throw, as that would crash the whole app process.
18
+ */
19
+ public class HCEService extends HostApduService {
38
20
  private static final String TAG = "HCEService";
39
21
 
40
- private boolean isForeground;
41
- private volatile boolean isDeactivated;
42
- private String backgroundSessionUUID;
43
- private RTNHCEAndroidModule hceModule;
44
- private byte[] pendingCAPDU;
45
- private volatile boolean needsResponse;
46
- private volatile Boolean reportedDeviceLocked;
47
- private Boolean pendingDeviceLocked;
48
- private final HashMap<String, Integer> taskSessionIdMap;
49
-
50
- public HCEService() {
51
- super();
52
-
53
- taskSessionIdMap = new HashMap<>();
54
- }
55
-
56
22
  private boolean isDeviceLocked() {
57
23
  /*
58
24
  The KeyguardManager has to be obtained lazily, as the service's context
@@ -62,278 +28,35 @@ public class HCEService extends HostApduService implements HCEServiceCallback {
62
28
  return keyguardManager != null && keyguardManager.isDeviceLocked();
63
29
  }
64
30
 
65
- private void reportDeviceLockState(boolean deviceLocked) {
66
- if (reportedDeviceLocked != null && reportedDeviceLocked == deviceLocked) {
67
- return;
68
- }
69
-
70
- reportedDeviceLocked = deviceLocked;
71
- final String type = deviceLocked ? "deviceLocked" : "deviceUnlocked";
72
- Log.d(TAG, "HCEService:reportDeviceLockState " + type);
73
-
74
- if (isForeground) {
75
- hceModule.sendEvent(type, "");
76
- } else {
77
- hceModule.sendBackgroundEvent(backgroundSessionUUID, type, "");
78
- }
79
- }
80
-
81
- private boolean isAppOnForeground(Context context) {
82
- /*
83
- We need to check if app is in foreground otherwise the app will crash.
84
- https://stackoverflow.com/questions/8489993/check-android-application-is-in-foreground-or-not
85
- */
86
- ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
87
- List<ActivityManager.RunningAppProcessInfo> appProcesses =
88
- activityManager.getRunningAppProcesses();
89
- if (appProcesses == null) {
90
- return false;
91
- }
92
- final String packageName = context.getPackageName();
93
- for (ActivityManager.RunningAppProcessInfo appProcess : appProcesses) {
94
- if (appProcess.importance ==
95
- ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND &&
96
- appProcess.processName.equals(packageName)) {
97
- return true;
98
- }
99
- }
100
- return false;
101
- }
102
-
103
- @Override
104
- public void onBackgroundHCEInit(String handle) {
105
- Log.d(TAG, "HCEService:onBackgroundHCEInit");
106
- Log.d(TAG, "HCEService:onBackgroundHCEInit:" + handle + ":" + backgroundSessionUUID);
107
-
108
- if (handle == null && backgroundSessionUUID != null) {
109
- Log.d(TAG, "HCEService:onBackgroundHCEInit foreground call to background session");
110
- throw new IllegalStateException();
111
- } else if (handle != null && !handle.equals(backgroundSessionUUID)) {
112
- Log.d(TAG, "HCEService:onBackgroundHCEInit mismatched background session handle");
113
- throw new IllegalStateException();
114
- }
115
-
116
- if (pendingCAPDU != null) {
117
- Log.d(TAG, "HCEService:onBackgroundHCEInit send pendingCAPDU");
118
-
119
- if (pendingDeviceLocked != null) {
120
- reportDeviceLockState(pendingDeviceLocked);
121
- pendingDeviceLocked = null;
122
- }
123
-
124
- hceModule.sendBackgroundEvent(backgroundSessionUUID, "received", BinaryUtils.ByteArrayToHexString(pendingCAPDU));
125
- pendingCAPDU = null;
126
- }
127
- }
128
-
129
- @Override
130
- public void onBackgroundHCEFinish(String handle) {
131
- Log.d(TAG, "HCEService:onBackgroundHCEFinish");
132
- Integer taskId = taskSessionIdMap.get(handle);
133
-
134
- if (taskId == null) {
135
- Log.d(TAG, "HCEService:onBackgroundHCEFinish unable to resolve taskId");
136
- throw new IllegalArgumentException();
137
- }
138
-
139
- ReactHost reactHost = ((ReactApplication) getApplication()).getReactHost();
140
-
141
- if (reactHost == null) {
142
- Log.d(TAG, "HCEService:onBackgroundHCEFinish getReactHost() returned null");
143
- throw new IllegalArgumentException();
144
- }
145
-
146
- ReactContext reactContext = reactHost.getCurrentReactContext();
147
-
148
- if (reactContext == null) {
149
- Log.d(TAG, "HCEService:onBackgroundHCEFinish getCurrentReactContext() returned null");
150
- throw new IllegalArgumentException();
151
- }
152
-
153
- HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
154
- headlessJsTaskContext.finishTask(taskId);
155
- taskSessionIdMap.remove(handle);
156
- stopSelf();
157
- }
158
-
159
- @Override
160
- public void onRespondAPDU(String handle, String rapdu) {
161
- Log.d(TAG, "HCEService:onRespondAPDU");
162
-
163
- if (handle == null && backgroundSessionUUID != null) {
164
- Log.d(TAG, "HCEService:onRespondAPDU foreground call to background session");
165
- throw new IllegalStateException();
166
- } else if (handle != null && !handle.equals(backgroundSessionUUID)) {
167
- Log.d(TAG, "HCEService:onRespondAPDU mismatched background session handle");
168
- throw new IllegalStateException();
169
- }
170
-
171
- if (!needsResponse) {
172
- Log.d(TAG, "HCEService:onRespondAPDU not waiting for a response");
173
- throw new IllegalStateException();
174
- }
175
-
176
- needsResponse = false;
177
- sendResponseApdu(BinaryUtils.HexStringToByteArray(rapdu));
178
- }
179
-
180
31
  @Override
181
32
  public byte[] processCommandApdu(byte[] command, Bundle extras) {
182
- Log.d(TAG, "HCEService:processCommandApdu");
183
-
184
- if (isDeactivated) {
185
- onCreate();
186
- }
187
-
188
- String capdu = BinaryUtils.ByteArrayToHexString(command).toUpperCase(Locale.ROOT);
189
-
190
- if (isForeground) {
191
- if (hceModule._isHCERunning() && hceModule.isHCEActiveConnection()) {
192
- Log.d(TAG, "HCEService:processCommandApdu foreground sendEvent received");
193
- reportDeviceLockState(isDeviceLocked());
194
- needsResponse = true;
195
- hceModule.sendEvent("received", capdu);
196
- } else {
197
- Log.d(TAG, "HCEService:processCommandApdu foreground respond 6999");
198
- return new byte[]{(byte) 0x69, (byte) 0x99};
199
- }
200
- } else {
201
- if (hceModule != null && hceModule.isHCEBackgroundHandlerReady()) {
202
- Log.d(TAG, "HCEService:processCommandApdu background sendBackgroundEvent received");
203
- reportDeviceLockState(isDeviceLocked());
204
- needsResponse = true;
205
- hceModule.sendBackgroundEvent(backgroundSessionUUID, "received", capdu);
206
- } else {
207
- Log.d(TAG, "HCEService:processCommandApdu background pendingCAPDU");
208
- needsResponse = true;
209
- pendingCAPDU = command;
210
- pendingDeviceLocked = isDeviceLocked();
211
- }
212
- }
213
-
214
- return null;
215
- }
216
-
217
- @Override
218
- public void onCreate() {
219
- Log.d(TAG, "HCEService:onCreate");
220
-
221
- ReactHost reactHost = ((ReactApplication) getApplication()).getReactHost();
222
-
223
- if (reactHost == null) {
224
- throw new RuntimeException("BUG! getReactHost() returned null.");
225
- }
226
-
227
- isForeground = isAppOnForeground(getApplicationContext());
228
- isDeactivated = false;
229
- pendingCAPDU = null;
230
- pendingDeviceLocked = null;
231
- hceModule = null;
232
- needsResponse = false;
233
- reportedDeviceLocked = null;
234
-
235
- if (isForeground) {
236
- Log.d(TAG, "HCEService:onCreate foreground");
237
- this.backgroundSessionUUID = null;
238
-
239
- ReactContext reactContext = reactHost.getCurrentReactContext();
240
-
241
- if (reactContext == null) {
242
- throw new RuntimeException("BUG! getCurrentReactContext() returned null in foreground.");
243
- }
244
-
245
- this.hceModule = ((RTNHCEAndroidModule) reactContext.getNativeModule("NativeHCEModule"));
246
-
247
- if (this.hceModule == null) {
248
- throw new RuntimeException("BUG! getNativeModule() returned null in foreground.");
249
- }
250
-
251
- this.hceModule.setHCEService(this);
252
-
253
- if (this.hceModule._isHCERunning()) {
254
- this.hceModule.sendEvent("readerDetected", "");
255
- } else {
256
- this.hceModule.setHCEBrokenConnection();
257
- }
258
- } else {
259
- ReactContext reactContext = reactHost.getCurrentReactContext();
260
- Log.d(TAG, "HCEService:onCreate background");
261
- final String useSessID = UUID.randomUUID().toString();
262
- backgroundSessionUUID = useSessID;
263
-
264
- if (reactContext == null) {
265
- reactHost.addReactInstanceEventListener(evReactContext -> {
266
- HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(evReactContext);
267
- headlessJsTaskContext.addTaskEventListener(new HeadlessJsTaskEventListener() {
268
- @Override
269
- public void onHeadlessJsTaskStart(int i) {
270
- Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskStart: " + i);
271
- }
272
-
273
- @Override
274
- public void onHeadlessJsTaskFinish(int i) {
275
- Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskFinish: " + i);
276
- }
277
- });
278
-
279
- setupRunJSTask(evReactContext, useSessID);
280
- });
281
- reactHost.start();
282
- } else {
283
- setupRunJSTask(reactContext, useSessID);
284
- }
33
+ try {
34
+ // binder call, so it's done before entering HCEManager's critical section
35
+ boolean deviceLocked = isDeviceLocked();
36
+ return HCEManager.getInstance().onCommand(this, command, deviceLocked);
37
+ } catch (RuntimeException e) {
38
+ Log.e(TAG, "processCommandApdu failed", e);
39
+ return HCEManager.SW_NO_PRECISE_DIAGNOSIS.clone();
285
40
  }
286
41
  }
287
42
 
288
43
  @Override
289
44
  public void onDeactivated(int reason) {
290
- Log.d(TAG, "HCEService:onDeactivated: " + reason);
291
- needsResponse = false;
292
- isDeactivated = true;
293
-
294
- if (isForeground) {
295
- if (this.hceModule != null && this.hceModule.isHCEActiveConnection()) {
296
- this.hceModule.sendEvent("readerDeselected", "");
297
- }
298
- } else if (backgroundSessionUUID != null) {
299
- String prevBackgroundSessionUUID = backgroundSessionUUID;
300
- backgroundSessionUUID = null;
301
-
302
- if (this.hceModule != null) {
303
- this.hceModule.sendBackgroundEvent(prevBackgroundSessionUUID, "readerDeselected", "");
304
- }
305
- }
306
- }
307
-
308
- public void setupRunJSTask(final @NonNull ReactContext reactContext, final String useSessUUID) {
309
- hceModule = (RTNHCEAndroidModule) reactContext.getNativeModule("NativeHCEModule");
310
-
311
- if (hceModule == null) {
312
- throw new RuntimeException("BUG! getNativeModule() returned null in background.");
45
+ try {
46
+ HCEManager.getInstance().onDeactivated(this, reason);
47
+ } catch (RuntimeException e) {
48
+ Log.e(TAG, "onDeactivated failed", e);
313
49
  }
314
-
315
- hceModule.setHCEService(this);
316
-
317
- UiThreadUtil.runOnUiThread(() -> {
318
- Log.d(TAG, "HCEService:setupRunJSTask:runOnUiThread startTask");
319
- Bundle argBundle = new Bundle();
320
- argBundle.putString("handle", useSessUUID);
321
- HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
322
- int taskId = headlessJsTaskContext.startTask(
323
- new HeadlessJsTaskConfig(
324
- "handleBackgroundHCECall",
325
- Arguments.fromBundle(argBundle),
326
- 15000,
327
- false // not allowed in foreground
328
- ));
329
- taskSessionIdMap.put(useSessUUID, taskId);
330
- });
331
50
  }
332
51
 
333
52
  @Override
334
53
  public void onDestroy() {
335
- super.onDestroy();
54
+ try {
55
+ HCEManager.getInstance().onServiceDestroyed(this);
56
+ } catch (RuntimeException e) {
57
+ Log.e(TAG, "onDestroy failed", e);
58
+ }
336
59
 
337
- Log.d(TAG, "HCEService:onDestroy");
60
+ super.onDestroy();
338
61
  }
339
62
  }