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

Split javascript interface methods into domain objects. #4293

Merged
merged 1 commit into from
Oct 21, 2024
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
72 changes: 1 addition & 71 deletions starboard/android/apk/app/src/main/assets/injected_script.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,76 +36,6 @@ var injectBackKeyPress = () => {

}

function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary); // Encode binary string to Base64
}

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: 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) {
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(data);
console.log('1 platformService.send(' + text + ')');
var convert_to_b64 = arrayBufferToBase64(data);
console.log('sending as b64:' + convert_to_b64);
Android.platform_service_send(name, convert_to_b64);
},
close: () => {
console.log('1 platformService.close()');
Android.close_platform_service(name);
},
}
},
send: (data) => {
console.log('platformService.send(' + JSON.stringify(data) + ')');
},
close: () => {
console.log('platformService.close()');
},
}


var accountmanager = {
getAuthToken: () => { },
requestPairing: () => { },
Expand Down Expand Up @@ -213,7 +143,7 @@ function intercept_is_type_supported() {

function inject() {
window.h5vcc = h5;
window.H5vccPlatformService = platform_services;
// window.H5vccPlatformService = platform_services;
intercept_is_type_supported();
set_black_video_poster_image();
}
Expand Down
70 changes: 70 additions & 0 deletions starboard/android/apk/app/src/main/assets/platform_services.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
function arrayBufferToBase64(buffer) {
const bytes = new Uint8Array(buffer);
let binary = '';
for (let i = 0; i < bytes.byteLength; i++) {
binary += String.fromCharCode(bytes[i]);
}
return window.btoa(binary); // Encode binary string to Base64
}

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_H5vccPlatformService.has_platform_service(name);
},
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_H5vccPlatformService.open_platform_service(serviceId, name);
return {
'name': name,
'send': function (data) {
const decoder = new TextDecoder('utf-8');
const text = decoder.decode(data);
console.log('1 platformService.send(' + text + ')');
var convert_to_b64 = arrayBufferToBase64(data);
console.log('sending as b64:' + convert_to_b64);
Android_H5vccPlatformService.platform_service_send(name, convert_to_b64);
},
close: () => {
console.log('1 platformService.close()');
Android_H5vccPlatformService.close_platform_service(name);
},
}
},
send: (data) => {
console.log('platformService.send(' + JSON.stringify(data) + ')');
},
close: () => {
console.log('platformService.close()');
},
}

window.H5vccPlatformService = platform_services;
Original file line number Diff line number Diff line change
@@ -1,107 +1,43 @@
package dev.cobalt.coat;
import static dev.cobalt.util.Log.TAG;

import android.app.Activity;
import android.content.Context;
import android.graphics.Bitmap;
import android.webkit.WebView;
import android.webkit.WebSettings;
import android.webkit.WebViewClient;

import androidx.annotation.NonNull;

import dev.cobalt.util.Log;
import dev.cobalt.coat.BuildConfig;
import java.util.List;
import java.util.ArrayList;

