Implement base function calls.

As an example two functions are added:
 - Create notification.
 - Clear notifications.
This commit is contained in:
Sergio Martínez Portela 2020-05-27 14:08:53 +02:00
parent 3f24489138
commit 41da29f669
13 changed files with 302 additions and 18 deletions

View File

@ -133,8 +133,13 @@ public class DeckPreviewActivity extends ReloadableAppCompatActivity {
@Override
protected void onPostExecute(Tuple2<String, Consumer<String>> result) {
try {
result.item2.apply(result.item1);
}
catch (Throwable ex) {
Log.e(LogTag, "Error on login UI update", ex);
}
}
}
@Override

View File

@ -4,11 +4,10 @@ import android.content.Context;
import android.provider.Settings;
import com.codigoparallevar.minicards.ConfigManager;
import com.codigoparallevar.minicards.bridge.blocks.DefaultAndroidBlocks;
import com.programaker.bridge.ProgramakerBridge;
import com.programaker.bridge.ProgramakerBridgeConfiguration;
import java.util.Collections;
public class ProgramakerAndroidBridge {
private static final String LogTag = "PM Android Bridge";
private ProgramakerBridge bridgeRunner = null;
@ -24,12 +23,7 @@ public class ProgramakerAndroidBridge {
return serviceName;
}
// public static ProgramakerBridgeConfiguration GetConfiguration(Context ctx) {
// List<ProgramakerBridgeConfigurationBlock> blocks = new LinkedList<>();
// return new ProgramakerBridgeConfiguration(serviceName, blocks);
// }
// Builder
private final Context ctx;
private final String userId;
private final String bridgeId;
@ -44,7 +38,7 @@ public class ProgramakerAndroidBridge {
ProgramakerBridgeConfiguration configuration = new ProgramakerBridgeConfiguration(
ProgramakerAndroidBridge.GetBridgeName(this.ctx),
new ConfigManager(this.ctx),
Collections.emptyList()
DefaultAndroidBlocks.GetBuilder(this.ctx).Build()
);
this.bridgeRunner = new ProgramakerBridge(this.bridgeId, this.userId, configuration, onReady, onComplete);

View File

@ -14,6 +14,8 @@ import com.codigoparallevar.minicards.ui_helpers.DoAsync;
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 ProgramakerAndroidBridge bridge = null;
private static final String LogTag = "PM BridgeService";

View File

@ -0,0 +1,51 @@
package com.codigoparallevar.minicards.bridge.blocks;
import android.util.Log;
import org.jetbrains.annotations.NotNull;
import org.json.JSONException;
import org.json.JSONObject;
class BlockArgumentDefinition {
private final static String LogTag = "PM BlockArgument";
private final Type type;
private final String defaultValue;
public enum Type {
STRING,
INT,
FLOAT,
BOOL,
}
BlockArgumentDefinition(BlockArgumentDefinition.Type type, String defaultValue) {
this.type = type;
this.defaultValue = defaultValue;
}
@NotNull
public JSONObject serialize() throws JSONException {
JSONObject obj = new JSONObject();
obj.put("type", BlockArgumentDefinition.TypeToString(type));
obj.put("default", this.defaultValue);
return obj;
}
private static String TypeToString(Type type) {
switch (type) {
case STRING:
return "string";
case INT:
return "integer";
case FLOAT:
return "float";
case BOOL:
return "bool";
default:
Log.e(LogTag, "Unknown type: " + type);
return "string";
}
}
}

View File

@ -0,0 +1,33 @@
package com.codigoparallevar.minicards.bridge.blocks;
import com.codigoparallevar.minicards.types.functional.Consumer;
import com.programaker.bridge.ProgramakerBridgeConfigurationBlock;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
public class BridgeBlockListBuilder {
private final LinkedList<ProgramakerBridgeConfigurationBlock> blockList;
public BridgeBlockListBuilder() {
this.blockList = new LinkedList<>();
}
public List<ProgramakerBridgeConfigurationBlock> Build() {
return blockList;
}
public BridgeBlockListBuilder addOperation(String id, String message,
List<BlockArgumentDefinition> arguments,
Consumer<List<? extends Object>> operation) {
return this.addOperation(new OperationBlockDefinition(id, message, arguments, operation));
}
private BridgeBlockListBuilder addOperation(ProgramakerBridgeConfigurationBlock block) {
this.blockList.add(block);
return this;
}
}

View File

@ -0,0 +1,75 @@
package com.codigoparallevar.minicards.bridge.blocks;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.content.Context;
import androidx.core.app.NotificationCompat;
import com.codigoparallevar.minicards.R;
import com.codigoparallevar.minicards.bridge.ProgramakerBridgeService;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Random;
public class DefaultAndroidBlocks {
public static BridgeBlockListBuilder GetBuilder(Context ctx) {
// System services
String notifServiceId = Context.NOTIFICATION_SERVICE;
NotificationManager notificationManager = (NotificationManager) ctx.getSystemService(notifServiceId);
assert notificationManager != null;
Random notificationRandom = new Random();
String notificationChannelId = ProgramakerBridgeService.BridgeUserNotificationChannel;
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
ProgramakerBridgeService.BridgeUserNotificationChannel,
ProgramakerBridgeService.BridgeUserNotificationChannelName,
NotificationManager.IMPORTANCE_DEFAULT);
notificationManager.createNotificationChannel(channel);
}
return new BridgeBlockListBuilder()
// Notifications
.addOperation(
"notifications_new",
"Create notification. Title: %1, text: %2",
new LinkedList<BlockArgumentDefinition>() {{
add(new BlockArgumentDefinition(BlockArgumentDefinition.Type.STRING, "My notification"));
add(new BlockArgumentDefinition(BlockArgumentDefinition.Type.STRING, "Sample description"));
}},
(List<?> params) -> {
if (params.size() != 2) {
throw new Exception("Expected two (2) arguments, found " + params.size());
}
String title = params.get(0).toString();
String description = params.get(1).toString();
Notification notif = new NotificationCompat
.Builder(ctx, ProgramakerBridgeService.BridgeUserNotificationChannel)
.setContentTitle(title)
.setContentText(description)
.setSmallIcon(R.drawable.ic_center_focus_weak_black) // TODO: Change icon
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
.build();
int notificationId = notificationRandom.nextInt();
notificationManager.notify(notificationId, notif);
}
)
.addOperation(
"notifications_clear",
"Clear notifications",
Collections.emptyList(),
(List<?> params) -> {
notificationManager.cancelAll();
}
);
}
}

