Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Two-way platform service comm #4185

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 31 additions & 4 deletions starboard/android/apk/app/src/main/assets/injected_script.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,41 @@ function arrayBufferToBase64(buffer) {
}

var platform_services = {
callbacks: {
},
callback_from_android: (serviceID, dataFromJava) => {
console.log("Wrapper callback received:", name, dataFromJava);
const binaryString = window.atob(dataFromJava);
console.log("message:" + binaryString);
const len = binaryString.length;
const bytes = new Uint8Array(len);
for (let i = 0; i < len; i++) {
bytes[i] = binaryString.charCodeAt(i);
}
const arrayBuffer = bytes.buffer;
window.H5vccPlatformService.callbacks[serviceID].callback(arrayBuffer);
},
has: (name) => {
console.log('platformService.has(' + name + ')');
return Android.has_platform_service(name);
},
open: (name, callback) => {
console.log('platformService.open(' + name + ',' + JSON.stringify(callback) + ')');
Android.open_platform_service(name);
// this needs to return an object with send
open: function(name, callback) {
console.log('platformService.open(' + name + ',' +
JSON.stringify(callback) + ')');
if (typeof callback !== 'function') {
console.log("THROWING Missing or invalid callback function.")
throw new Error("Missing or invalid callback function.");
} else {
console.log("callback was function!!!");
}

const serviceId = Object.keys(this.callbacks).length + 1;
// Store the callback with the service ID, name, and callback
window.H5vccPlatformService.callbacks[serviceId] = {
name: name,
callback: callback
};
Android.open_platform_service(serviceId, name);
return {
'name': name,
'send': function (data) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,16 @@
import dev.cobalt.coat.BuildConfig;

public class ChrobaltWebView extends WebView {
public void evalJavaScript(String javascript) { // Make sure it's public
this.evaluateJavascript(javascript, null);
}

StarboardBridge bridge = null;

WebAppInterface webAppInterface = null;

ChrobaltWebViewClient webViewClient = null;


private class ChrobaltWebViewClient extends WebViewClient {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.CallSuper;
import com.google.androidgamesdk.GameActivity;
import dev.cobalt.libraries.services.accountmanager.AccountManagerModule;
import dev.cobalt.util.DisplayUtil;
import dev.cobalt.libraries.services.clientloginfo.ClientLogInfoModule;
import dev.cobalt.libraries.services.FakeSoftMicModule;
Expand All @@ -50,7 +51,7 @@ public abstract class CobaltActivity extends GameActivity {

private long timeInNanoseconds;

private WebView webView;
private ChrobaltWebView webView;

@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
Expand Down Expand Up @@ -103,6 +104,10 @@ protected void onCreate(Bundle savedInstanceState) {
CobaltService.Factory fakeSoftMicFactory = new FakeSoftMicModule().provideFactory(
getApplicationContext());
getStarboardBridge().registerCobaltService(fakeSoftMicFactory);
CobaltService.Factory accountManagerFactory =
new AccountManagerModule()
.provideFactory(getApplicationContext(), this.getStarboardBridge().getExecutor());
getStarboardBridge().registerCobaltService(accountManagerFactory);

// Make the activity full-screen
getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
Expand All @@ -123,6 +128,7 @@ public void handleOnBackPressed() {
webView = new ChrobaltWebView(this,getStarboardBridge());
// Load Kabuki
webView.loadUrl(startDeepLink);
getStarboardBridge().setJavaScriptCallback(javascript -> webView.evalJavaScript(javascript));

// Set the WebView as the main content view of the activity
setContentView(webView);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,19 @@

import static dev.cobalt.util.Log.TAG;

import android.util.Base64;
import dev.cobalt.util.Log;

/** Abstract class that provides an interface for Cobalt to interact with a platform service. */
public abstract class CobaltService {

interface StringCallback {
void onStringResult(long name, String result);
}

// Indicate is the service opened, and be able to send data to client
protected boolean opened = true;
private StringCallback jsCallback;

/** Interface that returns an object that extends CobaltService. */
public interface Factory {
Expand Down Expand Up @@ -55,6 +62,10 @@ public static class ResponseToClient {
public byte[] data;
}

void setCallback(StringCallback jsCallback) {
this.jsCallback = jsCallback;
}

/** Receive data from client of the service. */
@SuppressWarnings("unused")
public abstract ResponseToClient receiveFromClient(byte[] data);
Expand Down Expand Up @@ -89,9 +100,15 @@ protected void sendToClient(long nativeService, byte[] data) {
"Platform service did not send data to client, because client already closed the"
+ " platform service.");
return;
} else {
if (this.jsCallback != null) {
String base64Data = Base64.encodeToString(data, Base64.NO_WRAP);
Log.w(TAG, "Sending : `" + base64Data + "`");
this.jsCallback.onStringResult(nativeService, base64Data);
} else {
Log.w(TAG, "Callback was null");
}
}

}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,17 @@
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Nullable;
import dev.cobalt.util.Log;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashMap;
import java.util.TimeZone;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
* Mock Starboard bridge
Expand All @@ -40,6 +45,10 @@ public interface HostApplication {
StarboardBridge getStarboardBridge();
}

interface JavaScriptCallback {
void onStringResult(String result);
}

private NetworkStatus networkStatus;
private AdvertisingId advertisingId;
private final VolumeStateReceiver volumeStateReceiver;
Expand Down Expand Up @@ -67,10 +76,12 @@ public void run() {

private boolean starboardApplicationReady = true;

public StarboardBridge(
Context appContext,
String[] args,
String startDeepLink) {
private ExecutorService executor;

private JavaScriptCallback evalJavaScriptCallback;
private final Handler mainHandler = new Handler(Looper.getMainLooper());

public StarboardBridge(Context appContext, String[] args, String startDeepLink) {

this.appContext = appContext;
this.args = args;
Expand All @@ -80,6 +91,12 @@ public StarboardBridge(
this.advertisingId = new AdvertisingId(appContext);
this.volumeStateReceiver = new VolumeStateReceiver(appContext);
this.isAmatiDevice = appContext.getPackageManager().hasSystemFeature(AMATI_EXPERIENCE_FEATURE);

this.executor = Executors.newFixedThreadPool(2);
}

public Executor getExecutor() {
return this.executor;
}

protected void onActivityStart(Activity activity) {
Expand Down Expand Up @@ -172,6 +189,18 @@ boolean hasCobaltService(String serviceName) {
return weHaveIt;
}

public void setJavaScriptCallback(JavaScriptCallback callback) {
this.evalJavaScriptCallback = callback;
}

public void callbackFromService(long name, String foo) {
mainHandler.post(
() -> {
this.evalJavaScriptCallback.onStringResult(
"window.H5vccPlatformService.callback_from_android(" + name + ",'" + foo + "');");
});
}

@SuppressWarnings("unused")
CobaltService openCobaltService(long nativeService, String serviceName) {
if (cobaltServices.get(serviceName) != null) {
Expand All @@ -186,7 +215,7 @@ CobaltService openCobaltService(long nativeService, String serviceName) {
}
CobaltService service = factory.createCobaltService(nativeService);
if (service != null) {
//service.receiveStarboardBridge(this);
service.setCallback(this::callbackFromService);
cobaltServices.put(serviceName, service);
}
return service;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,74 +1,73 @@
package dev.cobalt.coat;

import static dev.cobalt.util.Log.TAG;
import dev.cobalt.util.Log;

import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import android.content.Context;
import android.util.Base64;
import android.webkit.JavascriptInterface;
import dev.cobalt.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

/**
* Native call bridge
*/
/** Native call bridge */
public class WebAppInterface {
Context mContext;
StarboardBridge bridge;
Context mContext;
StarboardBridge bridge;

// Instantiate the interface and set the context
WebAppInterface(Context c, StarboardBridge b) {
mContext = c;
bridge = b;
}
// Instantiate the interface and set the context
WebAppInterface(Context c, StarboardBridge b) {
mContext = c;
bridge = b;
}

@JavascriptInterface
public boolean has_platform_service(String servicename) {
return bridge.hasCobaltService(servicename);
}
@JavascriptInterface
public boolean has_platform_service(String servicename) {
return bridge.hasCobaltService(servicename);
}

@JavascriptInterface
public void open_platform_service(String servicename) {
bridge.openCobaltService(1, servicename);
}
@JavascriptInterface
public void open_platform_service(long number, String servicename) {
bridge.openCobaltService(number, servicename);
}

@JavascriptInterface
public void close_platform_service(String servicename) {
bridge.closeCobaltService(servicename);
}
@JavascriptInterface
public void platform_service_send(String servicename, String base64Data) {
byte[] data = Base64.decode(base64Data, Base64.DEFAULT);
bridge.sendToCobaltService(servicename, data);
}
@JavascriptInterface
public void close_platform_service(String servicename) {
bridge.closeCobaltService(servicename);
}

@JavascriptInterface
public String getSystemProperty(String propertyName, String defaultValue) {
return System.getProperty(propertyName, defaultValue);
}
@JavascriptInterface
public void platform_service_send(String servicename, String base64Data) {
byte[] data = Base64.decode(base64Data, Base64.DEFAULT);
bridge.sendToCobaltService(servicename, data);
}

@JavascriptInterface
public String getRestrictedSystemProperty(String propName, String defaultValue) {
try {
Process process = Runtime.getRuntime().exec("getprop " + propName);
BufferedReader bufferedReader = new BufferedReader(
new InputStreamReader(process.getInputStream()));
@JavascriptInterface
public String getSystemProperty(String propertyName, String defaultValue) {
return System.getProperty(propertyName, defaultValue);
}

return bufferedReader.readLine();
} catch (IOException e) {
return defaultValue;
}
}
@JavascriptInterface
public String getRestrictedSystemProperty(String propName, String defaultValue) {
try {
Process process = Runtime.getRuntime().exec("getprop " + propName);
BufferedReader bufferedReader =
new BufferedReader(new InputStreamReader(process.getInputStream()));

@JavascriptInterface
public String getAdvertisingId() {
Log.i(TAG, "getAdvertisingId:" + bridge.getAdvertisingId());
return bridge.getAdvertisingId();
return bufferedReader.readLine();
} catch (IOException e) {
return defaultValue;
}
}

@JavascriptInterface
public boolean getLimitAdTracking() {
return bridge.getLimitAdTracking();
}
@JavascriptInterface
public String getAdvertisingId() {
Log.i(TAG, "getAdvertisingId:" + bridge.getAdvertisingId());
return bridge.getAdvertisingId();
}

@JavascriptInterface
public boolean getLimitAdTracking() {
return bridge.getLimitAdTracking();
}
}
Loading
Loading