From 1fa1b2a20b9fbbc3971c1e8bd880ed8f032107e9 Mon Sep 17 00:00:00 2001 From: Colin Liang Date: Mon, 21 Oct 2024 16:20:36 -0700 Subject: [PATCH] Split javascript interface methods into domain objects. Change-Id: I8c9af486c107377d16316361b12be4db3d47dd17 --- .../app/src/main/assets/injected_script.js | 72 +----------- .../app/src/main/assets/platform_services.js | 70 ++++++++++++ .../java/dev/cobalt/coat/ChrobaltWebView.java | 106 +++++------------- .../cobalt/coat/ChrobaltWebViewClient.java | 64 +++++++++++ .../java/dev/cobalt/coat/StarboardBridge.java | 8 +- .../java/dev/cobalt/coat/WebAppInterface.java | 38 +------ .../android_webview/H5vccPlatformService.java | 45 ++++++++ .../JavaScriptAndroidObject.java | 10 ++ .../cobalt/util/SystemPropertiesHelper.java | 68 ++++++----- .../main/java/dev/cobalt/util/UserAgent.java | 60 ++++++++++ 10 files changed, 328 insertions(+), 213 deletions(-) create mode 100644 starboard/android/apk/app/src/main/assets/platform_services.js create mode 100644 starboard/android/apk/app/src/main/java/dev/cobalt/coat/ChrobaltWebViewClient.java create mode 100644 starboard/android/apk/app/src/main/java/dev/cobalt/coat/android_webview/H5vccPlatformService.java create mode 100644 starboard/android/apk/app/src/main/java/dev/cobalt/coat/android_webview/JavaScriptAndroidObject.java create mode 100644 starboard/android/apk/app/src/main/java/dev/cobalt/util/UserAgent.java diff --git a/starboard/android/apk/app/src/main/assets/injected_script.js b/starboard/android/apk/app/src/main/assets/injected_script.js index ac304afa164e..5a9106b10a3e 100644 --- a/starboard/android/apk/app/src/main/assets/injected_script.js +++ b/starboard/android/apk/app/src/main/assets/injected_script.js @@ -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: () => { }, @@ -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(); } diff --git a/starboard/android/apk/app/src/main/assets/platform_services.js b/starboard/android/apk/app/src/main/assets/platform_services.js new file mode 100644 index 000000000000..6dfcafea91ea --- /dev/null +++ b/starboard/android/apk/app/src/main/assets/platform_services.js @@ -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; diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ChrobaltWebView.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ChrobaltWebView.java index 1c87b5605402..ed8347f3a739 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ChrobaltWebView.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ChrobaltWebView.java @@ -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 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); @@ -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); + } } diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ChrobaltWebViewClient.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ChrobaltWebViewClient.java new file mode 100644 index 000000000000..73f306080a76 --- /dev/null +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/ChrobaltWebViewClient.java @@ -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 javaScriptAndroidObjectList; + + public ChrobaltWebViewClient(List 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); + } + +} diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java index ad241235f6a3..a119fb9b1214 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/StarboardBridge.java @@ -183,7 +183,7 @@ public void registerCobaltService(CobaltService.Factory factory) { } @SuppressWarnings("unused") - boolean hasCobaltService(String serviceName) { + public boolean hasCobaltService(String serviceName) { boolean weHaveIt =cobaltServiceFactories.get(serviceName) != null; Log.e(TAG, "From bridge, hasCobaltService:" + serviceName + " got? : " + weHaveIt); return weHaveIt; @@ -202,7 +202,7 @@ public void callbackFromService(long name, String foo) { } @SuppressWarnings("unused") - 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)); @@ -226,13 +226,13 @@ public CobaltService getOpenedCobaltService(String serviceName) { } @SuppressWarnings("unused") - void closeCobaltService(String serviceName) { + public void closeCobaltService(String serviceName) { Log.i(TAG, String.format("Close service: %s", serviceName)); cobaltServices.remove(serviceName); } // Differing impl - void sendToCobaltService(String serviceName, byte [] data) { + public void sendToCobaltService(String serviceName, byte [] data) { Log.i(TAG, String.format("Send to : %s data: %s", serviceName, Arrays.toString(data))); CobaltService service = cobaltServices.get(serviceName); if (service == null) { diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/WebAppInterface.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/WebAppInterface.java index 257afda3abdf..2185addfaf8b 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/WebAppInterface.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/WebAppInterface.java @@ -3,12 +3,9 @@ import static dev.cobalt.util.Log.TAG; 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; +import dev.cobalt.util.SystemPropertiesHelper; /** Native call bridge */ public class WebAppInterface { @@ -21,43 +18,14 @@ public class WebAppInterface { bridge = b; } - @JavascriptInterface - public boolean has_platform_service(String servicename) { - return bridge.hasCobaltService(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 String getSystemProperty(String propertyName, String defaultValue) { - return System.getProperty(propertyName, defaultValue); + return SystemPropertiesHelper.getSystemProperty(propertyName, 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())); - - return bufferedReader.readLine(); - } catch (IOException e) { - return defaultValue; - } + return SystemPropertiesHelper.getRestrictedSystemProperty(propName, defaultValue); } @JavascriptInterface diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/android_webview/H5vccPlatformService.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/android_webview/H5vccPlatformService.java new file mode 100644 index 000000000000..13e9ef30694e --- /dev/null +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/android_webview/H5vccPlatformService.java @@ -0,0 +1,45 @@ +package dev.cobalt.coat.android_webview; + +import android.util.Base64; +import android.webkit.JavascriptInterface; +import dev.cobalt.coat.StarboardBridge; + +public class H5vccPlatformService implements JavaScriptAndroidObject { + StarboardBridge bridge; + + // Instantiate the interface and set the context + public H5vccPlatformService(StarboardBridge b) { + bridge = b; + } + + @Override + public String getJavaScriptInterfaceName() { + return "Android_H5vccPlatformService"; + } + + @Override + public String getJavaScriptAssets() { + return "platform_services.js"; + } + + @JavascriptInterface + public boolean has_platform_service(String servicename) { + return bridge.hasCobaltService(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); + } +} diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/coat/android_webview/JavaScriptAndroidObject.java b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/android_webview/JavaScriptAndroidObject.java new file mode 100644 index 000000000000..50f3dc9c93d1 --- /dev/null +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/coat/android_webview/JavaScriptAndroidObject.java @@ -0,0 +1,10 @@ +package dev.cobalt.coat.android_webview; + +public interface JavaScriptAndroidObject { + + // the name is used by WebView.addJavascriptInterface (Object object, String name) + public String getJavaScriptInterfaceName(); + + // the js file name under assets + public String getJavaScriptAssets(); +} diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/util/SystemPropertiesHelper.java b/starboard/android/apk/app/src/main/java/dev/cobalt/util/SystemPropertiesHelper.java index 24a87169a039..897ce46cc30a 100644 --- a/starboard/android/apk/app/src/main/java/dev/cobalt/util/SystemPropertiesHelper.java +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/util/SystemPropertiesHelper.java @@ -16,36 +16,54 @@ import static dev.cobalt.util.Log.TAG; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; import java.lang.reflect.Method; -/** Utility class for accessing system properties via reflection. */ public class SystemPropertiesHelper { - private static Method getStringMethod; - - static { - try { - getStringMethod = - ClassLoader.getSystemClassLoader() - .loadClass("android.os.SystemProperties") - .getMethod("get", String.class); - if (getStringMethod == null) { - Log.e(TAG, "Couldn't load system properties getString"); - } - } catch (Exception exception) { - Log.e(TAG, "Exception looking up system properties methods: ", exception); + private static Method getStringMethod; + + static { + try { + getStringMethod = + ClassLoader.getSystemClassLoader() + .loadClass("android.os.SystemProperties") + .getMethod("get", String.class); + if (getStringMethod == null) { + Log.e(TAG, "Couldn't load system properties getString"); + } + } catch (Exception exception) { + Log.e(TAG, "Exception looking up system properties methods: ", exception); + } + } + + private SystemPropertiesHelper() {} + + public static String getString(String property) { + if (getStringMethod != null) { + try { + return (String) getStringMethod.invoke(null, new Object[] {property}); + } catch (Exception exception) { + Log.e(TAG, "Exception getting system property: ", exception); + } + } + return null; } - } - private SystemPropertiesHelper() {} + public static String getRestrictedSystemProperty(String propName, String defaultValue) { + try { + Process process = Runtime.getRuntime().exec("getprop " + propName); + BufferedReader bufferedReader = + new BufferedReader(new InputStreamReader(process.getInputStream())); + + return bufferedReader.readLine(); + } catch (IOException e) { + return defaultValue; + } + } - public static String getString(String property) { - if (getStringMethod != null) { - try { - return (String) getStringMethod.invoke(null, new Object[] {property}); - } catch (Exception exception) { - Log.e(TAG, "Exception getting system property: ", exception); - } + public static String getSystemProperty(String propertyName, String defaultValue) { + return System.getProperty(propertyName, defaultValue); } - return null; - } } diff --git a/starboard/android/apk/app/src/main/java/dev/cobalt/util/UserAgent.java b/starboard/android/apk/app/src/main/java/dev/cobalt/util/UserAgent.java new file mode 100644 index 000000000000..d4ff3ef587c2 --- /dev/null +++ b/starboard/android/apk/app/src/main/java/dev/cobalt/util/UserAgent.java @@ -0,0 +1,60 @@ +package dev.cobalt.util; + +import static dev.cobalt.util.Log.TAG; + +import android.content.Context; +import android.content.pm.PackageManager; + + +public class UserAgent { + private final Context appContext; + + public UserAgent(Context context) { + this.appContext = context; + } + + public String createUserAgentString() { + // TODO: sanitize inputs + String brand = SystemPropertiesHelper.getRestrictedSystemProperty("ro.product.brand","defaultBrand"); + String model = SystemPropertiesHelper.getRestrictedSystemProperty("ro.product.model","defaultModel"); + String firmware = SystemPropertiesHelper.getRestrictedSystemProperty("ro.build.id","defaultFirmware"); + String chipset = SystemPropertiesHelper.getRestrictedSystemProperty("ro.board.platform","defaultChipset"); + String oemKey = SystemPropertiesHelper.getRestrictedSystemProperty("ro.oem.key1","defaultModelYear"); + String integrator = SystemPropertiesHelper.getRestrictedSystemProperty("ro.product.manufacturer","defaultIntegrator"); + String androidVersion = SystemPropertiesHelper.getRestrictedSystemProperty("ro.build.version.release","defaultAndroidVersion"); + String abi = SystemPropertiesHelper.getRestrictedSystemProperty("ro.product.cpu.abi", "defaultABI"); + String aux = this.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; + } + + protected String getUserAgentAuxField() { + StringBuilder sb = new StringBuilder(); + + String packageName = appContext.getApplicationInfo().packageName; + sb.append(packageName); + sb.append('/'); + + try { + sb.append(appContext.getPackageManager().getPackageInfo(packageName, 0).versionName); + } catch (PackageManager.NameNotFoundException ex) { + // Should never happen + Log.e(TAG, "Can't find our own package", ex); + } + + return sb.toString(); + } + + +}