Skip to content

Commit 58eb519

Browse files
authored
Fix bugs with background HCE on Android (#48)
1 parent 8a1ed18 commit 58eb519

File tree

5 files changed

+119
-58
lines changed

5 files changed

+119
-58
lines changed

packages/host-card-emulation/android/src/main/java/com/itsecrnd/rtnhceandroid/HCEService.java

Lines changed: 81 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -20,27 +20,34 @@
2020

2121
import com.facebook.react.ReactApplication;
2222
import com.facebook.react.ReactHost;
23-
import com.facebook.react.ReactInstanceEventListener;
2423
import com.facebook.react.bridge.Arguments;
2524
import com.facebook.react.bridge.ReactContext;
2625
import com.facebook.react.bridge.UiThreadUtil;
2726
import com.facebook.react.jstasks.HeadlessJsTaskConfig;
2827
import com.facebook.react.jstasks.HeadlessJsTaskContext;
2928
import com.facebook.react.jstasks.HeadlessJsTaskEventListener;
3029

30+
import java.util.HashMap;
3131
import java.util.List;
3232
import java.util.Locale;
3333
import java.util.UUID;
3434

3535
@RequiresApi(api = Build.VERSION_CODES.TIRAMISU)
36-
public class HCEService extends HostApduService implements ReactInstanceEventListener, HCEServiceCallback {
36+
public class HCEService extends HostApduService implements HCEServiceCallback {
3737
private static final String TAG = "HCEService";
3838

3939
private boolean isForeground;
4040
private String backgroundSessionUUID;
4141
private RTNHCEAndroidModule hceModule;
4242
private byte[] pendingCAPDU;
4343
private volatile boolean needsResponse;
44+
private final HashMap<String, Integer> taskSessionIdMap;
45+
46+
public HCEService() {
47+
super();
48+
49+
taskSessionIdMap = new HashMap<>();
50+
}
4451

4552
private boolean isAppOnForeground(Context context) {
4653
/*
@@ -67,6 +74,7 @@ private boolean isAppOnForeground(Context context) {
6774
@Override
6875
public void onBackgroundHCEInit(String handle) {
6976
Log.d(TAG, "HCEService:onBackgroundHCEInit");
77+
Log.d(TAG, "HCEService:onBackgroundHCEInit:" + handle + ":" + backgroundSessionUUID);
7078

7179
if (handle == null && backgroundSessionUUID != null) {
7280
Log.d(TAG, "HCEService:onBackgroundHCEInit foreground call to background session");
@@ -83,6 +91,35 @@ public void onBackgroundHCEInit(String handle) {
8391
}
8492
}
8593

94+
@Override
95+
public void onBackgroundHCEFinish(String handle) {
96+
Log.d(TAG, "HCEService:onBackgroundHCEFinish");
97+
Integer taskId = taskSessionIdMap.get(handle);
98+
99+
if (taskId == null) {
100+
Log.d(TAG, "HCEService:onBackgroundHCEFinish unable to resolve taskId");
101+
throw new IllegalArgumentException();
102+
}
103+
104+
ReactHost reactHost = ((ReactApplication) getApplication()).getReactHost();
105+
106+
if (reactHost == null) {
107+
Log.d(TAG, "HCEService:onBackgroundHCEFinish getReactHost() returned null");
108+
throw new IllegalArgumentException();
109+
}
110+
111+
ReactContext reactContext = reactHost.getCurrentReactContext();
112+
113+
if (reactContext == null) {
114+
Log.d(TAG, "HCEService:onBackgroundHCEFinish getCurrentReactContext() returned null");
115+
throw new IllegalArgumentException();
116+
}
117+
118+
HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
119+
headlessJsTaskContext.finishTask(taskId);
120+
taskSessionIdMap.remove(handle);
121+
}
122+
86123
@Override
87124
public void onRespondAPDU(String handle, String rapdu) {
88125
Log.d(TAG, "HCEService:onRespondAPDU");
@@ -110,13 +147,13 @@ public byte[] processCommandApdu(byte[] command, Bundle extras) {
110147
String capdu = BinaryUtils.ByteArrayToHexString(command).toUpperCase(Locale.ROOT);
111148

112149
if (isForeground) {
113-
if (hceModule._isHCERunning() && !hceModule.isHCEBrokenConnection()) {
150+
if (hceModule._isHCERunning() && hceModule.isHCEActiveConnection()) {
114151
Log.d(TAG, "HCEService:processCommandApdu foreground sendEvent received");
115152
needsResponse = true;
116153
hceModule.sendEvent("received", capdu);
117154
} else {
118155
Log.d(TAG, "HCEService:processCommandApdu foreground respond 6999");
119-
return new byte[] { (byte) 0x69, (byte) 0x99 };
156+
return new byte[]{(byte) 0x69, (byte) 0x99};
120157
}
121158
} else {
122159
if (hceModule != null && hceModule.isHCEBackgroundHandlerReady()) {
@@ -174,13 +211,29 @@ public void onCreate() {
174211
} else {
175212
ReactContext reactContext = reactHost.getCurrentReactContext();
176213
Log.d(TAG, "HCEService:onCreate background");
177-
this.backgroundSessionUUID = UUID.randomUUID().toString();
214+
final String useSessID = UUID.randomUUID().toString();
215+
backgroundSessionUUID = useSessID;
178216

179217
if (reactContext == null) {
180-
reactHost.addReactInstanceEventListener(this);
218+
reactHost.addReactInstanceEventListener(evReactContext -> {
219+
HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(evReactContext);
220+
headlessJsTaskContext.addTaskEventListener(new HeadlessJsTaskEventListener() {
221+
@Override
222+
public void onHeadlessJsTaskStart(int i) {
223+
Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskStart: " + i);
224+
}
225+
226+
@Override
227+
public void onHeadlessJsTaskFinish(int i) {
228+
Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskFinish: " + i);
229+
}
230+
});
231+
232+
setupRunJSTask(evReactContext, useSessID);
233+
});
181234
reactHost.start();
182235
} else {
183-
setupRunJSTask(reactContext);
236+
setupRunJSTask(reactContext, useSessID);
184237
}
185238
}
186239
}
@@ -191,37 +244,20 @@ public void onDeactivated(int reason) {
191244
needsResponse = false;
192245

193246
if (isForeground) {
194-
if (this.hceModule != null && !this.hceModule.isHCEBrokenConnection()) {
247+
if (this.hceModule != null && this.hceModule.isHCEActiveConnection()) {
195248
this.hceModule.sendEvent("readerDeselected", "");
196249
}
197-
} else {
198-
this.hceModule.sendBackgroundEvent(backgroundSessionUUID, "readerDeselected", "");
199-
}
250+
} else if (backgroundSessionUUID != null) {
251+
String prevBackgroundSessionUUID = backgroundSessionUUID;
252+
backgroundSessionUUID = null;
200253

201-
if (this.hceModule != null) {
202-
this.hceModule.setHCEService(null);
203-
}
204-
}
205-
206-
@Override
207-
public void onReactContextInitialized(@NonNull ReactContext reactContext) {
208-
HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
209-
headlessJsTaskContext.addTaskEventListener(new HeadlessJsTaskEventListener() {
210-
@Override
211-
public void onHeadlessJsTaskStart(int i) {
212-
Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskStart: " + i);
213-
}
214-
215-
@Override
216-
public void onHeadlessJsTaskFinish(int i) {
217-
Log.d(TAG, "HCEService:HeadlessJsTaskEventListener:onHeadlessJsTaskFinish: " + i);
254+
if (this.hceModule != null) {
255+
this.hceModule.sendBackgroundEvent(prevBackgroundSessionUUID, "readerDeselected", "");
218256
}
219-
});
220-
221-
setupRunJSTask(reactContext);
257+
}
222258
}
223259

224-
public void setupRunJSTask(@NonNull ReactContext reactContext) {
260+
public void setupRunJSTask(final @NonNull ReactContext reactContext, final String useSessUUID) {
225261
hceModule = (RTNHCEAndroidModule) reactContext.getNativeModule("NativeHCEModule");
226262

227263
if (hceModule == null) {
@@ -230,21 +266,19 @@ public void setupRunJSTask(@NonNull ReactContext reactContext) {
230266

231267
hceModule.setHCEService(this);
232268

233-
UiThreadUtil.runOnUiThread(new Runnable() {
234-
@Override
235-
public void run() {
236-
Log.d(TAG, "HCEService:setupRunJSTask:runOnUiThread startTask");
237-
Bundle argBundle = new Bundle();
238-
argBundle.putString("handle", backgroundSessionUUID);
239-
HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
240-
headlessJsTaskContext.startTask(
241-
new HeadlessJsTaskConfig(
242-
"handleBackgroundHCECall",
243-
Arguments.fromBundle(argBundle),
244-
15000,
245-
false // not allowed in foreground
246-
));
247-
}
269+
UiThreadUtil.runOnUiThread(() -> {
270+
Log.d(TAG, "HCEService:setupRunJSTask:runOnUiThread startTask");
271+
Bundle argBundle = new Bundle();
272+
argBundle.putString("handle", useSessUUID);
273+
HeadlessJsTaskContext headlessJsTaskContext = Companion.getInstance(reactContext);
274+
int taskId = headlessJsTaskContext.startTask(
275+
new HeadlessJsTaskConfig(
276+
"handleBackgroundHCECall",
277+
Arguments.fromBundle(argBundle),
278+
15000,
279+
false // not allowed in foreground
280+
));
281+
taskSessionIdMap.put(useSessUUID, taskId);
248282
});
249283
}
250284

packages/host-card-emulation/android/src/main/java/com/itsecrnd/rtnhceandroid/HCEServiceCallback.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@
22

33
public interface HCEServiceCallback {
44
void onBackgroundHCEInit(String handle);
5+
void onBackgroundHCEFinish(String handle);
56
void onRespondAPDU(String handle, String rapdu);
67
}

packages/host-card-emulation/android/src/main/java/com/itsecrnd/rtnhceandroid/RTNHCEAndroidModule.java

Lines changed: 34 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,29 @@ public String getName() {
5151

5252
void sendEvent(final String type, final String arg) {
5353
Log.d(TAG, "RTNHCEAndroidModule:sendEvent");
54-
WritableMap map = Arguments.createMap();
55-
map.putString("type", type);
56-
map.putString("arg", arg);
57-
emitOnEvent(map);
54+
55+
if (this.mEventEmitterCallback != null) {
56+
WritableMap map = Arguments.createMap();
57+
map.putString("type", type);
58+
map.putString("arg", arg);
59+
emitOnEvent(map);
60+
} else {
61+
Log.d(TAG, "RTNHCEAndroidModule:sendEvent missing event emitter, ignore");
62+
}
5863
}
5964

6065
void sendBackgroundEvent(final String audience, final String type, final String arg) {
6166
Log.d(TAG, "RTNHCEAndroidModule:sendBackgroundEvent");
62-
WritableMap map = Arguments.createMap();
63-
map.putString("audience", audience);
64-
map.putString("type", type);
65-
map.putString("arg", arg);
66-
emitOnBackgroundEvent(map);
67+
68+
if (this.mEventEmitterCallback != null) {
69+
WritableMap map = Arguments.createMap();
70+
map.putString("audience", audience);
71+
map.putString("type", type);
72+
map.putString("arg", arg);
73+
emitOnBackgroundEvent(map);
74+
} else {
75+
Log.d(TAG, "RTNHCEAndroidModule:sendBackgroundEvent missing event emitter, ignore");
76+
}
6777
}
6878

6979
boolean _isHCERunning() {
@@ -78,8 +88,8 @@ boolean isHCEBackgroundHandlerReady() {
7888
return this.hceBackgroundReady;
7989
}
8090

81-
boolean isHCEBrokenConnection() {
82-
return this.hceBrokenConnection;
91+
boolean isHCEActiveConnection() {
92+
return !this.hceBrokenConnection;
8393
}
8494

8595
void setHCEService(HCEServiceCallback serviceCallback) {
@@ -171,6 +181,19 @@ public boolean beginBackgroundHCE(String handle) {
171181
return true;
172182
}
173183

184+
@Override
185+
public boolean finishBackgroundHCE(String handle) {
186+
Log.d(TAG, "RTNHCEAndroidModule:finishBackgroundHCE");
187+
188+
try {
189+
this.serviceCb.onBackgroundHCEFinish(handle);
190+
} catch (IllegalStateException e) {
191+
return false;
192+
}
193+
194+
return true;
195+
}
196+
174197
@Override
175198
public void startHCE(Promise promise) {
176199
Log.d(TAG, "RTNHCEAndroidModule:startHCE");

packages/host-card-emulation/js/NativeHCEModule.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ export interface Spec extends TurboModule {
7575
isSessionRunning(): boolean;
7676

7777
beginBackgroundHCE(handle: string): boolean;
78+
finishBackgroundHCE(handle: string): boolean;
7879

7980
/**
8081
* iOS: Start the card emulation.

packages/host-card-emulation/js/hceBackground.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,12 +32,14 @@ export const createBackgroundHCE = (handle: string) => {
3232
} finally {
3333
if (event.type === "readerDeselected") {
3434
subscription.remove();
35+
NativeHCEModule.finishBackgroundHCE(handle);
3536
}
3637
}
3738
});
3839

3940
if (!NativeHCEModule.beginBackgroundHCE(handle)) {
4041
subscription.remove();
42+
NativeHCEModule.finishBackgroundHCE(handle);
4143
}
4244
}
4345
}

0 commit comments

Comments
 (0)