View File

@ -0,0 +1,65 @@
package com.codigoparallevar.minicards.bridge.blocks;
import android.util.Log;
import com.codigoparallevar.minicards.types.functional.Consumer;
import com.programaker.bridge.ProgramakerBridgeConfigurationBlock;
import org.jetbrains.annotations.NotNull;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.util.List;
class OperationBlockDefinition implements ProgramakerBridgeConfigurationBlock {
private final static String LogTag = "PM OpBlockDefinition";
private final String id;
private final String message;
private final List<BlockArgumentDefinition> args;
private final Consumer<List<?>> operation;
public OperationBlockDefinition(String id, String message, List<BlockArgumentDefinition> args, Consumer<List<? extends Object>> operation) {
this.id = id;
this.message = message;
this.args = args;
this.operation = operation;
}
@NotNull
@Override
public JSONObject serialize() {
JSONObject obj = new JSONObject();
try {
JSONArray arguments = new JSONArray();
for (BlockArgumentDefinition arg : this.args) {
arguments.put(arg.serialize());
}
obj.put("id", this.id);
obj.put("function_name", this.id);
obj.put("message", this.message);
obj.put("block_type", "operation");
obj.put("block_result_type", JSONObject.NULL);
obj.put("arguments", arguments);
obj.put("save_to", JSONObject.NULL);
} catch (JSONException ex) {
Log.e(LogTag, "Error serializing block definition: " + ex, ex);
}
return obj;
}
@NotNull
@Override
public String getFunctionName() {
return this.id;
}
@Override
public void call(@NotNull List<?> arguments) throws Exception {
this.operation.apply(arguments);
}
}

View File

