Skip to content

Commit

Permalink
Split javascript interface methods into domain objects.
Browse files Browse the repository at this point in the history
Change-Id: I8c9af486c107377d16316361b12be4db3d47dd17
  • Loading branch information
Colin Liang committed Oct 21, 2024
1 parent 8667efb commit 1fa1b2a
Show file tree
Hide file tree
Showing 10 changed files with 328 additions and 213 deletions.
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

0 comments on commit 1fa1b2a

Please sign in to comment.