From 399c7ca71fee5ea9b8b16891a43030895dee8e8b Mon Sep 17 00:00:00 2001 From: Colin Liang Date: Thu, 5 Dec 2024 14:02:21 -0800 Subject: [PATCH] JavaBridge Implementation of H5vccPlatformService (#4525) 1. Fixed the race condition of injecting the java bridge object and loading the url from shell. 2. The pattern to polyfill the embeded javascript in the APK binary before Kabuki loads won't work, see context on b/379731250, we will move the javascript file chrobalt_preload.js and its sub modules on to Kabuki codebase. I will create a backlog bug for it when this code is submitted, and work with Kabuki eng to integrate the code on Kabuki. 3. A lot of H5vccPlatformService implementations are on google3. We had ClientLogInfo in the Cobalt repo, it did not cover all use cases H5vccPlatformService provides. I expand it to cover all use cases. b/379731250 b/379184982 b/378571210 --------- Co-authored-by: Colin Liang --- cobalt/android/BUILD.gn | 3 +- .../assets/html_media_element_extension.js | 8 -- .../amati_device_inspector.js | 0 .../src/kabuki_polyfill/chrobalt_preload.js | 7 ++ .../kabuki_polyfill/h5vcc_platform_service.js | 74 +++++++++++++++++++ .../html_media_element_extension.js | 11 +++ .../java/dev/cobalt/coat/CobaltActivity.java | 55 +++++++------- .../java/dev/cobalt/coat/CobaltService.java | 20 +++-- .../java/dev/cobalt/coat/StarboardBridge.java | 32 +++++--- .../coat/javabridge/AmatiDeviceInspector.java | 5 -- .../CobaltJavaScriptAndroidObject.java | 10 --- .../coat/javabridge/H5vccPlatformService.java | 64 ++++++++++++++++ .../javabridge/HTMLMediaElementExtension.java | 5 -- .../services/clientloginfo/ClientLogInfo.java | 31 +++++++- .../clientloginfo/chrobalt_preload.js | 5 ++ .../clientloginfo/client_log_info_demo.html | 15 +++- .../clientloginfo/h5vcc_platform_service.js | 74 +++++++++++++++++++ 17 files changed, 338 insertions(+), 81 deletions(-) delete mode 100644 cobalt/android/apk/app/src/app/assets/html_media_element_extension.js rename cobalt/android/apk/app/src/{app/assets => kabuki_polyfill}/amati_device_inspector.js (100%) create mode 100644 cobalt/android/apk/app/src/kabuki_polyfill/chrobalt_preload.js create mode 100644 cobalt/android/apk/app/src/kabuki_polyfill/h5vcc_platform_service.js create mode 100644 cobalt/android/apk/app/src/kabuki_polyfill/html_media_element_extension.js create mode 100644 cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/H5vccPlatformService.java create mode 100644 cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/chrobalt_preload.js create mode 100644 cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/h5vcc_platform_service.js diff --git a/cobalt/android/BUILD.gn b/cobalt/android/BUILD.gn index 0954e82ee17f1..c8b281c02945d 100644 --- a/cobalt/android/BUILD.gn +++ b/cobalt/android/BUILD.gn @@ -68,6 +68,7 @@ android_library("cobalt_apk_java") { "apk/app/src/main/java/dev/cobalt/coat/javabridge/AmatiDeviceInspector.java", "apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptAndroidObject.java", "apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptInterface.java", + "apk/app/src/main/java/dev/cobalt/coat/javabridge/H5vccPlatformService.java", "apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java", # "apk/app/src/main/java/dev/cobalt/coat/CobaltMediaSession.java", @@ -115,8 +116,6 @@ android_library("cobalt_apk_java") { android_assets("cobalt_apk_assets") { testonly = true sources = [ - "apk/app/src/app/assets/amati_device_inspector.js", - "apk/app/src/app/assets/html_media_element_extension.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", diff --git a/cobalt/android/apk/app/src/app/assets/html_media_element_extension.js b/cobalt/android/apk/app/src/app/assets/html_media_element_extension.js deleted file mode 100644 index 0bc185b69cef4..0000000000000 --- a/cobalt/android/apk/app/src/app/assets/html_media_element_extension.js +++ /dev/null @@ -1,8 +0,0 @@ -/** - * @license - * Copyright The Cobalt Authors. - * SPDX-License-Identifier: Apache-2.0 - */ - -HTMLMediaElement.prototype.canPlayType = HTMLMediaElementExtension.canPlayType; -console.log("HTMLMediaElement.canPlayType has been overwritten"); diff --git a/cobalt/android/apk/app/src/app/assets/amati_device_inspector.js b/cobalt/android/apk/app/src/kabuki_polyfill/amati_device_inspector.js similarity index 100% rename from cobalt/android/apk/app/src/app/assets/amati_device_inspector.js rename to cobalt/android/apk/app/src/kabuki_polyfill/amati_device_inspector.js diff --git a/cobalt/android/apk/app/src/kabuki_polyfill/chrobalt_preload.js b/cobalt/android/apk/app/src/kabuki_polyfill/chrobalt_preload.js new file mode 100644 index 0000000000000..6d529d14d28a4 --- /dev/null +++ b/cobalt/android/apk/app/src/kabuki_polyfill/chrobalt_preload.js @@ -0,0 +1,7 @@ +import { initializeH5vccPlatformService } from './h5vcc_platform_service.js'; +import { initializeHTMLMediaElement } from './html_media_element_extension.js'; + +export function chrobaltPreload() { + initializeH5vccPlatformService(); + initializeHTMLMediaElement(); +} diff --git a/cobalt/android/apk/app/src/kabuki_polyfill/h5vcc_platform_service.js b/cobalt/android/apk/app/src/kabuki_polyfill/h5vcc_platform_service.js new file mode 100644 index 0000000000000..b8ba198a423e8 --- /dev/null +++ b/cobalt/android/apk/app/src/kabuki_polyfill/h5vcc_platform_service.js @@ -0,0 +1,74 @@ +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 +} + +function base64ToArrayBuffer(base64) { + const binaryString = window.atob(base64); // Decode Base64 string to binary string + const bytes = new Uint8Array(binaryString.length); + for (let i = 0; i < binaryString.length; i++) { + bytes[i] = binaryString.charCodeAt(i); + } + return bytes.buffer; +} + +class PlatformServiceClient { + constructor(name) { + this.name = name; + } + + send(data) { + // convert the ArrayBuffer to base64 string because java bridge only takes primitive types as input. + const convertToB64 = arrayBufferToBase64(data); + const responseData = Android_H5vccPlatformService.platformServiceSend(this.name, convertToB64); + if (responseData === "") { + return null; + } + + // response_data has the synchronize response data converted to base64 string. + // convert it to ArrayBuffer, and return the ArrayBuffer to client. + return base64ToArrayBuffer(responseData); + } + + close() { + Android_H5vccPlatformService.closePlatformService(this.name); + } +} + +export function initializeH5vccPlatformService() { + if (typeof Android_H5vccPlatformService === 'undefined') { + return; + } + + // On Chrobalt, register window.H5vccPlatformService + window.H5vccPlatformService = { + // Holds the callback functions for the platform services when open() is called. + callbacks: { + }, + callbackFromAndroid: (serviceID, dataFromJava) => { + const arrayBuffer = base64ToArrayBuffer(dataFromJava); + window.H5vccPlatformService.callbacks[serviceID].callback(serviceID, arrayBuffer); + }, + has: (name) => { + return Android_H5vccPlatformService.hasPlatformService(name); + }, + open: function(name, callback) { + if (typeof callback !== 'function') { + throw new Error("window.H5vccPlatformService.open(), missing or invalid callback 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.openPlatformService(serviceID, name); + return new PlatformServiceClient(name); + }, + } +} diff --git a/cobalt/android/apk/app/src/kabuki_polyfill/html_media_element_extension.js b/cobalt/android/apk/app/src/kabuki_polyfill/html_media_element_extension.js new file mode 100644 index 0000000000000..2a1aef3dfe461 --- /dev/null +++ b/cobalt/android/apk/app/src/kabuki_polyfill/html_media_element_extension.js @@ -0,0 +1,11 @@ +/** + * @license + * Copyright The Cobalt Authors. + * SPDX-License-Identifier: Apache-2.0 + */ + +export function initializeHTMLMediaElement() { + if (typeof HTMLMediaElementExtension !== 'undefined') { + HTMLMediaElement.prototype.canPlayType = HTMLMediaElementExtension.canPlayType; + } +} diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java index b04f7bdff3554..1e1b4ef793629 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltActivity.java @@ -24,8 +24,6 @@ import android.media.AudioManager; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; import android.text.TextUtils; import android.util.Pair; import android.view.KeyEvent; @@ -36,14 +34,13 @@ import android.widget.LinearLayout; import android.widget.Toast; import dev.cobalt.app.CobaltApplication; -import dev.cobalt.coat.javabridge.AmatiDeviceInspector; import dev.cobalt.coat.javabridge.CobaltJavaScriptAndroidObject; import dev.cobalt.coat.javabridge.CobaltJavaScriptInterface; +import dev.cobalt.coat.javabridge.H5vccPlatformService; import dev.cobalt.coat.javabridge.HTMLMediaElementExtension; import dev.cobalt.media.AudioOutputManager; 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; @@ -81,7 +78,6 @@ public abstract class CobaltActivity extends Activity { private static final Pattern URL_PARAM_PATTERN = Pattern.compile("^[a-zA-Z0-9_=]*$"); - public static final int JAVA_BRIDGE_INITIALIZATION_DELAY_MILLI_SECONDS = 100; // Maintain the list of JavaScript-exposed objects as a member variable // to prevent them from being garbage collected prematurely. private List javaScriptAndroidObjectList = new ArrayList<>(); @@ -229,7 +225,14 @@ private void finishInitialization(Bundle savedInstanceState) { } // Set to overlay video mode. mShellManager.getContentViewRenderView().setOverlayVideoMode(true); - mShellManager.launchShell(shellUrl); + + // Load an empty page to let shell create WebContents. + mShellManager.launchShell(""); + // Inject JavaBridge objects to the WebContents. + initializeJavaBridge(); + // Load the `url` with the same shell we created above. + Log.i(TAG, "shellManager load url:" + shellUrl); + mShellManager.getActiveShell().loadUrl(shellUrl); toggleFullscreenMode(true); } @@ -361,8 +364,6 @@ protected void onCreate(Bundle savedInstanceState) { videoSurfaceView = new VideoSurfaceView(this); addContentView( videoSurfaceView, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); - - initializeJavaBridge(); } /** @@ -374,24 +375,14 @@ 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; + throw new RuntimeException("webContents is null in initializeJavaBridge. This should never happen."); } // --- Initialize the Java Bridge --- // 1. Gather all Java objects that need to be exposed to JavaScript. // TODO(b/379701165): consider to refine the way to add JavaScript interfaces. - javaScriptAndroidObjectList.add(new AmatiDeviceInspector(this)); + javaScriptAndroidObjectList.add(new H5vccPlatformService(this, getStarboardBridge())); javaScriptAndroidObjectList.add(new HTMLMediaElementExtension(this)); // 2. Use JavascriptInjector to inject Java objects into the WebContents. @@ -408,16 +399,6 @@ public void run() { 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) { - Log.d(TAG, "Evaluate JavaScript from Asset:" + jsFileName); - String jsCode = AssetLoader.loadJavaScriptFromAssets(this, jsFileName); - webContents.evaluateJavaScript(jsCode, null); - } - } } /** @@ -695,4 +676,18 @@ public void onLowMemory() { public long getAppStartTimestamp() { return timeInNanoseconds; } + + public void evaluateJavaScript(String jsCode) { + // evaluateJavaScript must run on UI thread. + runOnUiThread( + new Runnable() { + @Override + public void run() { + WebContents webContents = getActiveWebContents(); + if (webContents != null) { + webContents.evaluateJavaScript(jsCode, null); + } + } + }); + } } diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java index f63f1e3aa2d2c..53ba0ec73f1b7 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/CobaltService.java @@ -16,6 +16,7 @@ import static dev.cobalt.util.Log.TAG; +import android.util.Base64; import dev.cobalt.util.Log; import dev.cobalt.util.UsedByNative; @@ -24,6 +25,8 @@ public abstract class CobaltService { // Indicate is the service opened, and be able to send data to client protected boolean opened = true; private final Object lock = new Object(); + private StarboardBridge bridge; + protected CobaltActivity cobaltActivity; /** Interface that returns an object that extends CobaltService. */ public interface Factory { @@ -37,6 +40,10 @@ public interface Factory { /** Take in a reference to StarboardBridge & use it as needed. Default behavior is no-op. */ public void receiveStarboardBridge(StarboardBridge bridge) {} + public void setCobaltActivity(CobaltActivity cobaltActivity) { + this.cobaltActivity = cobaltActivity; + } + // Lifecycle /** Prepare service for start or resume. */ public abstract void beforeStartOrResume(); @@ -87,13 +94,16 @@ public void onClose() { /** * Send data from the service to the client. - * */ protected void sendToClient(long nativeService, byte[] data) { - // TODO(b/372558900): Implement Javascript Injection - } + if (this.cobaltActivity == null) { + Log.e(TAG, "CobaltActivity is null, can not run evaluateJavaScript()"); + return; + } - private void nativeSendToClient(long nativeService, byte[] data) { - // TODO(b/372558900): Implement Javascript Injection + // Use Base64.NO_WRAP instead of Base64.DEFAULT to avoid adding a new line. + String base64Data = Base64.encodeToString(data, Base64.NO_WRAP); + String jsCode = String.format("window.H5vccPlatformService.callbackFromAndroid(%d, '%s');", nativeService, base64Data); + this.cobaltActivity.evaluateJavaScript(jsCode); } } diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java index e8af16173ea42..731f3015ed2f4 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java @@ -735,15 +735,11 @@ public void registerCobaltService(CobaltService.Factory factory) { cobaltServiceFactories.put(factory.getServiceName(), factory); } - @SuppressWarnings("unused") - @UsedByNative - boolean hasCobaltService(String serviceName) { + public boolean hasCobaltService(String serviceName) { return cobaltServiceFactories.get(serviceName) != null; } - @SuppressWarnings("unused") - @UsedByNative - CobaltService openCobaltService(long nativeService, String serviceName) { + public CobaltService openCobaltService(long nativeService, String serviceName) { if (cobaltServices.get(serviceName) != null) { // Attempting to re-open an already open service fails. Log.e(TAG, String.format("Cannot open already open service %s", serviceName)); @@ -758,6 +754,11 @@ CobaltService openCobaltService(long nativeService, String serviceName) { if (service != null) { service.receiveStarboardBridge(this); cobaltServices.put(serviceName, service); + + Activity activity = activityHolder.get(); + if (activity instanceof CobaltActivity) { + service.setCobaltActivity((CobaltActivity) activity); + } } return service; } @@ -766,12 +767,25 @@ public CobaltService getOpenedCobaltService(String serviceName) { return cobaltServices.get(serviceName); } - @SuppressWarnings("unused") - @UsedByNative - void closeCobaltService(String serviceName) { + public void closeCobaltService(String serviceName) { cobaltServices.remove(serviceName); } + public byte[] sendToCobaltService(String serviceName, byte [] data) { + CobaltService service = cobaltServices.get(serviceName); + if (service == null) { + Log.e(TAG, String.format("Service not opened: %s", serviceName)); + return null; + } + CobaltService.ResponseToClient response = service.receiveFromClient(data); + if (response.invalidState) { + Log.e(TAG, String.format("Service %s received invalid data, closing.", serviceName)); + closeCobaltService(serviceName); + return null; + } + return response.data; + } + /** Returns the application start timestamp. */ @SuppressWarnings("unused") @UsedByNative diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/AmatiDeviceInspector.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/AmatiDeviceInspector.java index 66e4633adbc81..85e9cebc4d31c 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/AmatiDeviceInspector.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/AmatiDeviceInspector.java @@ -37,11 +37,6 @@ public String getJavaScriptInterfaceName() { return "Android_AmatiDeviceInspector"; } - @Override - public String getJavaScriptAssetName() { - return "amati_device_inspector.js"; - } - @CobaltJavaScriptInterface public void printIsAmatiDevice() { boolean isAmatiDevice = context.getPackageManager().hasSystemFeature("com.google.android.feature.AMATI_EXPERIENCE"); diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptAndroidObject.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptAndroidObject.java index ab17b10f56969..175ecaf25aefd 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptAndroidObject.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/CobaltJavaScriptAndroidObject.java @@ -14,8 +14,6 @@ package dev.cobalt.coat.javabridge; -import androidx.annotation.Nullable; - /** * Interface for Android objects that are exposed to JavaScript. */ @@ -28,12 +26,4 @@ public interface CobaltJavaScriptAndroidObject { * @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(); } diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/H5vccPlatformService.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/H5vccPlatformService.java new file mode 100644 index 0000000000000..c5c4f9a9af347 --- /dev/null +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/H5vccPlatformService.java @@ -0,0 +1,64 @@ +// Copyright 2024 The Cobalt Authors. All Rights Reserved. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package dev.cobalt.coat.javabridge; + +import android.content.Context; +import android.util.Base64; +import dev.cobalt.coat.StarboardBridge; + +/** + * H5vccPlatformService JavaScript object. + */ +public class H5vccPlatformService implements CobaltJavaScriptAndroidObject { + + private final Context context; + private final StarboardBridge bridge; + + public H5vccPlatformService(Context context, StarboardBridge bridge) { + this.context = context; + this.bridge = bridge; + } + + @Override + public String getJavaScriptInterfaceName() { + return "Android_H5vccPlatformService"; + } + + @CobaltJavaScriptInterface + public boolean hasPlatformService(String servicename) { + return bridge.hasCobaltService(servicename); + } + + @CobaltJavaScriptInterface + public void openPlatformService(long serviceId, String servicename) { + bridge.openCobaltService(serviceId, servicename); + } + + @CobaltJavaScriptInterface + public void closePlatformService(String servicename) { + bridge.closeCobaltService(servicename); + } + + @CobaltJavaScriptInterface + public String platformServiceSend(String servicename, String base64Data) { + byte[] data = Base64.decode(base64Data, Base64.DEFAULT); + byte[] result = bridge.sendToCobaltService(servicename, data); + if (result == null) { + return ""; + } + + return Base64.encodeToString(result, Base64.DEFAULT); + } +} diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java index 528819d1a9a46..09b7129add73a 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/coat/javabridge/HTMLMediaElementExtension.java @@ -30,11 +30,6 @@ public String getJavaScriptInterfaceName() { return "HTMLMediaElementExtension"; } - @Override - public String getJavaScriptAssetName() { - return "html_media_element_extension.js"; - } - @CobaltJavaScriptInterface public String canPlayType(String mimeType, String keySystem) { return nativeCanPlayType(mimeType, keySystem); diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/ClientLogInfo.java b/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/ClientLogInfo.java index b9eb75005b5f8..a4e9bf01dfce8 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/ClientLogInfo.java +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/ClientLogInfo.java @@ -7,6 +7,10 @@ import dev.cobalt.util.DisplayUtil; import dev.cobalt.util.Log; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; + + /** ClientLogInfo to report Android API support on android devices. */ public class ClientLogInfo extends CobaltService { public static final String TAG = "ClientLogInfo"; @@ -15,9 +19,15 @@ public class ClientLogInfo extends CobaltService { protected static final String SERVICE_NAME = "dev.cobalt.coat.clientloginfo"; private static String clientInfo = ""; + private final long nativeService; + private final ThreadPoolExecutor executor; public ClientLogInfo(Context appContext, long nativeService) { Log.i(TAG, "Opening ClientLogInfo"); + this.nativeService = nativeService; + + // Create a ThreadPoolExecutor with a fixed number of threads + this.executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(1); } @Override @@ -31,13 +41,28 @@ public void afterStopped() {} @Override public ResponseToClient receiveFromClient(byte[] data) { + String dataString = new String(data, UTF_8); + Log.i(TAG, "Received data from platform service client:" + dataString); + ResponseToClient response = new ResponseToClient(); response.invalidState = false; - String responseString = - "displayRefreshRate:" + DisplayUtil.getDefaultDisplayRefreshRate() + ";"; - responseString += clientInfo; + final String responseString = + "displayRefreshRate:" + DisplayUtil.getDefaultDisplayRefreshRate() + ";" + clientInfo; + + // synchronize response response.data = responseString.getBytes(UTF_8); + + // Submit a Runnable task to send async response + executor.execute( + () -> { + String asynResponseString = "async response: " + responseString; + Log.i(TAG, "Platform service send async responseString:" + asynResponseString); + sendToClient(nativeService, asynResponseString.getBytes(UTF_8)); + } + ); + + Log.i(TAG, "Platform service send sync responseString:" + responseString); return response; } diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/chrobalt_preload.js b/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/chrobalt_preload.js new file mode 100644 index 0000000000000..b1fd4d96fdced --- /dev/null +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/chrobalt_preload.js @@ -0,0 +1,5 @@ +import { initializeH5vccPlatformService } from './h5vcc_platform_service.js'; + +export function chrobaltPreload() { + initializeH5vccPlatformService(); +} diff --git a/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/client_log_info_demo.html b/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/client_log_info_demo.html index 79b7993ff8a41..673338d8bb0e1 100644 --- a/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/client_log_info_demo.html +++ b/cobalt/android/apk/app/src/main/java/dev/cobalt/libraries/services/clientloginfo/client_log_info_demo.html @@ -9,7 +9,10 @@ -