@ -1,5 +1,5 @@
package com.codigoparallevar.minicards.types.functional;
public interface Consumer<T> {
void apply(T param);
void apply(T param) throws Exception;
}

View File

@ -1,6 +1,7 @@
package com.codigoparallevar.minicards.ui_helpers;
import android.os.AsyncTask;
import android.util.Log;
import com.codigoparallevar.minicards.types.functional.Action;
import com.codigoparallevar.minicards.types.functional.Consumer;
@ -15,7 +16,11 @@ public class DoAsync extends AsyncTask<Tuple2<Action, Consumer<Throwable>>,
data[0].item1.run();
}
catch (Throwable ex) {
try {
data[0].item2.apply(ex);
} catch (Throwable subEx) {
Log.e("DoAsync", "Error handling exception", subEx);
}
}
return null;
}

View File

@ -19,7 +19,12 @@ public class GetAsync<T> extends AsyncTask<Tuple3<Producer<T>, Consumer<T>, Cons
return new Tuple2<>(result, data[0]._y);
}
catch (Throwable ex) {
try {
data[0]._z.apply(ex);
}
catch (Throwable subEx) {
Log.d("GetAsync", "Error handling exception", subEx);
}
return null;
}
}
@ -30,7 +35,12 @@ public class GetAsync<T> extends AsyncTask<Tuple3<Producer<T>, Consumer<T>, Cons
return;
}
else {
try {
result.item2.apply(result.item1);
}
catch (Throwable ex) {
Log.e("GetAsync", "Error on UI thread", ex);
}
}
}
}

View File

@ -97,6 +97,7 @@ class ProgramakerBridge(
when (type) {
"GET_HOW_TO_SERVICE_REGISTRATION" -> handleGetServiceRegistrationInfo(webSocket, value, messageId, userId, extraData)
"REGISTRATION" -> handlePerformRegistration(webSocket, value, messageId, userId, extraData)
"FUNCTION_CALL" -> handleFunctionCall(webSocket, value, messageId, userId, extraData)
else ->
{
Log.w(LogTag, "Unknown command type: $type")
@ -104,6 +105,38 @@ class ProgramakerBridge(
}
}
private fun handleFunctionCall(webSocket: WebSocket, value: Map<*, *>, messageId: String, userId: String?, extraData: Map<*, *>?) {
val functionName = value.get("function_name") as String
val arguments = value.get("arguments") as List<*>
try {
config.callFunction(functionName, arguments)
val result = JSONObject.NULL
webSocket.send(
JSONObject(
hashMapOf(
"message_id" to messageId,
"success" to true,
"result" to result
) as Map<*, *>
).toString()
)
}
catch (ex: Throwable) {
Log.w(LogTag, "Error on bridge call to $functionName", ex)
webSocket.send(
JSONObject(
hashMapOf(
"message_id" to messageId,
"success" to false
) as Map<*, *>
).toString()
)
}
}
private fun handleGetServiceRegistrationInfo(webSocket: WebSocket, value: Map<*, *>, messageId: String?, userId: String?, extraData: Map<*, *>?) {
// Registration is automatic
webSocket.send(

View File

@ -12,6 +12,7 @@ class ProgramakerBridgeConfiguration(
// val allow_multiple_connections: boolean // No reason for this use case
// val icon: ???, // Not supported yet // TODO: Add support
) {
fun serialize(): String {
var serializedBlocks = listOf<JSONObject>()
if (blocks != null) {
@ -34,4 +35,14 @@ class ProgramakerBridgeConfiguration(
return wrapper.toString()
}
fun callFunction(functionName: String, arguments: List<*>) {
for (block in blocks) {
if (block.getFunctionName() == functionName) {
return block.call(arguments)
}
}
throw IllegalArgumentException("Bridge function (name=$functionName) not found")
}
}

View File

@ -2,10 +2,10 @@ package com.programaker.bridge
import org.json.JSONObject
class ProgramakerBridgeConfigurationBlock {
fun serialize() : JSONObject {
val obj = JSONObject();
return obj;
}
interface ProgramakerBridgeConfigurationBlock {
fun serialize() : JSONObject;
fun getFunctionName(): String;
@Throws(Exception::class)
fun call(arguments: List<*>)
}