Skip to content

Commit

Permalink
Chrobalt Java Bridge
Browse files Browse the repository at this point in the history
b/372558900
  • Loading branch information
Colin Liang committed Oct 29, 2024
1 parent e16ed1e commit 6663610
Show file tree
Hide file tree
Showing 8 changed files with 165 additions and 0 deletions.
5 changes: 5 additions & 0 deletions cobalt/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ android_library("cobalt_apk_java") {
"apk/app/src/main/java/dev/cobalt/coat/CaptionSettings.java",
"apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java",
"apk/app/src/main/java/dev/cobalt/coat/CobaltHttpHelper.java",
"apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptAndroidObject.java",
"apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptAndroidObjectExample.java",
"apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptInterface.java",

# "apk/app/src/main/java/dev/cobalt/coat/CobaltMediaSession.java",
"apk/app/src/main/java/dev/cobalt/coat/CobaltService.java",
Expand Down Expand Up @@ -92,6 +95,7 @@ android_library("cobalt_apk_java") {

# "apk/app/src/main/java/dev/cobalt/storage/CobaltStorageLoader.java",
# "apk/app/src/main/java/dev/cobalt/storage/StorageProto.java",
"apk/app/src/main/java/dev/cobalt/util/AssetLoader.java",
"apk/app/src/main/java/dev/cobalt/util/DisplayUtil.java",
"apk/app/src/main/java/dev/cobalt/util/Holder.java",
"apk/app/src/main/java/dev/cobalt/util/IsEmulator.java",
Expand All @@ -105,6 +109,7 @@ android_library("cobalt_apk_java") {
android_assets("cobalt_apk_assets") {
testonly = true
sources = [
"apk/app/src/app/assets/example.js",
"apk/app/src/app/assets/not_empty.txt",
"apk/app/src/app/assets/test/not_empty.txt",
"apk/app/src/app/assets/web/cobalt_blue_splash_screen.css",
Expand Down
1 change: 1 addition & 0 deletions cobalt/android/apk/app/src/app/assets/example.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
AndroidExample.testJavaScriptMethod();
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,19 @@
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Pair;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
import android.view.ViewParent;
import android.widget.FrameLayout;
import dev.cobalt.coat.javabridge.CobaltJavaScriptAndroidObject;
import dev.cobalt.coat.javabridge.CobaltJavaScriptAndroidObjectExample;
import dev.cobalt.coat.javabridge.CobaltJavaScriptInterface;
import dev.cobalt.media.MediaCodecCapabilitiesLogger;
import dev.cobalt.media.VideoSurfaceView;
import dev.cobalt.util.AssetLoader;
import dev.cobalt.util.DisplayUtil;
import dev.cobalt.util.Log;
import dev.cobalt.util.UsedByNative;
Expand All @@ -38,7 +44,10 @@
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import org.chromium.content_public.browser.JavascriptInjector;
import org.chromium.content_public.browser.WebContents;
import org.chromium.content_shell_apk.ContentShellActivity;

// import dev.cobalt.media.AudioOutputManager;

/** Native activity that has the required JNI methods called by the Starboard implementation. */
Expand Down Expand Up @@ -66,6 +75,8 @@ public abstract class CobaltActivity extends ContentShellActivity {

private static final Pattern URL_PARAM_PATTERN = Pattern.compile("^[a-zA-Z0-9_=]*$");

public static final int JAVA_BRIDGE_INITIALIZATION_DELAY_MILLI_SECONDS = 100;

private VideoSurfaceView videoSurfaceView;

private boolean forceCreateNewVideoSurfaceView = false;
Expand Down Expand Up @@ -105,6 +116,55 @@ protected void onCreate(Bundle savedInstanceState) {
videoSurfaceView = new VideoSurfaceView(this);
addContentView(
videoSurfaceView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));

initializeJavaBridge();
}

/**
* Initializes the Java Bridge to allow communication between Java and JavaScript.
* This method injects Java objects into the WebView and loads corresponding JavaScript code.
*/
private void initializeJavaBridge() {

WebContents webContents = getActiveWebContents();
if (webContents == null) {
// WebContents not initialized yet, post a delayed runnable to check again
new Handler(Looper.getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
initializeJavaBridge(); // Recursive call to check again
}
}, JAVA_BRIDGE_INITIALIZATION_DELAY_MILLI_SECONDS);
return;
}

// --- Initialize the Java Bridge ---

// 1. Gather all Java objects that need to be exposed to JavaScript.
List<CobaltJavaScriptAndroidObject> javaScriptAndroidObjectList = new ArrayList<>();
javaScriptAndroidObjectList.add(new CobaltJavaScriptAndroidObjectExample());

// 2. Use JavascriptInjector to inject Java objects into the WebView.
// This makes the annotated methods in these objects accessible from JavaScript.
JavascriptInjector javascriptInjector = JavascriptInjector.fromWebContents(webContents, false);
if (javascriptInjector == null) {
Log.w(TAG, "javascriptInjector is null, failed to init Java Bridge.");
return;
}

javascriptInjector.setAllowInspection(true);
for (CobaltJavaScriptAndroidObject javascriptAndroidObject : javaScriptAndroidObjectList) {
javascriptInjector.addPossiblyUnsafeInterface(javascriptAndroidObject, javascriptAndroidObject.getJavaScriptInterfaceName(), CobaltJavaScriptInterface.class);
}

// 3. Load and evaluate JavaScript code that interacts with the injected Java objects.
for (CobaltJavaScriptAndroidObject javaScriptAndroidObject : javaScriptAndroidObjectList) {
String jsFileName = javaScriptAndroidObject.getJavaScriptAssetName();
if (jsFileName != null) {
String jsCode = AssetLoader.loadJavaScriptFromAssets(this, jsFileName);
webContents.evaluateJavaScript(jsCode, null);
}
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package dev.cobalt.coat.javabridge;

import androidx.annotation.Nullable;

/**
* Interface for Android objects that are exposed to JavaScript.
*/
public interface CobaltJavaScriptAndroidObject {

/**
* Gets the name used to expose this object to JavaScript.
* This name is used in the `addJavascriptInterface` method of the WebView.
*
* @return The JavaScript interface name.
*/
public String getJavaScriptInterfaceName();

/**
* Gets the name of the JavaScript asset file that uses this interface.
* This allows the JavaScript code to be loaded and interact with this object.
*
* @return The name of the JavaScript asset file, or null if not applicable.
*/
public @Nullable String getJavaScriptAssetName();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package dev.cobalt.coat.javabridge;

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

import android.util.Log;

/**
* A simple example of implement CobaltJavaScriptAndroidObject.
*/
public class CobaltJavaScriptAndroidObjectExample implements CobaltJavaScriptAndroidObject {

@Override
public String getJavaScriptInterfaceName() {
return "AndroidExample";
}

@Override
public String getJavaScriptAssetName() {
return "example.js";
}

@CobaltJavaScriptInterface
public void testJavaScriptMethod() {
Log.w(TAG, "Hello world");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package dev.cobalt.coat.javabridge;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* Annotation that allows exposing methods to JavaScript. Starting from API level
* {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR1} and above, methods explicitly
* marked with this annotation are available to the Javascript code.
*/
@SuppressWarnings("javadoc")
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface CobaltJavaScriptInterface {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package dev.cobalt.util;

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

import android.content.Context;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.io.IOException;

/** Utility functions for read asset. */
public class AssetLoader {

private AssetLoader() {}

public static String loadJavaScriptFromAssets(Context context, String filename) {
try {
InputStream is = context.getAssets().open(filename);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
return new String(buffer, StandardCharsets.UTF_8);
} catch (IOException ex) {
String error = "asset " + filename + " failed to load";
Log.e(TAG, error);
return String.format("console.error('%s');", error);
}
}
}
2 changes: 2 additions & 0 deletions content/app/content_main_runner_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -675,6 +675,8 @@ int NO_STACK_PROTECTOR RunZygote(ContentMainDelegate* delegate) {
return kMainFunctions[i].function(std::move(main_params));
}

content::RenderFrameHost::AllowInjectingJavaScript();

auto exit_code = delegate->RunProcess(process_type, std::move(main_params));
DCHECK(absl::holds_alternative<int>(exit_code));
DCHECK_GE(absl::get<int>(exit_code), 0);
Expand Down

0 comments on commit 6663610

Please sign in to comment.