public class ChrobaltWebView extends WebView {
public void evalJavaScript(String javascript) { // Make sure it's public
this.evaluateJavascript(javascript, null);
}
import dev.cobalt.coat.android_webview.H5vccPlatformService;
import dev.cobalt.coat.android_webview.JavaScriptAndroidObject;
import dev.cobalt.util.UserAgent;

public class ChrobaltWebView extends WebView {
StarboardBridge bridge = null;

WebAppInterface webAppInterface = null;

ChrobaltWebViewClient webViewClient = null;

private class ChrobaltWebViewClient extends WebViewClient {
@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.i(TAG, "Page started loading: " + url);
super.onPageStarted(view, url, favicon);

// Load from a bundled asset
String jsCode = Helpers.loadJavaScriptFromAsset(view.getContext(), "injected_script.js");
view.evaluateJavascript(jsCode, null);

// Load over network from development host
Helpers.loadJavaScriptFromURL("http://" + Helpers.getDevelopmentHostSetting(view.getContext()) + ":8000/dyn_script.js")
.thenAccept(jsCode2 -> {

if ( view.getContext() instanceof Activity) {
((Activity) view.getContext()).runOnUiThread(() -> {
// Perform UI operations here
Log.i(TAG, "Got JS2, injecting");
view.evaluateJavascript(jsCode2, null);
});
}

}).exceptionally(e -> {
// Handle any exceptions here
Log.e(TAG, "Error message: " + e.getMessage(), e);
return null;
});

Log.i(TAG, "JavaScript injected");
}

@Override
public void onPageFinished(WebView view, String url) {
Log.i(TAG, "Page finished loading: " + url);
super.onPageFinished(view, url);
}

}

private String createUserAgentString() {
// TODO: sanitize inputs
String brand = this.webAppInterface.getRestrictedSystemProperty("ro.product.brand","defaultBrand");
String model = this.webAppInterface.getRestrictedSystemProperty("ro.product.model","defaultModel");
String firmware = this.webAppInterface.getRestrictedSystemProperty("ro.build.id","defaultFirmware");
String chipset = this.webAppInterface.getRestrictedSystemProperty("ro.board.platform","defaultChipset");
String oemKey = this.webAppInterface.getRestrictedSystemProperty("ro.oem.key1","defaultModelYear");
String integrator = this.webAppInterface.getRestrictedSystemProperty("ro.product.manufacturer","defaultIntegrator");
String androidVersion = this.webAppInterface.getRestrictedSystemProperty("ro.build.version.release","defaultAndroidVersion");
String abi = this.webAppInterface.getRestrictedSystemProperty("ro.product.cpu.abi", "defaultABI");
String aux = this.bridge.getUserAgentAuxField();
String modelYear = "20" + oemKey.substring(9, 11);

// TODO: Resolve missing and hardcoded fields
String customUserAgent = String.format("Mozilla/5.0 (Linux %s; Android %s) %s (unlike Gecko)" +
" v8/8.8.278.8-jit gles Starboard/%s, %s_ATV_%s_%s/%s" +
" (%s, %s) %s",
abi, androidVersion,
"Cobalt/26.lts.99.42-gold","17",
integrator, chipset, modelYear, firmware,
brand, model, aux
);
Log.e(TAG, "Custom User-Agent: " + customUserAgent);
return customUserAgent;
}

public ChrobaltWebView(@NonNull Context context, @NonNull StarboardBridge bridge) {
super(context);

this.bridge = bridge;

// Todo, kill this
this.webAppInterface = new WebAppInterface(context, this.bridge);
this.webViewClient = new ChrobaltWebViewClient();

List<JavaScriptAndroidObject> javaScriptAndroidObjectList = new ArrayList<>();
javaScriptAndroidObjectList.add(new H5vccPlatformService(bridge));

this.webViewClient = new ChrobaltWebViewClient(javaScriptAndroidObjectList);

WebSettings webSettings = this.getSettings();

// Enable JavaScript
webSettings.setJavaScriptEnabled(true);

webSettings.setUserAgentString(createUserAgentString());
webSettings.setUserAgentString(new UserAgent(context).createUserAgentString());

// Set mixed content mode to allow all content to be loaded, regardless of the security origin
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
Expand All @@ -115,8 +51,22 @@ public ChrobaltWebView(@NonNull Context context, @NonNull StarboardBridge bridge
// Disable transition icon
webSettings.setMediaPlaybackRequiresUserGesture(false);

// Add all implementations of dev.cobalt.coat.android_webview.WebAppInterface
for (JavaScriptAndroidObject javascriptAndroidObject : javaScriptAndroidObjectList) {
// Have to cast to the class to avoid
// Error: None of the methods in the added interface (JavaScriptAndroidObject) have been annotated with @android.webkit.JavascriptInterface; they will not be visible in API 17 [JavascriptInterface]
if (javascriptAndroidObject instanceof H5vccPlatformService){
addJavascriptInterface((H5vccPlatformService)javascriptAndroidObject, javascriptAndroidObject.getJavaScriptInterfaceName());
}
// addJavascriptInterface(javascriptAndroidObject, javascriptAndroidObject.getJavaScriptInterfaceName());
}

addJavascriptInterface(this.webAppInterface, "Android");

setWebViewClient(this.webViewClient);
}

public void evalJavaScript(String javascript) { // Make sure it's public
this.evaluateJavascript(javascript, null);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package dev.cobalt.coat;

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

import android.app.Activity;
import android.graphics.Bitmap;
import android.webkit.WebView;
import android.webkit.WebViewClient;

import java.util.List;

import dev.cobalt.coat.android_webview.JavaScriptAndroidObject;
import dev.cobalt.util.Log;

public class ChrobaltWebViewClient extends WebViewClient {
private final List<JavaScriptAndroidObject> javaScriptAndroidObjectList;

public ChrobaltWebViewClient(List<JavaScriptAndroidObject> javaScriptAndroidObjectList) {
super();
this.javaScriptAndroidObjectList = javaScriptAndroidObjectList;
}

@Override
public void onPageStarted(WebView view, String url, Bitmap favicon) {
Log.i(TAG, "Page started loading: " + url);
super.onPageStarted(view, url, favicon);

for (JavaScriptAndroidObject javaScriptAndroidObject : javaScriptAndroidObjectList) {
String jsCode = Helpers.loadJavaScriptFromAsset(view.getContext(), javaScriptAndroidObject.getJavaScriptAssets());
view.evaluateJavascript(jsCode, null);
}

// Load from a bundled asset
String jsCode = Helpers.loadJavaScriptFromAsset(view.getContext(), "injected_script.js");
view.evaluateJavascript(jsCode, null);

// Load over network from development host
Helpers.loadJavaScriptFromURL("http://" + Helpers.getDevelopmentHostSetting(view.getContext()) + ":8000/dyn_script.js")
.thenAccept(jsCode2 -> {

if ( view.getContext() instanceof Activity) {
((Activity) view.getContext()).runOnUiThread(() -> {
// Perform UI operations here
Log.i(TAG, "Got JS2, injecting");
view.evaluateJavascript(jsCode2, null);
});
}

}).exceptionally(e -> {
// Handle any exceptions here
Log.e(TAG, "Error message: " + e.getMessage(), e);
return null;
});

Log.i(TAG, "JavaScript injected");
}

@Override
public void onPageFinished(WebView view, String url) {
Log.i(TAG, "Page finished loading: " + url);
super.onPageFinished(view, url);
}

}
Loading
Loading