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 @@ + + + + + + image/svg+xml + + + + + + + + + + + +