Skip to content

Commit 6b93d6f

Browse files
committed
Merge branch 'goservice-android'
2 parents 4715e57 + 93ad11b commit 6b93d6f

File tree

9 files changed

+177
-19
lines changed

9 files changed

+177
-19
lines changed

frontends/android/BitBoxApp/app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@
1111
<uses-feature android:name="android.hardware.camera" android:required="false" />
1212
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
1313

14+
<!-- for Go backend service -->
15+
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
16+
1417
<!-- to allow external links and mailto -->
1518
<queries>
1619
<intent>
@@ -62,5 +65,6 @@
6265
<meta-data android:name="android.hardware.usb.action.USB_DEVICE_ATTACHED"
6366
android:resource="@xml/device_filter" />
6467
</activity>
68+
<service android:name=".GoService" />
6569
</application>
6670
</manifest>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
package ch.shiftcrypto.bitboxapp;
2+
3+
import android.app.Notification;
4+
import android.app.NotificationChannel;
5+
import android.app.NotificationManager;
6+
import android.app.PendingIntent;
7+
import android.app.Service;
8+
import android.content.Intent;
9+
import android.graphics.drawable.Icon;
10+
import android.os.Binder;
11+
import android.os.Build;
12+
import android.os.IBinder;
13+
14+
import androidx.core.app.NotificationCompat;
15+
16+
import java.util.concurrent.locks.Lock;
17+
import java.util.concurrent.locks.ReentrantLock;
18+
19+
import goserver.GoAPIInterface;
20+
import goserver.GoEnvironmentInterface;
21+
import goserver.Goserver;
22+
23+
public class GoService extends Service {
24+
// Binder given to clients
25+
private final IBinder binder = new GoServiceBinder();
26+
27+
private boolean started = false;
28+
29+
private Lock startedLock = new ReentrantLock();
30+
31+
private final String channelId = "21";
32+
33+
private final int notificationId = 8;
34+
35+
@Override
36+
public void onCreate() {
37+
Util.log("GoService onCreate()");
38+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
39+
NotificationChannel channel = new NotificationChannel(
40+
channelId,
41+
"BitBoxApp",
42+
NotificationManager.IMPORTANCE_LOW
43+
);
44+
channel.setDescription("BitBoxApp service notification channel");
45+
NotificationManager manager = getSystemService(NotificationManager.class);
46+
manager.createNotificationChannel(channel);
47+
}
48+
// Create a PendingIntent that starts the main activity of your app
49+
Intent notificationIntent = new Intent(getApplicationContext(),MainActivity.class);
50+
notificationIntent.setAction(Intent.ACTION_MAIN);
51+
notificationIntent.addCategory(Intent.CATEGORY_LAUNCHER);
52+
notificationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
53+
54+
PendingIntent contentIntent = PendingIntent.getActivity(
55+
getApplicationContext(),
56+
0,
57+
notificationIntent,
58+
PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);
59+
Notification notification = new NotificationCompat.Builder(this, channelId)
60+
.setContentTitle(getString(R.string.app_name))
61+
.setContentText("Running in the background to ensure connection stability")
62+
.setSmallIcon(R.drawable.ic_notification)
63+
.setWhen(System.currentTimeMillis())
64+
.setContentIntent(contentIntent)
65+
.setAutoCancel(true)
66+
.build();
67+
startForeground(notificationId,notification);
68+
69+
// The service goes in foreground to keep working normally even when the app is out of
70+
// focus. This is needed to avoid timeouts when the backend is polling the BitBox for e.g.
71+
// an address verification.
72+
startForeground(notificationId, notification);
73+
Util.log("GoService onCreate completed");
74+
}
75+
76+
@Override
77+
public void onDestroy() {
78+
Util.log("GoService onDestroy()");
79+
}
80+
81+
public void startServer(String filePath, GoEnvironmentInterface goEnvironment, GoAPIInterface goAPI) {
82+
Util.log("GoService: Starting server...");
83+
startedLock.lock();
84+
if (!started) {
85+
Goserver.serve(filePath, goEnvironment, goAPI);
86+
started = true;
87+
Util.log("server started!");
88+
} else {
89+
Util.log("server already started!");
90+
}
91+
startedLock.unlock();
92+
}
93+
94+
public class GoServiceBinder extends Binder {
95+
GoService getService() {
96+
return GoService.this;
97+
}
98+
}
99+
100+
@Override
101+
public IBinder onBind(Intent intent) {
102+
return binder;
103+
}
104+
}

frontends/android/BitBoxApp/app/src/main/java/ch/shiftcrypto/bitboxapp/GoViewModel.java

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,29 @@
11
package ch.shiftcrypto.bitboxapp;
22

3-
import goserver.GoDeviceInfoInterface;
4-
import goserver.GoEnvironmentInterface;
5-
import goserver.GoReadWriteCloserInterface;
6-
import goserver.GoAPIInterface;
7-
import goserver.Goserver;
8-
9-
import android.content.Context;
10-
import android.net.ConnectivityManager;
11-
import android.net.NetworkCapabilities;
12-
import android.net.NetworkInfo;
13-
import android.os.Build;
14-
import android.os.Message;
15-
import android.os.Handler;
163
import android.app.Application;
4+
import android.content.Context;
175
import android.hardware.usb.UsbConstants;
186
import android.hardware.usb.UsbDevice;
197
import android.hardware.usb.UsbDeviceConnection;
208
import android.hardware.usb.UsbEndpoint;
219
import android.hardware.usb.UsbInterface;
2210
import android.hardware.usb.UsbManager;
11+
import android.net.ConnectivityManager;
12+
import android.net.NetworkCapabilities;
13+
import android.net.NetworkInfo;
14+
import android.os.Build;
15+
import android.os.Handler;
16+
import android.os.Message;
2317

