diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 3d78711..a664cc1 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -9,6 +9,7 @@
+
+ android:exported="true" />
diff --git a/app/src/main/java/com/codigoparallevar/minicards/bridge/ProgramakerBridgeService.java b/app/src/main/java/com/codigoparallevar/minicards/bridge/ProgramakerBridgeService.java
index 151aa8f..9a884fb 100644
--- a/app/src/main/java/com/codigoparallevar/minicards/bridge/ProgramakerBridgeService.java
+++ b/app/src/main/java/com/codigoparallevar/minicards/bridge/ProgramakerBridgeService.java
@@ -1,6 +1,11 @@
package com.codigoparallevar.minicards.bridge;
+import android.app.Notification;
+import android.app.NotificationChannel;
+import android.app.NotificationManager;
+import android.app.PendingIntent;
import android.app.Service;
+import android.content.Context;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.IBinder;
@@ -8,7 +13,10 @@ import android.os.Process;
import android.util.Log;
import android.widget.Toast;
+import androidx.core.app.NotificationCompat;
+
import com.codigoparallevar.minicards.ConfigManager;
+import com.codigoparallevar.minicards.R;
import com.codigoparallevar.minicards.types.functional.Tuple2;
import com.codigoparallevar.minicards.ui_helpers.DoAsync;
import com.programaker.api.ProgramakerApi;
@@ -16,18 +24,82 @@ import com.programaker.api.ProgramakerApi;
public class ProgramakerBridgeService extends Service {
public static final String BridgeUserNotificationChannel = "PROGRAMAKER_BRIDGE_USER_NOTIFICATION";
public static final CharSequence BridgeUserNotificationChannelName = "User notifications";
+
+ private static String BridgeStatusNotificationChannel = "PROGRAMAKER_BRIDGE_STATUS_NOTIFICATION";
+ private static CharSequence BridgeStatusNotificationChannelName = "Programaker bridge status";
+
+ private static final int BridgeStatusNotificationId = 9999;
+ public static final String COMMAND_PROGRAMAKER_BRIDGE_STOP = "com.programaker.bridge.commands.stop";
+ public static final String COMMAND_PROGRAMAKER_BRIDGE_START = "com.programaker.bridge.commands.start";
+
private static final long WAIT_TIME_BEFORE_RESTART_MILLIS = 10000; // 10s
private ProgramakerAndroidBridge bridge = null;
private static final String LogTag = "PM BridgeService";
@Override
public void onCreate() {
+ setBridgeStatusNotification(getString(R.string.bridge_service_not_started), true);
+ }
+
+ private void setBridgeStatusNotification(String title, boolean stopped) {
+ NotificationManager notificationManager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
+ assert notificationManager != null;
+
+ if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
+ NotificationChannel channel = new NotificationChannel(
+ ProgramakerBridgeService.BridgeStatusNotificationChannel,
+ ProgramakerBridgeService.BridgeStatusNotificationChannelName,
+ NotificationManager.IMPORTANCE_DEFAULT);
+ notificationManager.createNotificationChannel(channel);
+ }
+
+ Intent stopIntent = new Intent(this, ProgramakerBridgeService.class);
+ stopIntent.setAction(COMMAND_PROGRAMAKER_BRIDGE_STOP);
+ PendingIntent stopPendingIntent =
+ PendingIntent.getService(this, 0, stopIntent, 0);
+
+ Intent startIntent = new Intent(this, ProgramakerBridgeService.class);
+ startIntent.setAction(COMMAND_PROGRAMAKER_BRIDGE_START);
+ PendingIntent startPendingIntent =
+ PendingIntent.getService(this, 0, startIntent, 0);
+
+ NotificationCompat.Builder builder = new NotificationCompat
+ .Builder(this, ProgramakerBridgeService.BridgeStatusNotificationChannel)
+ .setContentTitle(title)
+ .setSmallIcon(R.drawable.ic_vector_icon)
+ .setPriority(NotificationCompat.PRIORITY_DEFAULT);
+
+
+ if (stopped) {
+ builder.addAction(R.drawable.ic_start, getString(R.string.start_bridge), startPendingIntent);
+ }
+ else {
+ builder.addAction(R.drawable.ic_cancel, getString(R.string.stop_bridge), stopPendingIntent);
+ }
+
+ Notification notification = builder.build();
+
+ if (stopped) {
+ notificationManager.notify(BridgeStatusNotificationId, notification);
+ }
+ else {
+ this.startForeground(BridgeStatusNotificationId, notification);
+ }
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
- Toast.makeText(this, "Starting bridge", Toast.LENGTH_SHORT).show();
- connectBridge();
+ String action = intent.getAction();
+ if (action == null) {
+ action = "";
+ }
+
+ if (action.equals(COMMAND_PROGRAMAKER_BRIDGE_STOP)) {
+ this.stopSelf();
+ }
+ else {
+ connectBridge();
+ }
// If we get killed, after returning from here, restart
return START_STICKY;
@@ -39,9 +111,8 @@ public class ProgramakerBridgeService extends Service {
String token = config.getToken();
if (token == null) {
Toast.makeText(this, "Cannot start bridge", Toast.LENGTH_SHORT).show();
- if (token == null) {
- Log.e(LogTag, "Cannot start bridge: Token is null");
- }
+ Log.e(LogTag, "Cannot start bridge: Token is null");
+ this.stopSelf();
}
if (ProgramakerBridgeService.this.bridge != null) {
@@ -72,6 +143,7 @@ public class ProgramakerBridgeService extends Service {
bridgeId);
ProgramakerBridgeService.this.bridge.start(
() -> { // On ready
+ setBridgeStatusNotification(getString(R.string.bridge_service_online), false);
if (config.getBridgeConnectionId() == null) {
new DoAsync().executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR,
new Tuple2<>(
@@ -100,11 +172,14 @@ public class ProgramakerBridgeService extends Service {
}
}, "ServiceStartArguments");
thread.setPriority(Process.THREAD_PRIORITY_BACKGROUND);
+
+ setBridgeStatusNotification(getString(R.string.bridge_service_starting), false);
thread.start();
}
private void onBridgeFailedAfterConnected() {
Log.e(LogTag, "Bridge stopped after connected. Waiting 10s then restarting");
+ setBridgeStatusNotification(getString(R.string.bridge_service_failed_restarting), false);
try {
Thread.sleep(WAIT_TIME_BEFORE_RESTART_MILLIS);
} catch (InterruptedException e) {
@@ -121,6 +196,6 @@ public class ProgramakerBridgeService extends Service {
@Override
public void onDestroy() {
- Toast.makeText(this, "Bridge stopped", Toast.LENGTH_SHORT).show();
+ setBridgeStatusNotification(getString(R.string.bridge_service_failed_stopping), true);
}
}
diff --git a/app/src/main/res/drawable/ic_cancel.xml b/app/src/main/res/drawable/ic_cancel.xml
new file mode 100644
index 0000000..39b6244
--- /dev/null
+++ b/app/src/main/res/drawable/ic_cancel.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_start.xml b/app/src/main/res/drawable/ic_start.xml
new file mode 100644
index 0000000..dfae628
--- /dev/null
+++ b/app/src/main/res/drawable/ic_start.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/ic_vector_icon.xml b/app/src/main/res/drawable/ic_vector_icon.xml
new file mode 100644
index 0000000..38f9ad0
--- /dev/null
+++ b/app/src/main/res/drawable/ic_vector_icon.xml
@@ -0,0 +1,12 @@
+
+
+
+
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index a4fd540..63f8b22 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -18,4 +18,11 @@
Placeholder text
Back to card Deck
Go back
+ Programaker bridge not started
+ Programaker bridge starting
+ Programaker bridge online
+ Programaker bridge failed, restarting...
+ Programaker bridge stopped
+ Stop bridge
+ Start bridge
diff --git a/asset-src/ic_vector_icon.svg b/asset-src/ic_vector_icon.svg
new file mode 100644
index 0000000..aa13b0a
--- /dev/null
+++ b/asset-src/ic_vector_icon.svg
@@ -0,0 +1,72 @@
+
+