Preliminary Android build support (#31086)

* Android build

* Fixes
* More fixes
  - Still failing in the linking step
* More work on getting linking working

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* android: use mozjs with ndk r25c. loads servo.org

more android build fixes.

* fix ./mach run for android and make it follow logs

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* add experimental logic for compositor pause/resume

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* pass DPI from android to simpleservo

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* ci: add android workflow

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* switch to ANDROID_SDK_ROOT and ANDROID_NDK_ROOT vars

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* upgrade gradle to 4.10.1

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* upgrade to gradle 5.1.1

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* upgrade to gradle 8 and agp 8

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* make compositing work again with external present

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* android: improve mach support for non-NixOS and CI

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* fix sampler compilation bug introduced in #30490

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* ci: add android build to main workflow

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* gradle: set MinSdk = targetSdk = 30

NDK requires we compile against the minSdk API level
which is 30 in our case.

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* add instructions for android in README.md

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* apk: move servosurface to servoview

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* apk: uncomment the mediasession callbacks on MainActivity

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* apk: fix crash on MainAtivity.onDestroy

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* apk: drop VR, arm 5 and unused code

This commit drops:
* support for google, oculusvr
* support for arm5 architecture

and also removes
* fakeld scripts
* unused java code

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* cleanup shell.nix

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* android: add FIXMEs for gstreamer code

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* apk: remove commented code and debug logs

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* cleanup ServoView.java

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* mach: comment call to download gstreamer deps for android

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* disable bluetooth for jniapi as blurdroid is broken

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* fixup! README.md

* fixup! remove change in Cargo.toml

* fixup! move shell variables together

* fixup! cleanup jniapi/Cargo.toml comments

* delete commented gstreamer related android code

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* remove unused config variable in servbuild

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* android: more cleanup

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* force no_static_freetype only for android

* use actions to manage sdk, ndk and java

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* rename embedder event names to be more clear.

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* link to startup crash issue

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* fix lint issues

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* upgrade env_logger to 0.10 with duplicate exception

libservo and android_logger can use env_logger 0.10
but quickcheck is still stuck on 0.8 and has not seen
any activity in the last 2 years. This commit adds
a duplicate exception until the quickcheck dependency
can be upgraded (or replaced)

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* android: fix comments

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* disable jemalloc on android

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>

* fixup! replace linux with android in cfg

---------

Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Mukilan Thiyagarajan 2024-01-22 18:30:15 +05:30 committed by GitHub
parent 8e6bdb69b1
commit d7de206dbd
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
58 changed files with 923 additions and 1382 deletions

View file

@ -18,6 +18,7 @@ use servo::compositing::windowing::{
AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent,
WindowMethods,
};
use servo::compositing::CompositeTarget;
use servo::config::prefs::pref_map;
pub use servo::config::prefs::{add_user_prefs, PrefValue};
use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods};
@ -298,7 +299,12 @@ pub fn init(
gl: gl.clone(),
});
let servo = Servo::new(embedder_callbacks, window_callbacks.clone(), None);
let servo = Servo::new(
embedder_callbacks,
window_callbacks.clone(),
None,
CompositeTarget::Window,
);
SERVO.with(|s| {
let mut servo_glue = ServoGlue {
@ -569,6 +575,24 @@ impl ServoGlue {
self.process_event(EmbedderEvent::Keyboard(key_event))
}
pub fn pause_compositor(&mut self) -> Result<(), &'static str> {
self.process_event(EmbedderEvent::InvalidateNativeSurface)
}
pub fn resume_compositor(
&mut self,
native_surface: *mut c_void,
coords: Coordinates,
) -> Result<(), &'static str> {
if native_surface.is_null() {
panic!("null passed for native_surface");
}
self.process_event(EmbedderEvent::ReplaceNativeSurface(
native_surface,
coords.framebuffer,
))
}
pub fn media_session_action(
&mut self,
action: MediaSessionActionType,
@ -789,6 +813,9 @@ impl ServoGlue {
EmbedderMsg::Panic(reason, backtrace) => {
self.callbacks.host_callbacks.on_panic(reason, backtrace);
},
EmbedderMsg::ReadyToPresent => {
self.servo.present();
},
EmbedderMsg::Status(..) |
EmbedderMsg::SelectFiles(..) |
EmbedderMsg::MoveTo(..) |
@ -798,7 +825,6 @@ impl ServoGlue {
EmbedderMsg::NewFavicon(..) |
EmbedderMsg::HeadParsed |
EmbedderMsg::SetFullscreenState(..) |
EmbedderMsg::ReadyToPresent |
EmbedderMsg::ReportProfile(..) |
EmbedderMsg::EventDelivered(..) => {},
}

View file

@ -1,7 +1,6 @@
[package]
name = "simpleservo_jniapi"
version = "0.0.1"
build = "build.rs"
authors = ["The Servo Project Developers"]
license = "MPL-2.0"
edition = "2018"
@ -14,21 +13,21 @@ test = false
bench = false
[dependencies]
android_injected_glue = "0.2"
android_logger = "0.10"
gstreamer = { workspace = true }
android_logger = "0.13"
jni = "0.18.0"
libc = { workspace = true }
log = { workspace = true }
serde_json = { workspace = true }
simpleservo = { path = "../api" }
# TODO: Once the native-bluetooth feature works for
# Android, remove the explicit feature list here.
simpleservo = { path = "../api", default-features = false, features = ["max_log_level", "webdriver"] }
[build-dependencies]
cc = "1.0"
[features]
debugmozjs = ["simpleservo/debugmozjs"]
default = ["max_log_level", "native-bluetooth", "webdriver"]
default = ["max_log_level", "webdriver", "no_static_freetype"]
googlevr = ["simpleservo/googlevr"]
js_backtrace = ["simpleservo/js_backtrace"]
max_log_level = ["simpleservo/max_log_level"]
@ -36,3 +35,4 @@ media-gstreamer = ["simpleservo/media-gstreamer"]
native-bluetooth = ["simpleservo/native-bluetooth"]
webdriver = ["simpleservo/webdriver"]
webgl_backtrace = ["simpleservo/webgl_backtrace"]
no_static_freetype = ["simpleservo/no_static_freetype"]

View file

@ -1,33 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::env;
use std::path::Path;
fn main() {
// Get the NDK path from NDK_HOME env.
let ndk_path =
env::var_os("ANDROID_NDK").expect("Please set the ANDROID_NDK environment variable");
let ndk_path = Path::new(&ndk_path);
// compiling android_native_app_glue.c
let c_file = ndk_path
.join("sources")
.join("android")
.join("native_app_glue")
.join("android_native_app_glue.c");
cc::Build::new()
.file(c_file)
.warnings(false)
.compile("android_native_app_glue");
// Get the output directory.
let out_dir =
env::var("OUT_DIR").expect("Cargo should have set the OUT_DIR environment variable");
println!("cargo:rustc-link-lib=static=android_native_app_glue");
println!("cargo:rustc-link-search=native={}", out_dir);
println!("cargo:rustc-link-lib=log");
println!("cargo:rustc-link-lib=android");
}

View file

@ -5,21 +5,18 @@
#![allow(non_snake_case)]
use std::os::raw::{c_char, c_int, c_void};
use std::ptr::{null, null_mut};
use std::sync::Arc;
use std::thread;
use android_logger::{self, Filter};
use gstreamer::debug_set_threshold_from_string;
use android_logger::{self, Config, FilterBuilder};
use jni::objects::{GlobalRef, JClass, JObject, JString, JValue};
use jni::sys::{jboolean, jfloat, jint, jstring, JNI_TRUE};
use jni::{errors, JNIEnv, JavaVM};
use jni::{JNIEnv, JavaVM};
use libc::{dup2, pipe, read};
use log::Level;
use log::{debug, error, info, warn};
use simpleservo::{
self, self, deinit, gl_glue, gl_glue, Coordinates, DeviceIntRect, EventLoopWaker, HostTrait,
InitOptions, InputMethodType, MediaSessionPlaybackState, MouseButton, PromptResult, ServoGlue,
ServoGlue, VRInitOptions, SERVO, SERVO,
self, gl_glue, Coordinates, DeviceIntRect, EventLoopWaker, HostTrait, InitOptions,
InputMethodType, MediaSessionPlaybackState, PromptResult, ServoGlue, SERVO,
};
struct HostCallbacks {
@ -27,6 +24,19 @@ struct HostCallbacks {
jvm: JavaVM,
}
extern "C" {
fn ANativeWindow_fromSurface(env: *mut jni::sys::JNIEnv, surface: JObject) -> *mut c_void;
}
#[no_mangle]
pub fn android_main() {
// FIXME(mukilan): this android_main is only present to stop
// the java side 'System.loadLibrary('simpleservo') call from
// failing due to undefined reference to android_main introduced
// by winit's android-activity crate. There is no way to disable
// this currently.
}
fn call<F>(env: &JNIEnv, f: F)
where
F: Fn(&mut ServoGlue) -> Result<(), &str>,
@ -51,11 +61,12 @@ pub fn Java_org_mozilla_servoview_JNIServo_version(env: JNIEnv, _class: JClass)
pub fn Java_org_mozilla_servoview_JNIServo_init(
env: JNIEnv,
_: JClass,
activity: JObject,
_activity: JObject,
opts: JObject,
callbacks_obj: JObject,
surface: JObject,
) {
let (mut opts, log, log_str, gst_debug_str) = match get_options(&env, opts) {
let (mut opts, log, log_str, _gst_debug_str) = match get_options(&env, opts, surface) {
Ok((opts, log, log_str, gst_debug_str)) => (opts, log, log_str, gst_debug_str),
Err(err) => {
throw(&env, &err);
@ -79,26 +90,26 @@ pub fn Java_org_mozilla_servoview_JNIServo_init(
"compositing::compositor",
"constellation::constellation",
];
let mut filter = Filter::default().with_min_level(Level::Debug);
let mut filter_builder = FilterBuilder::new();
for &module in &filters {
filter = filter.with_allowed_module_path(module);
filter_builder.filter_module(module, log::LevelFilter::Debug);
}
if let Some(log_str) = log_str {
for module in log_str.split(',') {
filter = filter.with_allowed_module_path(module);
filter_builder.filter_module(module, log::LevelFilter::Debug);
}
}
if let Some(gst_debug_str) = gst_debug_str {
debug_set_threshold_from_string(&gst_debug_str, true);
}
android_logger::init_once(filter, Some("simpleservo"));
android_logger::init_once(
Config::default()
.with_max_level(log::LevelFilter::Debug)
.with_filter(filter_builder.build())
.with_tag("simpleservo"),
)
}
info!("init");
initialize_android_glue(&env, activity);
redirect_stdout_to_logcat();
let callbacks_ref = match env.new_global_ref(callbacks_obj) {
@ -329,11 +340,33 @@ pub fn Java_org_mozilla_servoview_JNIServo_pinchZoomEnd(
}
#[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_click(env: JNIEnv, _: JClass, x: jint, y: jint) {
pub fn Java_org_mozilla_servoview_JNIServo_click(env: JNIEnv, _: JClass, x: jfloat, y: jfloat) {
debug!("click");
call(&env, |s| s.click(x as f32, y as f32));
}
#[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_pauseCompositor(env: JNIEnv, _: JClass) {
debug!("pauseCompositor");
call(&env, |s| s.pause_compositor());
}
#[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_resumeCompositor(
env: JNIEnv,
_: JClass,
surface: JObject,
coordinates: JObject,
) {
debug!("resumeCompositor");
let widget = unsafe { ANativeWindow_fromSurface(env.get_native_interface(), surface) };
let coords = jni_coords_to_rust_coords(&env, coordinates);
match coords {
Ok(coords) => call(&env, |s| s.resume_compositor(widget, coords.clone())),
Err(error) => throw(&env, &error),
}
}
#[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_mediaSessionAction(
env: JNIEnv,
@ -379,20 +412,6 @@ impl HostCallbacks {
}
impl HostTrait for HostCallbacks {
fn flush(&self) {
debug!("flush");
let env = self.jvm.get_env().unwrap();
env.call_method(self.callbacks.as_obj(), "flush", "()V", &[])
.unwrap();
}
fn make_current(&self) {
debug!("make_current");
let env = self.jvm.get_env().unwrap();
env.call_method(self.callbacks.as_obj(), "makeCurrent", "()V", &[])
.unwrap();
}
fn prompt_alert(&self, message: String, _trusted: bool) {
debug!("prompt_alert");
let env = self.jvm.get_env().unwrap();
@ -446,9 +465,10 @@ impl HostTrait for HostCallbacks {
.unwrap();
}
fn on_title_changed(&self, title: String) {
fn on_title_changed(&self, title: Option<String>) {
debug!("on_title_changed");
let env = self.jvm.get_env().unwrap();
let title = title.unwrap_or_else(String::new);
let s = match new_string(&env, &title) {
Ok(s) => s,
Err(_) => return,
@ -529,7 +549,7 @@ impl HostTrait for HostCallbacks {
fn on_ime_show(
&self,
_type: InputEncoding,
_input_type: InputMethodType,
_text: Option<(String, i32)>,
_multiline: bool,
_rect: DeviceIntRect,
@ -610,65 +630,16 @@ impl HostTrait for HostCallbacks {
.unwrap();
}
fn on_devtools_started(&self, port: Result<u16, ()>) {
fn on_devtools_started(&self, port: Result<u16, ()>, _token: String) {
match port {
Ok(p) => info!("Devtools Server running on port {}", p),
Err(()) => error!("Error running devtools server"),
}
}
}
fn initialize_android_glue(env: &JNIEnv, activity: JObject) {
use android_injected_glue::{ffi, ANDROID_APP};
fn show_context_menu(&self, _title: Option<String>, _items: Vec<String>) {}
// From jni-rs to android_injected_glue
let clazz = Box::leak(Box::new(env.new_global_ref(activity).unwrap()));
let activity = Box::into_raw(Box::new(ffi::ANativeActivity {
clazz: clazz.as_obj().into_inner() as *mut c_void,
vm: env.get_java_vm().unwrap().get_java_vm_pointer() as *mut ffi::_JavaVM,
callbacks: null_mut(),
env: null_mut(),
internalDataPath: null(),
externalDataPath: null(),
sdkVersion: 0,
instance: null_mut(),
assetManager: null_mut(),
obbPath: null(),
}));
extern "C" fn on_app_cmd(_: *mut ffi::android_app, _: i32) {}
extern "C" fn on_input_event(_: *mut ffi::android_app, _: *const c_void) -> i32 {
0
}
let app = Box::into_raw(Box::new(ffi::android_app {
activity,
onAppCmd: on_app_cmd,
onInputEvent: on_input_event,
userData: null_mut(),
config: null(),
savedState: null_mut(),
savedStateSize: 0,
looper: null_mut(),
inputQueue: null(),
window: null_mut(),
contentRect: ffi::ARect {
left: 0,
top: 0,
right: 0,
bottom: 0,
},
activityState: 0,
destroyRequested: 0,
}));
unsafe {
ANDROID_APP = app;
}
fn on_panic(&self, _reason: String, _backtrace: Option<String>) {}
}
extern "C" {
@ -803,28 +774,21 @@ fn jni_coords_to_rust_coords(env: &JNIEnv, obj: JObject) -> Result<Coordinates,
fn get_field<'a>(
env: &'a JNIEnv,
obj: JObject,
obj: JObject<'a>,
field: &str,
type_: &str,
) -> Result<Option<JValue<'a>>, String> {
if env.get_field_id(obj, field, type_).is_err() {
return Err(format!("Can't find `{}` field", &field));
return Err(format!("Can't find `{}` field", field));
}
env.get_field(obj, field, type_)
.map(|value| Some(value))
.or_else(|e| match *e.kind() {
errors::ErrorKind::NullPtr(_) => Ok(None),
_ => Err(format!(
"Can't find `{}` field: {}",
&field,
e.description()
)),
})
.or_else(|_| Err(format!("Can't find `{}` field", field)))
}
fn get_non_null_field<'a>(
env: &'a JNIEnv,
obj: JObject,
obj: JObject<'a>,
field: &str,
type_: &str,
) -> Result<JValue<'a>, String> {
@ -851,6 +815,7 @@ fn get_string(env: &JNIEnv, obj: JObject, field: &str) -> Result<Option<String>,
fn get_options(
env: &JNIEnv,
opts: JObject,
surface: JObject,
) -> Result<(InitOptions, bool, Option<String>, Option<String>), String> {
let args = get_string(env, opts, "args")?;
let url = get_string(env, opts, "url")?;
@ -862,13 +827,6 @@ fn get_options(
let log = get_non_null_field(env, opts, "enableLogs", "Z")?
.z()
.map_err(|_| "enableLogs not a boolean")?;
let enable_subpixel_text_antialiasing =
get_non_null_field(env, opts, "enableSubpixelTextAntialiasing", "Z")?
.z()
.map_err(|_| "enableSubpixelTextAntialiasing not a boolean")?;
let vr_pointer = get_non_null_field(env, opts, "VRExternalContext", "J")?
.j()
.map_err(|_| "VRExternalContext is not a long")? as *mut c_void;
let coordinates = get_non_null_field(
env,
opts,
@ -885,20 +843,16 @@ fn get_options(
None => None,
};
let native_window = unsafe { ANativeWindow_fromSurface(env.get_native_interface(), surface) };
let opts = InitOptions {
args: args.unwrap_or(vec![]),
url,
coordinates,
density,
enable_subpixel_text_antialiasing,
vr_init: if vr_pointer.is_null() {
VRInitOptions::None
} else {
VRInitOptions::VRExternal(vr_pointer)
},
xr_discovery: None,
gl_context_pointer: None,
native_display_pointer: None,
surfman_integration: simpleservo::SurfmanIntegration::Widget(native_window),
prefs: None,
};
Ok((opts, log, log_str, gst_debug_str))
}