2418
import androidx.lifecycle.AndroidViewModel;
2519

2620
import java.util.Locale;
2721

22+
import goserver.GoAPIInterface;
23+
import goserver.GoDeviceInfoInterface;
24+
import goserver.GoEnvironmentInterface;
25+
import goserver.GoReadWriteCloserInterface;
26+
2827
public class GoViewModel extends AndroidViewModel {
2928

3029
private class GoDeviceInfo implements GoDeviceInfoInterface {
@@ -180,19 +179,24 @@ public void pushNotify(String msg) {
180179
private GoEnvironment goEnvironment;
181180
private GoAPI goAPI;
182181

182+
public GoEnvironment getGoEnvironment() {
183+
return goEnvironment;
184+
}
185+
186+
public GoAPI getGoAPI() {
187+
return goAPI;
188+
}
189+
183190
public GoViewModel(Application app) {
184191
super(app);
185192

186193
this.goEnvironment = new GoEnvironment();
187194
this.goAPI = new GoAPI();
188-
189-
Goserver.serve(app.getApplicationContext().getFilesDir().getAbsolutePath(), this.goEnvironment, this.goAPI);
190195
}
191196

192197
@Override
193198
public void onCleared() {
194199
super.onCleared();
195-
Goserver.shutdown();
196200
}
197201

198202
public void setMessageHandlers(Handler callResponseHandler, Handler pushNotificationHandler) {

frontends/android/BitBoxApp/app/src/main/java/ch/shiftcrypto/bitboxapp/MainActivity.java

Lines changed: 49 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,18 @@
55
import android.app.AlertDialog;
66
import android.app.PendingIntent;
77
import android.content.BroadcastReceiver;
8+
import android.content.ComponentName;
89
import android.content.Context;
910
import android.content.DialogInterface;
1011
import android.content.Intent;
1112
import android.content.IntentFilter;
13+
import android.content.ServiceConnection;
1214
import android.content.pm.PackageManager;
1315
import android.content.res.Configuration;
1416
import android.hardware.usb.UsbDevice;
1517
import android.hardware.usb.UsbManager;
1618
import android.net.Uri;
19+
import android.os.IBinder;
1720
import android.os.Process;
1821
import androidx.appcompat.app.AppCompatActivity;
1922
import androidx.core.app.ActivityCompat;
@@ -61,6 +64,27 @@ public class MainActivity extends AppCompatActivity {
6164
// stores the request from onPermissionRequest until the user has granted or denied the permission.
6265
private PermissionRequest webViewpermissionRequest;
6366

67+
GoService goService;
68+
69+
// Connection to bind with GoService
70+
private ServiceConnection connection = new ServiceConnection() {
71+
72+
@Override
73+
public void onServiceConnected(ComponentName className,
74+
IBinder service) {
75+
GoService.GoServiceBinder binder = (GoService.GoServiceBinder) service;
76+
goService = binder.getService();
77+
Util.log("Bind connection completed!");
78+
startServer();
79+
}
80+
81+
@Override
82+
public void onServiceDisconnected(ComponentName arg0) {
83+
goService = null;
84+
Util.log("Bind connection unexpectedly closed!");
85+
}
86+
};
87+
6488
private class JavascriptBridge {
6589
private Context context;
6690

@@ -121,9 +145,15 @@ protected void onCreate(Bundle savedInstanceState) {
121145
// For onramp iframe'd widgets like MoonPay.
122146
CookieManager.getInstance().setAcceptThirdPartyCookies(vw, true);
123147

124-
// GoModel invokes the Go backend. It is in a ViewModel so it only runs once, not every time
125-
// onCreate is called (on a configuration change like orientation change)
148+
// GoModel manages the Go backend. It is in a ViewModel so it only runs once, not every time
149+
// onCreate is called (on a configuration change like orientation change).
126150
final GoViewModel goViewModel = ViewModelProviders.of(this).get(GoViewModel.class);
151+
152+
// The backend is run inside GoService, to avoid (as much as possible) latency errors due to
153+
// the scheduling when the app is out of focus.
154+
Intent intent = new Intent(this, GoService.class);
155+
bindService(intent, connection, Context.BIND_AUTO_CREATE);
156+
127157
goViewModel.setMessageHandlers(
128158
new Handler() {
129159
@Override
@@ -248,6 +278,11 @@ public void onPermissionRequest(PermissionRequest request) {
248278
this.updateDevice();
249279
}
250280

281+
private void startServer() {
282+
final GoViewModel gVM = ViewModelProviders.of(this).get(GoViewModel.class);
283+
goService.startServer(getApplicationContext().getFilesDir().getAbsolutePath(), gVM.getGoEnvironment(), gVM.getGoAPI());
284+
}
285+
251286
private static String readRawText(InputStream inputStream) throws IOException {
252287
if (inputStream == null) {
253288
return null;
@@ -380,10 +415,21 @@ protected void onStop() {
380415
Util.log("lifecycle: onStop");
381416
}
382417

418+
@Override
419+
protected void onRestart() {
420+
// This is here so that if the GoService gets killed while the app is in background it
421+
// will be started again.
422+
if (goService == null) {
423+
Intent intent = new Intent(this, GoService.class);
424+
bindService(intent, connection, Context.BIND_AUTO_CREATE);
425+
}
426+
super.onRestart();
427+
}
428+
383429
@Override
384430
protected void onDestroy() {
385-
super.onDestroy();
386431
Util.log("lifecycle: onDestroy");
432+
super.onDestroy();
387433
}
388434

389435
@Override
766 Bytes
Loading
549 Bytes
Loading
1.05 KB
Loading
1.47 KB
Loading
2.25 KB
Loading

0 commit comments

Comments
 (0)