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

@ -1,26 +1,17 @@
[target.arm-linux-androideabi] [target.aarch64-linux-android]
linker = "./support/android/fakeld/fake-ld-arm.sh" linker = "aarch64-linux-android30-clang"
ar = "arm-linux-androideabi-ar"
[target.armv7-linux-androideabi] [target.armv7-linux-androideabi]
linker = "./support/android/fakeld/fake-ld-armv7.sh" linker = "armv7a-linux-androideabi30-clang"
ar = "arm-linux-androideabi-ar"
[target.aarch64-linux-android] [target.armv-linux-androideabi]
linker = "./support/android/fakeld/fake-ld-arm64.sh" linker = "armv7a-linux-androideabi30-clang"
ar = "aarch64-linux-android-ar"
[target.i686-linux-android] [target.i686-linux-android]
linker = "./support/android/fakeld/fake-ld-x86.sh" linker = "i686-linux-android30-clang"
ar = "i686-linux-android-ar"
[target.arm-unknown-linux-gnueabihf] [target.x86_64-linux-android]
linker = "arm-linux-gnueabihf-gcc" linker = "x86_64-linux-android30-clang"
ar = "arm-linux-gnueabihf-ar"
[target.aarch64-unknown-linux-gnu]
linker = "aarch64-linux-gnu-gcc"
ar = "aarch64-linux-gnu-ar"
[target.x86_64-pc-windows-msvc] [target.x86_64-pc-windows-msvc]
linker = "lld-link.exe" linker = "lld-link.exe"

107
.github/workflows/android.yml vendored Normal file
View file

@ -0,0 +1,107 @@
name: Android
on:
workflow_call:
inputs:
profile:
required: false
default: "release"
type: string
workflow_dispatch:
inputs:
profile:
required: false
default: "release"
type: string
options: ["release", "debug", "production"]
push:
branches: ["try-android"]
env:
RUST_BACKTRACE: 1
SHELL: /bin/bash
SCCACHE_GHA_ENABLED: "true"
CCACHE: "sccache"
CARGO_INCREMENTAL: 0
jobs:
build:
name: Android Build
runs-on: ubuntu-22.04
strategy:
matrix:
arch: ['armv7-linux-androideabi', 'i686-linux-android']
steps:
- uses: actions/checkout@v3
if: github.event_name != 'issue_comment' && github.event_name != 'pull_request_target'
with:
fetch-depth: 2
# This is necessary to checkout the pull request if this run was triggered
# via an `issue_comment` action on a pull request.
- uses: actions/checkout@v3
if: github.event_name == 'issue_comment' || github.event_name == 'pull_request_target'
with:
ref: refs/pull/${{ github.event.issue.number || github.event.number }}/head
fetch-depth: 2
- name: Run sccache-cache
uses: mozilla-actions/sccache-action@v0.0.3
- name: Install taplo
uses: baptiste0928/cargo-install@v2
with:
crate: taplo-cli
locked: true
- name: Bootstrap Python
run: python3 -m pip install --upgrade pip virtualenv
- name: Bootstrap dependencies
run: sudo apt update && python3 ./mach bootstrap
- name: Set up JDK 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
- name: Setup Android SDK
uses: android-actions/setup-android@v3
with:
packages: 'tools platform-tools platforms;android-33'
- name: Install Android NDK
uses: nttld/setup-ndk@v1
id: setup-ndk
with:
ndk-version: r25c
- name: Build (arch ${{ matrix.arch }} profile ${{ inputs.profile }})
env:
ANDROID_NDK_ROOT: ${{ steps.setup-ndk.outputs.ndk-path }}
run: |
python3 ./mach build --android --target ${{ matrix.arch }} --${{ inputs.profile }}
# TODO: This is disabled since APK crashes during startup.
# See https://github.com/servo/servo/issues/31134
# - name: Script tests
# run: ./mach test-android-startup
- name: Rename build timing
run: cp -r target/cargo-timings target/cargo-timings-android-${{ matrix.arch }}
- name: Archive build timing
uses: actions/upload-artifact@v3
with:
name: cargo-timings
# Using a wildcard here ensures that the archive includes the path.
path: target/cargo-timings-*
- name: Upload APK artifact for mach package
uses: actions/upload-artifact@v3
with:
name: android-${{ matrix.arch }}-${{ inputs.profile }}
path: target/android/${{ matrix.arch }}/${{ inputs.profile }}/servoapp.apk
result:
name: Result
runs-on: ubuntu-latest
if: always()
# `needs: 'build'` is necessary to detect cancellation
needs:
- "build"
steps:
- name: Mark the job as successful
run: exit 0
if: ${{ !contains(needs.*.result, 'failure') && !contains(needs.*.result, 'cancelled') }}
- name: Mark the job as unsuccessful
run: exit 1
if: contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled')

View file

@ -76,7 +76,7 @@ jobs:
let platforms = []; let platforms = [];
if (platform == "all") { if (platform == "all") {
platforms = [ "linux", "windows", "macos" ]; platforms = [ "linux", "windows", "macos", "android" ];
} else { } else {
platforms = [ platform ]; platforms = [ platform ];
} }
@ -125,6 +125,15 @@ jobs:
unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }} unit-tests: ${{ fromJson(needs.decision.outputs.configuration).unit_tests }}
secrets: inherit secrets: inherit
build-android:
name: Android
needs: ["decision"]
if: ${{ contains(fromJson(needs.decision.outputs.configuration).platforms, 'android') }}
uses: ./.github/workflows/linux.yml
with:
profile: "release"
secrets: inherit
build-result: build-result:
name: Result name: Result
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -135,7 +144,7 @@ jobs:
- "build-win" - "build-win"
- "build-mac" - "build-mac"
- "build-linux" - "build-linux"
- "build-android"
steps: steps:
- name: Mark skipped jobs as successful - name: Mark skipped jobs as successful
if: ${{ fromJson(needs.decision.outputs.configuration).platforms[0] != null }} if: ${{ fromJson(needs.decision.outputs.configuration).platforms[0] != null }}

48
Cargo.lock generated
View file

@ -128,20 +128,20 @@ checksum = "80b9e34fcbf29c0563547cb2ecce9b49504597cad6166769b1e4efb45c6c2951"
[[package]] [[package]]
name = "android_log-sys" name = "android_log-sys"
version = "0.2.0" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85965b6739a430150bdd138e2374a98af0c3ee0d030b3bb7fc3bddff58d0102e" checksum = "5ecc8056bf6ab9892dcd53216c83d1597487d7dacac16c8df6b877d127df9937"
[[package]] [[package]]
name = "android_logger" name = "android_logger"
version = "0.10.1" version = "0.13.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9ed09b18365ed295d722d0b5ed59c01b79a826ff2d2a8f73d5ecca8e6fb2f66" checksum = "c494134f746c14dc653a35a4ea5aca24ac368529da5370ecf41fe0341c35772f"
dependencies = [ dependencies = [
"android_log-sys", "android_log-sys",
"env_logger", "env_logger 0.10.1",
"lazy_static",
"log", "log",
"once_cell",
] ]
[[package]] [[package]]
@ -1604,8 +1604,18 @@ version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3" checksum = "a19187fea3ac7e84da7dacf48de0c45d63c6a76f9490dae389aead16c243fce3"
dependencies = [ dependencies = [
"atty", "log",
"regex",
]
[[package]]
name = "env_logger"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95b3f3e67048839cb0d0781f445682a35113da7121f7c949db0e2be96a4fbece"
dependencies = [
"humantime", "humantime",
"is-terminal",
"log", "log",
"regex", "regex",
"termcolor", "termcolor",
@ -2940,6 +2950,17 @@ dependencies = [
"windows", "windows",
] ]
[[package]]
name = "is-terminal"
version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b"
dependencies = [
"hermit-abi 0.3.2",
"rustix",
"windows-sys 0.48.0",
]
[[package]] [[package]]
name = "itertools" name = "itertools"
version = "0.10.5" version = "0.10.5"
@ -3379,7 +3400,7 @@ dependencies = [
"devtools", "devtools",
"devtools_traits", "devtools_traits",
"embedder_traits", "embedder_traits",
"env_logger", "env_logger 0.10.1",
"euclid", "euclid",
"gaol", "gaol",
"gfx", "gfx",
@ -4568,7 +4589,7 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
dependencies = [ dependencies = [
"env_logger", "env_logger 0.8.4",
"log", "log",
"rand", "rand",
] ]
@ -5379,6 +5400,7 @@ version = "0.0.1"
dependencies = [ dependencies = [
"jemalloc-sys", "jemalloc-sys",
"jemallocator", "jemallocator",
"libc",
"winapi", "winapi",
] ]
@ -5592,7 +5614,7 @@ version = "0.0.1"
dependencies = [ dependencies = [
"backtrace", "backtrace",
"cbindgen", "cbindgen",
"env_logger", "env_logger 0.10.1",
"keyboard-types", "keyboard-types",
"lazy_static", "lazy_static",
"libc", "libc",
@ -5606,10 +5628,8 @@ dependencies = [
name = "simpleservo_jniapi" name = "simpleservo_jniapi"
version = "0.0.1" version = "0.0.1"
dependencies = [ dependencies = [
"android_injected_glue",
"android_logger", "android_logger",
"cc", "cc",
"gstreamer",
"jni", "jni",
"libc", "libc",
"log", "log",
@ -5906,9 +5926,9 @@ dependencies = [
[[package]] [[package]]
name = "surfman" name = "surfman"
version = "0.8.0" version = "0.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca39befaf946247c5d3323465a9ec86c4a05523bb87a0d3eb07e71c15181a338" checksum = "db2e4280229411d6eb8a8f873152dece1904df2682003bdc748adc181e003568"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags 1.3.2",
"cfg_aliases", "cfg_aliases",

View file

@ -33,7 +33,7 @@ data-url = "0.1.0"
devtools_traits = { path = "components/shared/devtools" } devtools_traits = { path = "components/shared/devtools" }
embedder_traits = { path = "components/shared/embedder" } embedder_traits = { path = "components/shared/embedder" }
encoding_rs = "0.8" encoding_rs = "0.8"
env_logger = "0.8" env_logger = "0.10"
euclid = "0.22" euclid = "0.22"
fnv = "1.0" fnv = "1.0"
fxhash = "0.2" fxhash = "0.2"

View file

@ -117,6 +117,27 @@ though of course it doesnt produce a binary you can run.
### Building for Android target ### Building for Android target
Prerequisites:
Servo's build system assumes that both the Android SDK & NDK are
already installed and expects the paths to be specified via the
environment variables `ANDROID_SDK_ROOT` and `ANDROID_NDK_ROOT`.
Servo also expects the following components are installed via
sdkmanager:
For building:
``` sh
sdkmanager install platform-tools platforms;android-33
```
To run in emulator, also install the related components:
``` sh
sdkmanager install emulator system-images;android-33;google_apis;x86
```
Build commands:
For ARM (`armv7-linux-androideabi`, most phones): For ARM (`armv7-linux-androideabi`, most phones):
``` sh ``` sh
@ -131,6 +152,12 @@ For x86 (typically for the emulator):
./mach package --release --target i686-linux-android ./mach package --release --target i686-linux-android
``` ```
Install the APK to the device or emulator:
``` sh
./mach install --release --android
```
## Running ## Running
Run Servo with the command: Run Servo with the command:

View file

@ -9,9 +9,12 @@ publish = false
[lib] [lib]
path = "lib.rs" path = "lib.rs"
[target.'cfg(not(windows))'.dependencies] [target.'cfg(not(any(windows, target_os = "android")))'.dependencies]
jemallocator = { workspace = true } jemallocator = { workspace = true }
jemalloc-sys = { workspace = true } jemalloc-sys = { workspace = true }
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
winapi = { workspace = true, features = ["heapapi"] } winapi = { workspace = true, features = ["heapapi"] }
[target.'cfg(target_os = "android")'.dependencies]
libc = { workspace = true }

View file

@ -9,7 +9,7 @@ static ALLOC: Allocator = Allocator;
pub use crate::platform::*; pub use crate::platform::*;
#[cfg(not(windows))] #[cfg(not(any(windows, target_os = "android")))]
mod platform { mod platform {
use std::os::raw::c_void; use std::os::raw::c_void;
@ -28,6 +28,21 @@ mod platform {
} }
} }
#[cfg(target_os = "android")]
mod platform {
pub use std::alloc::System as Allocator;
use std::os::raw::c_void;
/// Get the size of a heap block.
pub unsafe extern "C" fn usable_size(ptr: *const c_void) -> usize {
libc::malloc_usable_size(ptr)
}
pub mod libc_compat {
pub use libc::{free, malloc, realloc};
}
}
#[cfg(windows)] #[cfg(windows)]
mod platform { mod platform {
pub use std::alloc::System as Allocator; pub use std::alloc::System as Allocator;

View file

@ -100,9 +100,9 @@ impl BackgroundHangMonitorRegister for HangMonitorRegister {
not(any(target_arch = "arm", target_arch = "aarch64")) not(any(target_arch = "arm", target_arch = "aarch64"))
))] ))]
let sampler = crate::sampler_linux::LinuxSampler::new(); let sampler = crate::sampler_linux::LinuxSampler::new();
#[cfg(all( #[cfg(any(
any(target_os = "android", target_os = "linux"), target_os = "android",
any(target_arch = "arm", target_arch = "aarch64") all(target_os = "linux", any(target_arch = "arm", target_arch = "aarch64"))
))] ))]
let sampler = crate::sampler::DummySampler::new(); let sampler = crate::sampler::DummySampler::new();

View file

@ -22,6 +22,7 @@ servo_rand = { path = "../rand" }
uuid = { workspace = true } uuid = { workspace = true }
[features] [features]
default = ["bluetooth-test"]
native-bluetooth = ["blurz", "blurdroid", "blurmac", "bluetooth-test"] native-bluetooth = ["blurz", "blurdroid", "blurmac", "bluetooth-test"]
bluetooth-test = ["blurmock"] bluetooth-test = ["blurmock"]

View file

@ -501,6 +501,33 @@ impl<Window: WindowMethods + ?Sized> IOCompositor<Window> {
self.shutdown_state = ShutdownState::FinishedShuttingDown; self.shutdown_state = ShutdownState::FinishedShuttingDown;
} }
/// The underlying native surface can be lost during servo's lifetime.
/// On Android, for example, this happens when the app is sent to background.
/// We need to unbind the surface so that we don't try to use it again.
pub fn invalidate_native_surface(&mut self) {
debug!("Invalidating native surface in compositor");
if let Err(e) = self.webrender_surfman.unbind_native_surface_from_context() {
warn!("Unbinding native surface from context failed ({:?})", e);
}
}
/// On Android, this function will be called when the app moves to foreground
/// and the system creates a new native surface that needs to bound to the current
/// context.
#[allow(unsafe_code)]
pub fn replace_native_surface(&mut self, native_widget: *mut c_void, coords: DeviceIntSize) {
debug!("Replacing native surface in compositor: {native_widget:?}");
let connection = self.webrender_surfman.connection();
let native_widget =
unsafe { connection.create_native_widget_from_ptr(native_widget, coords.to_untyped()) };
if let Err(e) = self
.webrender_surfman
.bind_native_surface_to_context(native_widget)
{
warn!("Binding native surface to context failed ({:?})", e);
}
}
fn handle_browser_message(&mut self, msg: CompositorMsg) -> bool { fn handle_browser_message(&mut self, msg: CompositorMsg) -> bool {
match (msg, self.shutdown_state) { match (msg, self.shutdown_state) {
(_, ShutdownState::FinishedShuttingDown) => { (_, ShutdownState::FinishedShuttingDown) => {

View file

@ -10,6 +10,7 @@ use std::time::Duration;
use embedder_traits::{EmbedderProxy, EventLoopWaker}; use embedder_traits::{EmbedderProxy, EventLoopWaker};
use euclid::Scale; use euclid::Scale;
use keyboard_types::KeyboardEvent; use keyboard_types::KeyboardEvent;
use libc::c_void;
use msg::constellation_msg::{PipelineId, TopLevelBrowsingContextId, TraversalDirection}; use msg::constellation_msg::{PipelineId, TopLevelBrowsingContextId, TraversalDirection};
use script_traits::{MediaSessionActionType, MouseButton, TouchEventType, TouchId, WheelDelta}; use script_traits::{MediaSessionActionType, MouseButton, TouchEventType, TouchId, WheelDelta};
use servo_geometry::DeviceIndependentPixel; use servo_geometry::DeviceIndependentPixel;
@ -105,6 +106,14 @@ pub enum EmbedderEvent {
ChangeBrowserVisibility(TopLevelBrowsingContextId, bool), ChangeBrowserVisibility(TopLevelBrowsingContextId, bool),
/// Virtual keyboard was dismissed /// Virtual keyboard was dismissed
IMEDismissed, IMEDismissed,
/// Sent on platforms like Android where the native widget surface can be
/// automatically destroyed by the system, for example when the app
/// is sent to background.
InvalidateNativeSurface,
/// Sent on platforms like Android where system recreates a new surface for
/// the native widget when it is brough back to foreground. This event
/// carries the pointer to the native widget and its new size.
ReplaceNativeSurface(*mut c_void, DeviceIntSize),
} }
impl Debug for EmbedderEvent { impl Debug for EmbedderEvent {
@ -139,6 +148,8 @@ impl Debug for EmbedderEvent {
EmbedderEvent::ChangeBrowserVisibility(..) => write!(f, "ChangeBrowserVisibility"), EmbedderEvent::ChangeBrowserVisibility(..) => write!(f, "ChangeBrowserVisibility"),
EmbedderEvent::IMEDismissed => write!(f, "IMEDismissed"), EmbedderEvent::IMEDismissed => write!(f, "IMEDismissed"),
EmbedderEvent::ClearCache => write!(f, "ClearCache"), EmbedderEvent::ClearCache => write!(f, "ClearCache"),
EmbedderEvent::InvalidateNativeSurface => write!(f, "InvalidateNativeSurface"),
EmbedderEvent::ReplaceNativeSurface(..) => write!(f, "ReplaceNativeSurface"),
} }
} }
} }

View file

@ -4,12 +4,13 @@
use std::path::Path; use std::path::Path;
use log::warn;
use ucd::{Codepoint, UnicodeBlock}; use ucd::{Codepoint, UnicodeBlock};
use super::xml::{Attribute, Node}; use super::xml::{Attribute, Node};
use crate::text::util::is_cjk; use crate::text::util::is_cjk;
lazy_static! { lazy_static::lazy_static! {
static ref FONT_LIST: FontList = FontList::new(); static ref FONT_LIST: FontList = FontList::new();
} }

View file

@ -23,6 +23,6 @@ task_info = { path = "../../support/rust-task_info" }
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
regex = { workspace = true } regex = { workspace = true }
[target.'cfg(not(target_os = "windows"))'.dependencies] [target.'cfg(not(any(target_os = "windows", target_os = "android")))'.dependencies]
libc = { workspace = true } libc = { workspace = true }
jemalloc-sys = { workspace = true } jemalloc-sys = { workspace = true }

View file

@ -387,16 +387,16 @@ impl ReportsForest {
//--------------------------------------------------------------------------- //---------------------------------------------------------------------------
mod system_reporter { mod system_reporter {
#[cfg(not(target_os = "windows"))] #[cfg(not(any(target_os = "windows", target_os = "android")))]
use std::ffi::CString; use std::ffi::CString;
#[cfg(not(target_os = "windows"))] #[cfg(not(any(target_os = "windows", target_os = "android")))]
use std::mem::size_of; use std::mem::size_of;
#[cfg(not(target_os = "windows"))] #[cfg(not(any(target_os = "windows", target_os = "android")))]
use std::ptr::null_mut; use std::ptr::null_mut;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use libc::c_int; use libc::c_int;
#[cfg(not(target_os = "windows"))] #[cfg(not(any(target_os = "windows", target_os = "android")))]
use libc::{c_void, size_t}; use libc::{c_void, size_t};
use profile_traits::mem::{Report, ReportKind, ReporterRequest}; use profile_traits::mem::{Report, ReportKind, ReporterRequest};
use profile_traits::path; use profile_traits::path;
@ -499,10 +499,10 @@ mod system_reporter {
None None
} }
#[cfg(not(target_os = "windows"))] #[cfg(not(any(target_os = "windows", target_os = "android")))]
use jemalloc_sys::mallctl; use jemalloc_sys::mallctl;
#[cfg(not(target_os = "windows"))] #[cfg(not(any(target_os = "windows", target_os = "android")))]
fn jemalloc_stat(value_name: &str) -> Option<usize> { fn jemalloc_stat(value_name: &str) -> Option<usize> {
// Before we request the measurement of interest, we first send an "epoch" // Before we request the measurement of interest, we first send an "epoch"
// request. Without that jemalloc gives cached statistics(!) which can be // request. Without that jemalloc gives cached statistics(!) which can be
@ -549,7 +549,7 @@ mod system_reporter {
Some(value as usize) Some(value as usize)
} }
#[cfg(target_os = "windows")] #[cfg(any(target_os = "windows", target_os = "android"))]
fn jemalloc_stat(_value_name: &str) -> Option<usize> { fn jemalloc_stat(_value_name: &str) -> Option<usize> {
None None
} }

View file

@ -503,7 +503,14 @@ where
EmbedderEvent::Resize => { EmbedderEvent::Resize => {
return self.compositor.on_resize_window_event(); return self.compositor.on_resize_window_event();
}, },
EmbedderEvent::InvalidateNativeSurface => {
self.compositor.invalidate_native_surface();
},
EmbedderEvent::ReplaceNativeSurface(native_widget, coords) => {
self.compositor
.replace_native_surface(native_widget, coords);
self.compositor.composite();
},
EmbedderEvent::AllowNavigationResponse(pipeline_id, allowed) => { EmbedderEvent::AllowNavigationResponse(pipeline_id, allowed) => {
let msg = ConstellationMsg::AllowNavigationResponse(pipeline_id, allowed); let msg = ConstellationMsg::AllowNavigationResponse(pipeline_id, allowed);
if let Err(e) = self.constellation_chan.send(msg) { if let Err(e) = self.constellation_chan.send(msg) {
@ -1091,6 +1098,9 @@ fn default_user_agent_string_for(agent: UserAgent) -> &'static str {
const DESKTOP_UA_STRING: &'static str = const DESKTOP_UA_STRING: &'static str =
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Servo/1.0 Firefox/111.0"; "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:109.0) Servo/1.0 Firefox/111.0";
#[cfg(target_os = "android")]
const DESKTOP_UA_STRING: &'static str = "";
match agent { match agent {
UserAgent::Desktop => DESKTOP_UA_STRING, UserAgent::Desktop => DESKTOP_UA_STRING,
UserAgent::Android => "Mozilla/5.0 (Android; Mobile; rv:109.0) Servo/1.0 Firefox/111.0", UserAgent::Android => "Mozilla/5.0 (Android; Mobile; rv:109.0) Servo/1.0 Firefox/111.0",

View file

@ -216,4 +216,28 @@ impl WebrenderSurfman {
let ref context = self.0.context.borrow(); let ref context = self.0.context.borrow();
device.get_proc_address(context, name) device.get_proc_address(context, name)
} }
pub fn unbind_native_surface_from_context(&self) -> Result<(), Error> {
let device = self.0.device.borrow_mut();
let mut context = self.0.context.borrow_mut();
let mut surface = device.unbind_surface_from_context(&mut context)?.unwrap();
let _ = device.destroy_surface(&mut context, &mut surface)?;
Ok(())
}
pub fn bind_native_surface_to_context(&self, native_widget: NativeWidget) -> Result<(), Error> {
let mut device = self.0.device.borrow_mut();
let mut context = self.0.context.borrow_mut();
let surface_access = SurfaceAccess::GPUOnly;
let surface_type = SurfaceType::Widget { native_widget };
let surface = device.create_surface(&context, surface_access, surface_type)?;
device
.bind_surface_to_context(&mut context, surface)
.map_err(|(err, mut surface)| {
let _ = device.destroy_surface(&mut context, &mut surface);
err
})?;
device.make_context_current(&context)?;
Ok(())
}
} }

View file

@ -79,8 +79,8 @@ def main(avd_name, apk_path, *args):
def tool_path(directory, bin_name): def tool_path(directory, bin_name):
if "ANDROID_SDK" in os.environ: if "ANDROID_SDK_ROOT" in os.environ:
path = os.path.join(os.environ["ANDROID_SDK"], directory, bin_name) path = os.path.join(os.environ["ANDROID_SDK_ROOT"], directory, bin_name)
if os.path.exists(path): if os.path.exists(path):
return path return path

View file

@ -10,6 +10,10 @@ with import (builtins.fetchTarball {
url = "https://github.com/oxalica/rust-overlay/archive/a0df72e106322b67e9c6e591fe870380bd0da0d5.tar.gz"; url = "https://github.com/oxalica/rust-overlay/archive/a0df72e106322b67e9c6e591fe870380bd0da0d5.tar.gz";
})) }))
]; ];
config = {
android_sdk.accept_license = true;
allowUnfree = true;
};
}; };
let let
rustToolchain = rust-bin.fromRustupToolchainFile ../rust-toolchain.toml; rustToolchain = rust-bin.fromRustupToolchainFile ../rust-toolchain.toml;
@ -27,6 +31,25 @@ let
# - glibc 2.38 (#31054) # - glibc 2.38 (#31054)
llvmPackages = llvmPackages_14; llvmPackages = llvmPackages_14;
stdenv = llvmPackages.stdenv; stdenv = llvmPackages.stdenv;
buildToolsVersion = "33.0.2";
androidComposition = androidenv.composeAndroidPackages {
buildToolsVersions = [ buildToolsVersion ];
includeEmulator = true;
platformVersions = [ "33" ];
includeSources = false;
includeSystemImages = true;
systemImageTypes = [ "google_apis" ];
abiVersions = [ "x86" "armeabi-v7a" ];
includeNDK = true;
ndkVersion = "25.2.9519653";
useGoogleAPIs = false;
useGoogleTVAddOns = false;
includeExtras = [
"extras;google;gcm"
];
};
androidSdk = androidComposition.androidsdk;
in in
stdenv.mkDerivation rec { stdenv.mkDerivation rec {
name = "servo-env"; name = "servo-env";
@ -111,22 +134,33 @@ stdenv.mkDerivation rec {
RUSTC_BOOTSTRAP = "crown"; RUSTC_BOOTSTRAP = "crown";
})) }))
# for android builds
# TODO: make this optional
openjdk17_headless
androidSdk
] ++ (lib.optionals stdenv.isDarwin [ ] ++ (lib.optionals stdenv.isDarwin [
darwin.apple_sdk.frameworks.AppKit darwin.apple_sdk.frameworks.AppKit
]); ]);
LIBCLANG_PATH = llvmPackages.clang-unwrapped.lib + "/lib/"; LIBCLANG_PATH = llvmPackages.clang-unwrapped.lib + "/lib/";
# Required by ./mach build --android
ANDROID_SDK_ROOT = "${androidSdk}/libexec/android-sdk";
ANDROID_NDK_ROOT = "${ANDROID_SDK_ROOT}/ndk-bundle";
GRADLE_OPTS = "-Dorg.gradle.project.android.aapt2FromMavenOverride=${ANDROID_SDK_ROOT}/build-tools/${buildToolsVersion}/aapt2";
# Allow cargo to download crates # Allow cargo to download crates
SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt"; SSL_CERT_FILE = "${cacert}/etc/ssl/certs/ca-bundle.crt";
# Enable colored cargo and rustc output # Enable colored cargo and rustc output
TERMINFO = "${ncurses.out}/share/terminfo"; TERMINFO = "${ncurses.out}/share/terminfo";
# Provide libraries that arent linked against but somehow required # Provide libraries that arent linked against but somehow required
LD_LIBRARY_PATH = lib.makeLibraryPath [ LD_LIBRARY_PATH = lib.makeLibraryPath [
# Fixes missing library errors # Fixes missing library errors
xorg.libXcursor xorg.libXrandr xorg.libXi libxkbcommon zlib xorg.libXcursor xorg.libXrandr xorg.libXi libxkbcommon
# [WARN script::dom::gpu] Could not get GPUAdapter ("NotFound") # [WARN script::dom::gpu] Could not get GPUAdapter ("NotFound")
# TLA Err: Error: Couldn't request WebGPU adapter. # TLA Err: Error: Couldn't request WebGPU adapter.

View file

@ -18,6 +18,7 @@ use servo::compositing::windowing::{
AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent, AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent,
WindowMethods, WindowMethods,
}; };
use servo::compositing::CompositeTarget;
use servo::config::prefs::pref_map; use servo::config::prefs::pref_map;
pub use servo::config::prefs::{add_user_prefs, PrefValue}; pub use servo::config::prefs::{add_user_prefs, PrefValue};
use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods}; use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods};
@ -298,7 +299,12 @@ pub fn init(
gl: gl.clone(), 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| { SERVO.with(|s| {
let mut servo_glue = ServoGlue { let mut servo_glue = ServoGlue {
@ -569,6 +575,24 @@ impl ServoGlue {
self.process_event(EmbedderEvent::Keyboard(key_event)) 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( pub fn media_session_action(
&mut self, &mut self,
action: MediaSessionActionType, action: MediaSessionActionType,
@ -789,6 +813,9 @@ impl ServoGlue {
EmbedderMsg::Panic(reason, backtrace) => { EmbedderMsg::Panic(reason, backtrace) => {
self.callbacks.host_callbacks.on_panic(reason, backtrace); self.callbacks.host_callbacks.on_panic(reason, backtrace);
}, },
EmbedderMsg::ReadyToPresent => {
self.servo.present();
},
EmbedderMsg::Status(..) | EmbedderMsg::Status(..) |
EmbedderMsg::SelectFiles(..) | EmbedderMsg::SelectFiles(..) |
EmbedderMsg::MoveTo(..) | EmbedderMsg::MoveTo(..) |
@ -798,7 +825,6 @@ impl ServoGlue {
EmbedderMsg::NewFavicon(..) | EmbedderMsg::NewFavicon(..) |
EmbedderMsg::HeadParsed | EmbedderMsg::HeadParsed |
EmbedderMsg::SetFullscreenState(..) | EmbedderMsg::SetFullscreenState(..) |
EmbedderMsg::ReadyToPresent |
EmbedderMsg::ReportProfile(..) | EmbedderMsg::ReportProfile(..) |
EmbedderMsg::EventDelivered(..) => {}, EmbedderMsg::EventDelivered(..) => {},
} }

View file

@ -1,7 +1,6 @@
[package] [package]
name = "simpleservo_jniapi" name = "simpleservo_jniapi"
version = "0.0.1" version = "0.0.1"
build = "build.rs"
authors = ["The Servo Project Developers"] authors = ["The Servo Project Developers"]
license = "MPL-2.0" license = "MPL-2.0"
edition = "2018" edition = "2018"
@ -14,21 +13,21 @@ test = false
bench = false bench = false
[dependencies] [dependencies]
android_injected_glue = "0.2" android_logger = "0.13"
android_logger = "0.10"
gstreamer = { workspace = true }
jni = "0.18.0" jni = "0.18.0"
libc = { workspace = true } libc = { workspace = true }
log = { workspace = true } log = { workspace = true }
serde_json = { 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] [build-dependencies]
cc = "1.0" cc = "1.0"
[features] [features]
debugmozjs = ["simpleservo/debugmozjs"] debugmozjs = ["simpleservo/debugmozjs"]
default = ["max_log_level", "native-bluetooth", "webdriver"] default = ["max_log_level", "webdriver", "no_static_freetype"]
googlevr = ["simpleservo/googlevr"] googlevr = ["simpleservo/googlevr"]
js_backtrace = ["simpleservo/js_backtrace"] js_backtrace = ["simpleservo/js_backtrace"]
max_log_level = ["simpleservo/max_log_level"] max_log_level = ["simpleservo/max_log_level"]
@ -36,3 +35,4 @@ media-gstreamer = ["simpleservo/media-gstreamer"]
native-bluetooth = ["simpleservo/native-bluetooth"] native-bluetooth = ["simpleservo/native-bluetooth"]
webdriver = ["simpleservo/webdriver"] webdriver = ["simpleservo/webdriver"]
webgl_backtrace = ["simpleservo/webgl_backtrace"] 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)] #![allow(non_snake_case)]
use std::os::raw::{c_char, c_int, c_void}; use std::os::raw::{c_char, c_int, c_void};
use std::ptr::{null, null_mut};
use std::sync::Arc; use std::sync::Arc;
use std::thread; use std::thread;
use android_logger::{self, Filter}; use android_logger::{self, Config, FilterBuilder};
use gstreamer::debug_set_threshold_from_string;
use jni::objects::{GlobalRef, JClass, JObject, JString, JValue}; use jni::objects::{GlobalRef, JClass, JObject, JString, JValue};
use jni::sys::{jboolean, jfloat, jint, jstring, JNI_TRUE}; use jni::sys::{jboolean, jfloat, jint, jstring, JNI_TRUE};
use jni::{errors, JNIEnv, JavaVM}; use jni::{JNIEnv, JavaVM};
use libc::{dup2, pipe, read}; use libc::{dup2, pipe, read};
use log::Level; use log::{debug, error, info, warn};
use simpleservo::{ use simpleservo::{
self, self, deinit, gl_glue, gl_glue, Coordinates, DeviceIntRect, EventLoopWaker, HostTrait, self, gl_glue, Coordinates, DeviceIntRect, EventLoopWaker, HostTrait, InitOptions,
InitOptions, InputMethodType, MediaSessionPlaybackState, MouseButton, PromptResult, ServoGlue, InputMethodType, MediaSessionPlaybackState, PromptResult, ServoGlue, SERVO,
ServoGlue, VRInitOptions, SERVO, SERVO,
}; };
struct HostCallbacks { struct HostCallbacks {
@ -27,6 +24,19 @@ struct HostCallbacks {
jvm: JavaVM, 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) fn call<F>(env: &JNIEnv, f: F)
where where
F: Fn(&mut ServoGlue) -> Result<(), &str>, 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( pub fn Java_org_mozilla_servoview_JNIServo_init(
env: JNIEnv, env: JNIEnv,
_: JClass, _: JClass,
activity: JObject, _activity: JObject,
opts: JObject, opts: JObject,
callbacks_obj: 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), Ok((opts, log, log_str, gst_debug_str)) => (opts, log, log_str, gst_debug_str),
Err(err) => { Err(err) => {
throw(&env, &err); throw(&env, &err);
@ -79,26 +90,26 @@ pub fn Java_org_mozilla_servoview_JNIServo_init(
"compositing::compositor", "compositing::compositor",
"constellation::constellation", "constellation::constellation",
]; ];
let mut filter = Filter::default().with_min_level(Level::Debug); let mut filter_builder = FilterBuilder::new();
for &module in &filters { 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 { if let Some(log_str) = log_str {
for module in log_str.split(',') { 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 { android_logger::init_once(
debug_set_threshold_from_string(&gst_debug_str, true); Config::default()
} .with_max_level(log::LevelFilter::Debug)
.with_filter(filter_builder.build())
android_logger::init_once(filter, Some("simpleservo")); .with_tag("simpleservo"),
)
} }
info!("init"); info!("init");
initialize_android_glue(&env, activity);
redirect_stdout_to_logcat(); redirect_stdout_to_logcat();
let callbacks_ref = match env.new_global_ref(callbacks_obj) { let callbacks_ref = match env.new_global_ref(callbacks_obj) {
@ -329,11 +340,33 @@ pub fn Java_org_mozilla_servoview_JNIServo_pinchZoomEnd(
} }
#[no_mangle] #[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"); debug!("click");
call(&env, |s| s.click(x as f32, y as f32)); 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] #[no_mangle]
pub fn Java_org_mozilla_servoview_JNIServo_mediaSessionAction( pub fn Java_org_mozilla_servoview_JNIServo_mediaSessionAction(
env: JNIEnv, env: JNIEnv,
@ -379,20 +412,6 @@ impl HostCallbacks {
} }
impl HostTrait for 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) { fn prompt_alert(&self, message: String, _trusted: bool) {
debug!("prompt_alert"); debug!("prompt_alert");
let env = self.jvm.get_env().unwrap(); let env = self.jvm.get_env().unwrap();
@ -446,9 +465,10 @@ impl HostTrait for HostCallbacks {
.unwrap(); .unwrap();
} }
fn on_title_changed(&self, title: String) { fn on_title_changed(&self, title: Option<String>) {
debug!("on_title_changed"); debug!("on_title_changed");
let env = self.jvm.get_env().unwrap(); let env = self.jvm.get_env().unwrap();
let title = title.unwrap_or_else(String::new);
let s = match new_string(&env, &title) { let s = match new_string(&env, &title) {
Ok(s) => s, Ok(s) => s,
Err(_) => return, Err(_) => return,
@ -529,7 +549,7 @@ impl HostTrait for HostCallbacks {
fn on_ime_show( fn on_ime_show(
&self, &self,
_type: InputEncoding, _input_type: InputMethodType,
_text: Option<(String, i32)>, _text: Option<(String, i32)>,
_multiline: bool, _multiline: bool,
_rect: DeviceIntRect, _rect: DeviceIntRect,
@ -610,65 +630,16 @@ impl HostTrait for HostCallbacks {
.unwrap(); .unwrap();
} }
fn on_devtools_started(&self, port: Result<u16, ()>) { fn on_devtools_started(&self, port: Result<u16, ()>, _token: String) {
match port { match port {
Ok(p) => info!("Devtools Server running on port {}", p), Ok(p) => info!("Devtools Server running on port {}", p),
Err(()) => error!("Error running devtools server"), Err(()) => error!("Error running devtools server"),
} }
} }
}
fn initialize_android_glue(env: &JNIEnv, activity: JObject) { fn show_context_menu(&self, _title: Option<String>, _items: Vec<String>) {}
use android_injected_glue::{ffi, ANDROID_APP};
// From jni-rs to android_injected_glue fn on_panic(&self, _reason: String, _backtrace: Option<String>) {}
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;
}
} }
extern "C" { extern "C" {
@ -803,28 +774,21 @@ fn jni_coords_to_rust_coords(env: &JNIEnv, obj: JObject) -> Result<Coordinates,
fn get_field<'a>( fn get_field<'a>(
env: &'a JNIEnv, env: &'a JNIEnv,
obj: JObject, obj: JObject<'a>,
field: &str, field: &str,
type_: &str, type_: &str,
) -> Result<Option<JValue<'a>>, String> { ) -> Result<Option<JValue<'a>>, String> {
if env.get_field_id(obj, field, type_).is_err() { 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_) env.get_field(obj, field, type_)
.map(|value| Some(value)) .map(|value| Some(value))
.or_else(|e| match *e.kind() { .or_else(|_| Err(format!("Can't find `{}` field", field)))
errors::ErrorKind::NullPtr(_) => Ok(None),
_ => Err(format!(
"Can't find `{}` field: {}",
&field,
e.description()
)),
})
} }
fn get_non_null_field<'a>( fn get_non_null_field<'a>(
env: &'a JNIEnv, env: &'a JNIEnv,
obj: JObject, obj: JObject<'a>,
field: &str, field: &str,
type_: &str, type_: &str,
) -> Result<JValue<'a>, String> { ) -> Result<JValue<'a>, String> {
@ -851,6 +815,7 @@ fn get_string(env: &JNIEnv, obj: JObject, field: &str) -> Result<Option<String>,
fn get_options( fn get_options(
env: &JNIEnv, env: &JNIEnv,
opts: JObject, opts: JObject,
surface: JObject,
) -> Result<(InitOptions, bool, Option<String>, Option<String>), String> { ) -> Result<(InitOptions, bool, Option<String>, Option<String>), String> {
let args = get_string(env, opts, "args")?; let args = get_string(env, opts, "args")?;
let url = get_string(env, opts, "url")?; let url = get_string(env, opts, "url")?;
@ -862,13 +827,6 @@ fn get_options(
let log = get_non_null_field(env, opts, "enableLogs", "Z")? let log = get_non_null_field(env, opts, "enableLogs", "Z")?
.z() .z()
.map_err(|_| "enableLogs not a boolean")?; .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( let coordinates = get_non_null_field(
env, env,
opts, opts,
@ -885,20 +843,16 @@ fn get_options(
None => None, None => None,
}; };
let native_window = unsafe { ANativeWindow_fromSurface(env.get_native_interface(), surface) };
let opts = InitOptions { let opts = InitOptions {
args: args.unwrap_or(vec![]), args: args.unwrap_or(vec![]),
url,
coordinates, coordinates,
density, density,
enable_subpixel_text_antialiasing,
vr_init: if vr_pointer.is_null() {
VRInitOptions::None
} else {
VRInitOptions::VRExternal(vr_pointer)
},
xr_discovery: None, xr_discovery: None,
gl_context_pointer: None, gl_context_pointer: None,
native_display_pointer: None, native_display_pointer: None,
surfman_integration: simpleservo::SurfmanIntegration::Widget(native_window),
prefs: None,
}; };
Ok((opts, log, log_str, gst_debug_str)) Ok((opts, log, log_str, gst_debug_str))
} }

View file

@ -12,7 +12,6 @@ import glob
import json import json
import os import os
import os.path as path import os.path as path
import platform
import re import re
import subprocess import subprocess
import sys import sys
@ -30,7 +29,7 @@ from mach.decorators import (
import servo.platform import servo.platform
from servo.command_base import CommandBase, cd, check_call from servo.command_base import CommandBase, cd, check_call
from servo.util import delete, download_bytes, download_file, extract, check_hash from servo.util import delete, download_bytes
@CommandProvider @CommandProvider
@ -66,150 +65,6 @@ class MachCommands(CommandBase):
return 1 return 1
return 0 return 0
@Command('bootstrap-android',
description='Install the Android SDK and NDK.',
category='bootstrap')
@CommandArgument('--build',
action='store_true',
help='Install Android-specific dependencies for building')
@CommandArgument('--emulator-x86',
action='store_true',
help='Install Android x86 emulator and system image')
@CommandArgument('--accept-all-licences',
action='store_true',
help='For non-interactive use')
def bootstrap_android(self, build=False, emulator_x86=False, accept_all_licences=False):
if not (build or emulator_x86):
print("Must specify `--build` or `--emulator-x86` or both.")
ndk = "android-ndk-r15c-{system}-{arch}"
tools = "sdk-tools-{system}-4333796"
emulator_platform = "android-28"
emulator_image = "system-images;%s;google_apis;x86" % emulator_platform
known_sha1 = {
# https://dl.google.com/android/repository/repository2-1.xml
"sdk-tools-darwin-4333796.zip": "ed85ea7b59bc3483ce0af4c198523ba044e083ad",
"sdk-tools-linux-4333796.zip": "8c7c28554a32318461802c1291d76fccfafde054",
"sdk-tools-windows-4333796.zip": "aa298b5346ee0d63940d13609fe6bec621384510",
# https://developer.android.com/ndk/downloads/older_releases
"android-ndk-r15c-windows-x86.zip": "f2e47121feb73ec34ced5e947cbf1adc6b56246e",
"android-ndk-r15c-windows-x86_64.zip": "970bb2496de0eada74674bb1b06d79165f725696",
"android-ndk-r15c-darwin-x86_64.zip": "ea4b5d76475db84745aa8828000d009625fc1f98",
"android-ndk-r15c-linux-x86_64.zip": "0bf02d4e8b85fd770fd7b9b2cdec57f9441f27a2",
}
toolchains = path.join(self.context.topdir, "android-toolchains")
if not path.isdir(toolchains):
os.makedirs(toolchains)
def download(target_dir, name, flatten=False):
final = path.join(toolchains, target_dir)
if path.isdir(final):
return
base_url = "https://dl.google.com/android/repository/"
filename = name + ".zip"
url = base_url + filename
archive = path.join(toolchains, filename)
if not path.isfile(archive):
download_file(filename, url, archive)
check_hash(archive, known_sha1[filename], "sha1")
print("Extracting " + filename)
remove = True # Set to False to avoid repeated downloads while debugging this script
if flatten:
extracted = final + "_"
extract(archive, extracted, remove=remove)
contents = os.listdir(extracted)
assert len(contents) == 1
os.rename(path.join(extracted, contents[0]), final)
os.rmdir(extracted)
else:
extract(archive, final, remove=remove)
system = platform.system().lower()
machine = platform.machine().lower()
arch = {"i386": "x86"}.get(machine, machine)
if build:
download("ndk", ndk.format(system=system, arch=arch), flatten=True)
download("sdk", tools.format(system=system))
components = []
if emulator_x86:
components += [
"platform-tools",
"emulator",
"platforms;" + emulator_platform,
emulator_image,
]
if build:
components += [
"platform-tools",
"platforms;android-18",
]
sdkmanager = [path.join(toolchains, "sdk", "tools", "bin", "sdkmanager")] + components
if accept_all_licences:
yes = subprocess.Popen(["yes"], stdout=subprocess.PIPE)
process = subprocess.Popen(
sdkmanager, stdin=yes.stdout, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
)
# Reduce progress bar spam by removing duplicate lines.
# Printing the same line again with \r is a no-op in a real terminal,
# but each line is shown individually in Taskcluster's log viewer.
previous_line = None
line = b""
while 1:
# Read one byte at a time because in Python:
# * readline() blocks until "\n", which doesn't come before the prompt
# * read() blocks until EOF, which doesn't come before the prompt
# * read(n) keeps reading until it gets n bytes or EOF,
# but we don't know reliably how many bytes to read until the prompt
byte = process.stdout.read(1)
if len(byte) == 0:
print(line)
break
line += byte
if byte == b'\n' or byte == b'\r':
if line != previous_line:
print(line.decode("utf-8", "replace"), end="")
sys.stdout.flush()
previous_line = line
line = b""
exit_code = process.wait()
yes.terminate()
if exit_code:
return exit_code
else:
subprocess.check_call(sdkmanager)
if emulator_x86:
avd_path = path.join(toolchains, "avd", "servo-x86")
process = subprocess.Popen(stdin=subprocess.PIPE, stdout=subprocess.PIPE, args=[
path.join(toolchains, "sdk", "tools", "bin", "avdmanager"),
"create", "avd",
"--path", avd_path,
"--name", "servo-x86",
"--package", emulator_image,
"--force",
])
output = b""
while 1:
# Read one byte at a time, see comment above.
byte = process.stdout.read(1)
if len(byte) == 0:
break
output += byte
# There seems to be no way to disable this prompt:
if output.endswith(b"Do you wish to create a custom hardware profile? [no]"):
process.stdin.write("no\n")
assert process.wait() == 0
with open(path.join(avd_path, "config.ini"), "a") as f:
f.write("disk.dataPartition.size=2G\n")
@Command('update-hsts-preload', @Command('update-hsts-preload',
description='Download the HSTS preload list', description='Download the HSTS preload list',
category='bootstrap') category='bootstrap')

View file

@ -98,7 +98,6 @@ class MachCommands(CommandBase):
for key in env: for key in env:
print((key, env[key])) print((key, env[key]))
self.download_and_build_android_dependencies_if_needed(env)
status = self.run_cargo_build_like_command( status = self.run_cargo_build_like_command(
"build", opts, env=env, verbose=verbose, "build", opts, env=env, verbose=verbose,
libsimpleservo=libsimpleservo, **kwargs libsimpleservo=libsimpleservo, **kwargs

View file

@ -334,7 +334,7 @@ class CommandBase(object):
def get_binary_path(self, build_type: BuildType, target=None, android=False, simpleservo=False): def get_binary_path(self, build_type: BuildType, target=None, android=False, simpleservo=False):
base_path = util.get_target_dir() base_path = util.get_target_dir()
if android: if android:
base_path = path.join(base_path, "android", self.config["android"]["target"]) base_path = path.join(base_path, self.config["android"]["target"])
simpleservo = True simpleservo = True
elif target: elif target:
base_path = path.join(base_path, target) base_path = path.join(base_path, target)
@ -529,58 +529,38 @@ class CommandBase(object):
# Paths to Android build tools: # Paths to Android build tools:
if self.config["android"]["sdk"]: if self.config["android"]["sdk"]:
env["ANDROID_SDK"] = self.config["android"]["sdk"] env["ANDROID_SDK_ROOT"] = self.config["android"]["sdk"]
if self.config["android"]["ndk"]: if self.config["android"]["ndk"]:
env["ANDROID_NDK"] = self.config["android"]["ndk"] env["ANDROID_NDK_ROOT"] = self.config["android"]["ndk"]
if self.config["android"]["toolchain"]:
env["ANDROID_TOOLCHAIN"] = self.config["android"]["toolchain"]
if self.config["android"]["platform"]:
env["ANDROID_PLATFORM"] = self.config["android"]["platform"]
# These are set because they are the variable names that build-apk
# expects. However, other submodules have makefiles that reference
# the env var names above. Once winit is enabled and set as the
# default, we could modify the subproject makefiles to use the names
# below and remove the vars above, to avoid duplication.
if "ANDROID_SDK" in env:
env["ANDROID_HOME"] = env["ANDROID_SDK"]
if "ANDROID_NDK" in env:
env["NDK_HOME"] = env["ANDROID_NDK"]
if "ANDROID_TOOLCHAIN" in env:
env["NDK_STANDALONE"] = env["ANDROID_TOOLCHAIN"]
toolchains = path.join(self.context.topdir, "android-toolchains") toolchains = path.join(self.context.topdir, "android-toolchains")
for kind in ["sdk", "ndk"]: for kind in ["sdk", "ndk"]:
default = os.path.join(toolchains, kind) default = os.path.join(toolchains, kind)
if os.path.isdir(default): if os.path.isdir(default):
env.setdefault("ANDROID_" + kind.upper(), default) env.setdefault(f"ANDROID_{kind.upper()}_ROOT", default)
tools = os.path.join(toolchains, "sdk", "platform-tools") if "ANDROID_NDK_ROOT" not in env:
if os.path.isdir(tools): print("Please set the ANDROID_NDK_ROOT environment variable.")
env["PATH"] = "%s%s%s" % (tools, os.pathsep, env["PATH"])
if "ANDROID_NDK" not in env:
print("Please set the ANDROID_NDK environment variable.")
sys.exit(1) sys.exit(1)
if "ANDROID_SDK" not in env: if "ANDROID_SDK_ROOT" not in env:
print("Please set the ANDROID_SDK environment variable.") print("Please set the ANDROID_SDK_ROOT environment variable.")
sys.exit(1) sys.exit(1)
android_platform = self.config["android"]["platform"] android_platform = self.config["android"]["platform"]
android_toolchain_name = self.config["android"]["toolchain_name"] android_toolchain_name = self.config["android"]["toolchain_name"]
android_toolchain_prefix = self.config["android"]["toolchain_prefix"]
android_lib = self.config["android"]["lib"] android_lib = self.config["android"]["lib"]
android_arch = self.config["android"]["arch"]
# Check if the NDK version is 15 android_api = android_platform.replace('android-', '')
if not os.path.isfile(path.join(env["ANDROID_NDK"], 'source.properties')):
# Check if the NDK version is 25
if not os.path.isfile(path.join(env["ANDROID_NDK_ROOT"], 'source.properties')):
print("ANDROID_NDK should have file `source.properties`.") print("ANDROID_NDK should have file `source.properties`.")
print("The environment variable ANDROID_NDK may be set at a wrong path.") print("The environment variable ANDROID_NDK_ROOT may be set at a wrong path.")
sys.exit(1) sys.exit(1)
with open(path.join(env["ANDROID_NDK"], 'source.properties'), encoding="utf8") as ndk_properties: with open(path.join(env["ANDROID_NDK_ROOT"], 'source.properties'), encoding="utf8") as ndk_properties:
lines = ndk_properties.readlines() lines = ndk_properties.readlines()
if lines[1].split(' = ')[1].split('.')[0] != '15': if lines[1].split(' = ')[1].split('.')[0] != '25':
print("Currently only support NDK 15. Please re-run `./mach bootstrap-android`.") print("Servo currently only supports NDK r25c.")
sys.exit(1) sys.exit(1)
# Android builds also require having the gcc bits on the PATH and various INCLUDE # Android builds also require having the gcc bits on the PATH and various INCLUDE
@ -598,48 +578,33 @@ class CommandBase(object):
host_suffix = "x86_64" host_suffix = "x86_64"
host = os_type + "-" + host_suffix host = os_type + "-" + host_suffix
host_cc = env.get('HOST_CC') or shutil.which(["clang"]) or util.whichget_exec_path(["gcc"]) host_cc = env.get('HOST_CC') or shutil.which("clang")
host_cxx = env.get('HOST_CXX') or util.whichget_exec_path(["clang++"]) or util.whichget_exec_path(["g++"]) host_cxx = env.get('HOST_CXX') or shutil.which("clang++")
llvm_toolchain = path.join(env['ANDROID_NDK'], "toolchains", "llvm", "prebuilt", host)
gcc_toolchain = path.join(env['ANDROID_NDK'], "toolchains",
android_toolchain_prefix + "-4.9", "prebuilt", host)
gcc_libs = path.join(gcc_toolchain, "lib", "gcc", android_toolchain_name, "4.9.x")
llvm_toolchain = path.join(env['ANDROID_NDK_ROOT'], "toolchains", "llvm", "prebuilt", host)
env['PATH'] = (path.join(llvm_toolchain, "bin") + ':' + env['PATH']) env['PATH'] = (path.join(llvm_toolchain, "bin") + ':' + env['PATH'])
env['ANDROID_SYSROOT'] = path.join(env['ANDROID_NDK'], "sysroot")
support_include = path.join(env['ANDROID_NDK'], "sources", "android", "support", "include") def to_ndk_bin(prog):
cpufeatures_include = path.join(env['ANDROID_NDK'], "sources", "android", "cpufeatures") return path.join(llvm_toolchain, "bin", prog)
cxx_include = path.join(env['ANDROID_NDK'], "sources", "cxx-stl",
"llvm-libc++", "include")
clang_include = path.join(llvm_toolchain, "lib64", "clang", "3.8", "include")
cxxabi_include = path.join(env['ANDROID_NDK'], "sources", "cxx-stl",
"llvm-libc++abi", "include")
sysroot_include = path.join(env['ANDROID_SYSROOT'], "usr", "include")
arch_include = path.join(sysroot_include, android_toolchain_name)
android_platform_dir = path.join(env['ANDROID_NDK'], "platforms", android_platform, "arch-" + android_arch)
arch_libs = path.join(android_platform_dir, "usr", "lib")
clang_include = path.join(llvm_toolchain, "lib64", "clang", "5.0", "include")
android_api = android_platform.replace('android-', '')
env["RUST_TARGET"] = self.cross_compile_target env["RUST_TARGET"] = self.cross_compile_target
env['HOST_CC'] = host_cc env['HOST_CC'] = host_cc
env['HOST_CXX'] = host_cxx env['HOST_CXX'] = host_cxx
env['HOST_CFLAGS'] = '' env['HOST_CFLAGS'] = ''
env['HOST_CXXFLAGS'] = '' env['HOST_CXXFLAGS'] = ''
env['CC'] = path.join(llvm_toolchain, "bin", "clang") env['CC'] = to_ndk_bin("clang")
env['CPP'] = path.join(llvm_toolchain, "bin", "clang") + " -E" env['CPP'] = to_ndk_bin("clang") + " -E"
env['CXX'] = path.join(llvm_toolchain, "bin", "clang++") env['CXX'] = to_ndk_bin("clang++")
env['ANDROID_TOOLCHAIN'] = gcc_toolchain
env['ANDROID_TOOLCHAIN_DIR'] = gcc_toolchain env['AR'] = to_ndk_bin("llvm-ar")
env['ANDROID_VERSION'] = android_api env['RANLIB'] = to_ndk_bin("llvm-ranlib")
env['ANDROID_PLATFORM_DIR'] = android_platform_dir env['OBJCOPY'] = to_ndk_bin("llvm-objcopy")
env['GCC_TOOLCHAIN'] = gcc_toolchain env['YASM'] = to_ndk_bin("yasm")
gcc_toolchain_bin = path.join(gcc_toolchain, android_toolchain_name, "bin") env['STRIP'] = to_ndk_bin("llvm-strip")
env['AR'] = path.join(gcc_toolchain_bin, "ar") env['HARFBUZZ_SYS_NO_PKG_CONFIG'] = "true"
env['RANLIB'] = path.join(gcc_toolchain_bin, "ranlib") env['RUST_FONTCONFIG_DLOPEN'] = "on"
env['OBJCOPY'] = path.join(gcc_toolchain_bin, "objcopy")
env['YASM'] = path.join(env['ANDROID_NDK'], 'prebuilt', host, 'bin', 'yasm') env["LIBCLANG_PATH"] = path.join(llvm_toolchain, "lib64")
# A cheat-sheet for some of the build errors caused by getting the search path wrong... # A cheat-sheet for some of the build errors caused by getting the search path wrong...
# #
# fatal error: 'limits' file not found # fatal error: 'limits' file not found
@ -651,52 +616,28 @@ class CommandBase(object):
# #
# Also worth remembering: autoconf uses C for its configuration, # Also worth remembering: autoconf uses C for its configuration,
# even for C++ builds, so the C flags need to line up with the C++ flags. # even for C++ builds, so the C flags need to line up with the C++ flags.
env['CFLAGS'] = ' '.join([ env['CFLAGS'] = "--target=" + android_toolchain_name
"--target=" + self.cross_compile_target, env['CXXFLAGS'] = "--target=" + android_toolchain_name
"--sysroot=" + env['ANDROID_SYSROOT'],
"--gcc-toolchain=" + gcc_toolchain, # These two variables are needed for the mozjs compilation.
"-isystem", sysroot_include, env['ANDROID_API_LEVEL'] = android_api
"-I" + arch_include, env["ANDROID_NDK_HOME"] = env["ANDROID_NDK_ROOT"]
"-B" + arch_libs,
"-L" + arch_libs, # The two variables set below are passed by our custom
"-D__ANDROID_API__=" + android_api, # support/android/toolchain.cmake to the NDK's CMake toolchain file
])
env['CXXFLAGS'] = ' '.join([
"--target=" + self.cross_compile_target,
"--sysroot=" + env['ANDROID_SYSROOT'],
"--gcc-toolchain=" + gcc_toolchain,
"-I" + cpufeatures_include,
"-I" + cxx_include,
"-I" + clang_include,
"-isystem", sysroot_include,
"-I" + cxxabi_include,
"-I" + clang_include,
"-I" + arch_include,
"-I" + support_include,
"-L" + gcc_libs,
"-B" + arch_libs,
"-L" + arch_libs,
"-D__ANDROID_API__=" + android_api,
"-D__STDC_CONSTANT_MACROS",
"-D__NDK_FPABI__=",
])
env['CPPFLAGS'] = ' '.join([
"--target=" + self.cross_compile_target,
"--sysroot=" + env['ANDROID_SYSROOT'],
"-I" + arch_include,
])
env["NDK_ANDROID_VERSION"] = android_api
env["ANDROID_ABI"] = android_lib env["ANDROID_ABI"] = android_lib
env["ANDROID_PLATFORM"] = android_platform env["ANDROID_PLATFORM"] = android_platform
env["NDK_CMAKE_TOOLCHAIN_FILE"] = path.join(env['ANDROID_NDK'], "build", "cmake", "android.toolchain.cmake") env["NDK_CMAKE_TOOLCHAIN_FILE"] = path.join(
env["CMAKE_TOOLCHAIN_FILE"] = path.join(self.android_support_dir(), "toolchain.cmake") env['ANDROID_NDK_ROOT'], "build", "cmake", "android.toolchain.cmake")
env["CMAKE_TOOLCHAIN_FILE"] = path.join(
self.context.topdir, "support", "android", "toolchain.cmake")
# Set output dir for gradle aar files # Set output dir for gradle aar files
env["AAR_OUT_DIR"] = self.android_aar_dir() env["AAR_OUT_DIR"] = path.join(self.context.topdir, "target", "android", "aar")
if not os.path.exists(env['AAR_OUT_DIR']): if not os.path.exists(env['AAR_OUT_DIR']):
os.makedirs(env['AAR_OUT_DIR']) os.makedirs(env['AAR_OUT_DIR'])
env['PKG_CONFIG_ALLOW_CROSS'] = "1" env['PKG_CONFIG_SYSROOT_DIR'] = path.join(llvm_toolchain, 'sysroot')
@staticmethod @staticmethod
def common_command_arguments(build_configuration=False, build_type=False): def common_command_arguments(build_configuration=False, build_type=False):
@ -863,11 +804,7 @@ class CommandBase(object):
if self.config["build"]["media-stack"] != "auto": if self.config["build"]["media-stack"] != "auto":
media_stack = self.config["build"]["media-stack"] media_stack = self.config["build"]["media-stack"]
assert media_stack assert media_stack
elif ( elif not self.cross_compile_target:
not self.cross_compile_target
or ("armv7" in self.cross_compile_target and self.is_android_build)
or "x86_64" in self.cross_compile_target
):
media_stack = "gstreamer" media_stack = "gstreamer"
else: else:
media_stack = "dummy" media_stack = "dummy"
@ -943,22 +880,16 @@ class CommandBase(object):
return call(["cargo", command] + args + cargo_args, env=env, verbose=verbose) return call(["cargo", command] + args + cargo_args, env=env, verbose=verbose)
def android_support_dir(self):
return path.join(self.context.topdir, "support", "android")
def android_aar_dir(self):
return path.join(self.context.topdir, "target", "android", "aar")
def android_adb_path(self, env): def android_adb_path(self, env):
if "ANDROID_SDK" in env: if "ANDROID_SDK_ROOT" in env:
sdk_adb = path.join(env["ANDROID_SDK"], "platform-tools", "adb") sdk_adb = path.join(env["ANDROID_SDK_ROOT"], "platform-tools", "adb")
if path.exists(sdk_adb): if path.exists(sdk_adb):
return sdk_adb return sdk_adb
return "adb" return "adb"
def android_emulator_path(self, env): def android_emulator_path(self, env):
if "ANDROID_SDK" in env: if "ANDROID_SDK_ROOT" in env:
sdk_adb = path.join(env["ANDROID_SDK"], "emulator", "emulator") sdk_adb = path.join(env["ANDROID_SDK_ROOT"], "emulator", "emulator")
if path.exists(sdk_adb): if path.exists(sdk_adb):
return sdk_adb return sdk_adb
return "emulator" return "emulator"
@ -968,29 +899,29 @@ class CommandBase(object):
build by writing the appropriate toolchain configuration values build by writing the appropriate toolchain configuration values
into the stored configuration.""" into the stored configuration."""
if target == "armv7-linux-androideabi": if target == "armv7-linux-androideabi":
self.config["android"]["platform"] = "android-21" self.config["android"]["platform"] = "android-30"
self.config["android"]["target"] = target self.config["android"]["target"] = target
self.config["android"]["toolchain_prefix"] = "arm-linux-androideabi" self.config["android"]["toolchain_prefix"] = "arm-linux-androideabi"
self.config["android"]["arch"] = "arm" self.config["android"]["arch"] = "arm"
self.config["android"]["lib"] = "armeabi-v7a" self.config["android"]["lib"] = "armeabi-v7a"
self.config["android"]["toolchain_name"] = "arm-linux-androideabi" self.config["android"]["toolchain_name"] = "armv7a-linux-androideabi30"
return True return True
elif target == "aarch64-linux-android": elif target == "aarch64-linux-android":
self.config["android"]["platform"] = "android-21" self.config["android"]["platform"] = "android-30"
self.config["android"]["target"] = target self.config["android"]["target"] = target
self.config["android"]["toolchain_prefix"] = target self.config["android"]["toolchain_prefix"] = target
self.config["android"]["arch"] = "arm64" self.config["android"]["arch"] = "arm64"
self.config["android"]["lib"] = "arm64-v8a" self.config["android"]["lib"] = "arm64-v8a"
self.config["android"]["toolchain_name"] = target self.config["android"]["toolchain_name"] = "aarch64-linux-androideabi30"
return True return True
elif target == "i686-linux-android": elif target == "i686-linux-android":
# https://github.com/jemalloc/jemalloc/issues/1279 # https://github.com/jemalloc/jemalloc/issues/1279
self.config["android"]["platform"] = "android-21" self.config["android"]["platform"] = "android-30"
self.config["android"]["target"] = target self.config["android"]["target"] = target
self.config["android"]["toolchain_prefix"] = "x86" self.config["android"]["toolchain_prefix"] = target
self.config["android"]["arch"] = "x86" self.config["android"]["arch"] = "x86"
self.config["android"]["lib"] = "x86" self.config["android"]["lib"] = "x86"
self.config["android"]["toolchain_name"] = target self.config["android"]["toolchain_name"] = "i686-linux-android30"
return True return True
return False return False

View file

@ -160,7 +160,7 @@ class PackageCommands(CommandBase):
else: else:
raise Exception("TODO what should this be?") raise Exception("TODO what should this be?")
flavor_name = "Main" flavor_name = "Basic"
if flavor is not None: if flavor is not None:
flavor_name = flavor.title() flavor_name = flavor.title()
@ -176,10 +176,7 @@ class PackageCommands(CommandBase):
variant = ":assemble" + flavor_name + arch_string + build_type_string variant = ":assemble" + flavor_name + arch_string + build_type_string
apk_task_name = ":servoapp" + variant apk_task_name = ":servoapp" + variant
aar_task_name = ":servoview" + variant aar_task_name = ":servoview" + variant
maven_task_name = ":servoview:uploadArchive"
argv = ["./gradlew", "--no-daemon", apk_task_name, aar_task_name] argv = ["./gradlew", "--no-daemon", apk_task_name, aar_task_name]
if maven:
argv.append(maven_task_name)
try: try:
with cd(path.join("support", "android", "apk")): with cd(path.join("support", "android", "apk")):
subprocess.check_call(argv, env=env) subprocess.check_call(argv, env=env)

View file

@ -118,6 +118,7 @@ class PostBuildCommands(CommandBase):
"am start " + extra + " org.mozilla.servo/org.mozilla.servo.MainActivity", "am start " + extra + " org.mozilla.servo/org.mozilla.servo.MainActivity",
"sleep 0.5", "sleep 0.5",
"echo Servo PID: $(pidof org.mozilla.servo)", "echo Servo PID: $(pidof org.mozilla.servo)",
"logcat --pid=$(pidof org.mozilla.servo)",
"exit" "exit"
] ]
args = [self.android_adb_path(env)] args = [self.android_adb_path(env)]
@ -129,7 +130,7 @@ class PostBuildCommands(CommandBase):
if usb: if usb:
args += ["-d"] args += ["-d"]
shell = subprocess.Popen(args + ["shell"], stdin=subprocess.PIPE) shell = subprocess.Popen(args + ["shell"], stdin=subprocess.PIPE)
shell.communicate("\n".join(script) + "\n") shell.communicate(bytes("\n".join(script) + "\n", "utf8"))
return shell.wait() return shell.wait()
args = [bin or self.get_nightly_binary_path(nightly) or self.get_binary_path(build_type)] args = [bin or self.get_nightly_binary_path(nightly) or self.get_binary_path(build_type)]

View file

@ -65,6 +65,10 @@ packages = [
# style (0.64) vs. webxr (0.66) vs. mozjs_sys (0.68). # style (0.64) vs. webxr (0.66) vs. mozjs_sys (0.68).
"bindgen", "bindgen",
# quickcheck (required by layout_2020 for tests) is
# stuck on 0.8.4 with no new releases.
"env_logger",
] ]
# Files that are ignored for all tidy and lint checks. # Files that are ignored for all tidy and lint checks.
files = [ files = [

View file

@ -56,8 +56,6 @@ media-stack = "auto"
# Android information # Android information
[android] [android]
# Defaults to the value of $ANDROID_SDK, $ANDROID_NDK, $ANDROID_TOOLCHAIN, $ANDROID_PLATFORM respectively # Defaults to the value of $ANDROID_SDK_ROOT, $ANDROID_NDK_ROOT respectively
#sdk = "/opt/android-sdk" #sdk = "/opt/android-sdk"
#ndk = "/opt/android-ndk" #ndk = "/opt/android-ndk"
#toolchain = "/opt/android-toolchain"
#platform = "android-18"

View file

@ -1,42 +1,30 @@
import org.apache.tools.ant.taskdefs.condition.Os // Top-level build file where you can add configuration options common to all sub-projects/modules.
plugins {
buildscript { id 'com.android.application' version '8.0.1' apply false
repositories { id 'com.android.library' version '8.0.1' apply false
jcenter()
google()
}
dependencies {
classpath 'com.android.tools.build:gradle:3.1.3'
}
}
allprojects {
repositories {
jcenter()
flatDir {
dirs rootDir.absolutePath + "/../../../target/android/aar"
}
google()
}
} }
// Utility methods // Utility methods
String getTargetDir(boolean debug, String arch) { ext.getTargetDir = { boolean debug, String arch ->
def basePath = project.rootDir.getParentFile().getParentFile().getParentFile().absolutePath def basePath = project.rootDir.getParentFile().getParentFile().getParentFile().absolutePath
return basePath + '/target/android/' + getSubTargetDir(debug, arch) return basePath + '/target/android/' + getSubTargetDir(debug, arch)
} }
String getSubTargetDir(boolean debug, String arch) { ext.getNativeTargetDir = { boolean debug, String arch ->
def basePath = project.rootDir.getParentFile().getParentFile().getParentFile().absolutePath
return basePath + '/target/' + getSubTargetDir(debug, arch)
}
ext.getSubTargetDir = { boolean debug, String arch ->
return getRustTarget(arch) + '/' + (debug ? 'debug' : 'release') return getRustTarget(arch) + '/' + (debug ? 'debug' : 'release')
} }
String getJniLibsPath(boolean debug, String arch) { ext.getJniLibsPath = { boolean debug, String arch ->
return getTargetDir(debug, arch) + '/apk/jniLibs' return getTargetDir(debug, arch) + '/jniLibs'
} }
static String getRustTarget(String arch) { ext.getRustTarget = { String arch ->
switch (arch.toLowerCase()) { switch (arch.toLowerCase()) {
case 'arm' : return 'arm-linux-androideabi'
case 'armv7' : return 'armv7-linux-androideabi' case 'armv7' : return 'armv7-linux-androideabi'
case 'arm64' : return 'aarch64-linux-android' case 'arm64' : return 'aarch64-linux-android'
case 'x86' : return 'i686-linux-android' case 'x86' : return 'i686-linux-android'
@ -44,9 +32,8 @@ static String getRustTarget(String arch) {
} }
} }
static String getNDKAbi(String arch) { ext.getNDKAbi = { String arch ->
switch (arch.toLowerCase()) { switch (arch.toLowerCase()) {
case 'arm' : return 'armeabi'
case 'armv7' : return 'armeabi-v7a' case 'armv7' : return 'armeabi-v7a'
case 'arm64' : return 'arm64-v8a' case 'arm64' : return 'arm64-v8a'
case 'x86' : return 'x86' case 'x86' : return 'x86'
@ -54,16 +41,10 @@ static String getNDKAbi(String arch) {
} }
} }
String getNdkDir() { ext.getNdkDir = { ->
// Read environment variable used in rust build system // Read environment variable used in rust build system
String ndkDir = System.getenv('ANDROID_NDK') String ndkRoot = System.getenv('ANDROID_NDK_ROOT')
if (ndkDir == null) { if (ndkRoot == null) {
ndkDir = System.getenv('ANDROID_NDK_HOME')
}
if (ndkDir == null) {
ndkDir = System.getenv('ANDROID_NDK_ROOT')
}
if (ndkDir == null) {
// Fallback to ndkDir in local.properties // Fallback to ndkDir in local.properties
def rootDir = project.rootDir def rootDir = project.rootDir
def localProperties = new File(rootDir, "local.properties") def localProperties = new File(rootDir, "local.properties")
@ -72,14 +53,13 @@ String getNdkDir() {
properties.load(instr) properties.load(instr)
} }
ndkDir = properties.getProperty('ndk.dir') ndkRoot = properties.getProperty('ndk.dir')
} }
def cmd = Os.isFamily(Os.FAMILY_WINDOWS) ? 'ndk-build.cmd' : 'ndk-build' def ndkDir = ndkRoot != null ? new File(ndkRoot) : null
def ndkbuild = new File(ndkDir + '/' + cmd) if (!ndkDir || !ndkDir.exists()) {
if (!ndkbuild.exists()) { throw new GradleException("Please set a valid ANDROID_NDK_ROOT environment variable" +
throw new GradleException("Please set a valid NDK_HOME environment variable" +
"or ndk.dir path in local.properties file"); "or ndk.dir path in local.properties file");
} }
return ndkbuild.absolutePath return ndkDir.absolutePath
} }

View file

@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

View file

@ -1,6 +1,5 @@
#Wed Jul 11 13:23:08 CEST 2018
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.4-all.zip

View file

@ -1,4 +1,4 @@
#!/usr/bin/env bash #!/usr/bin/env sh
############################################################################## ##############################################################################
## ##
@ -6,42 +6,6 @@
## ##
############################################################################## ##############################################################################
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS=""
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn ( ) {
echo "$*"
}
die ( ) {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
esac
# Attempt to set APP_HOME # Attempt to set APP_HOME
# Resolve links: $0 may be a link # Resolve links: $0 may be a link
PRG="$0" PRG="$0"
@ -60,6 +24,46 @@ cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`" APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM. # Determine the Java command to use to start the JVM.
@ -85,7 +89,7 @@ location of your Java installation."
fi fi
# Increase the maximum file descriptors if we can. # Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n` MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@ -150,11 +154,19 @@ if $cygwin ; then
esac esac
fi fi
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules # Escape application args
function splitJvmOpts() { save () {
JVM_OPTS=("$@") for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
} }
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS APP_ARGS=$(save "$@")
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" # Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
cd "$(dirname "$0")"
fi
exec "$JAVACMD" "$@"

View file

@ -8,14 +8,14 @@
@rem Set local scope for the variables with windows NT shell @rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal if "%OS%"=="Windows_NT" setlocal
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS=
set DIRNAME=%~dp0 set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=. if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0 set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME% set APP_HOME=%DIRNAME%
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m"
@rem Find java.exe @rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome if defined JAVA_HOME goto findJavaFromJavaHome
@ -46,10 +46,9 @@ echo location of your Java installation.
goto fail goto fail
:init :init
@rem Get command-line arguments, handling Windowz variants @rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args if not "%OS%" == "Windows_NT" goto win9xME_args
if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args :win9xME_args
@rem Slurp the command line arguments. @rem Slurp the command line arguments.
@ -60,11 +59,6 @@ set _SKIP=2
if "x%~1" == "x" goto execute if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%* set CMD_LINE_ARGS=%*
goto execute
:4NT_args
@rem Get arguments from the 4NT Shell from JP Software
set CMD_LINE_ARGS=%$
:execute :execute
@rem Setup the command line @rem Setup the command line

View file

@ -16,13 +16,7 @@
MY_LOCAL_PATH := $(call my-dir) MY_LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS) include $(CLEAR_VARS)
LOCAL_PATH:= $(SERVO_TARGET_DIR) LOCAL_PATH := $(SERVO_TARGET_DIR)
LOCAL_MODULE := servojni LOCAL_MODULE := servojni
LOCAL_SRC_FILES := libsimpleservo.so LOCAL_SRC_FILES := libsimpleservo.so
include $(PREBUILT_SHARED_LIBRARY) include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_PATH:= $(SERVO_TARGET_DIR)/../../gstreamer/gst-build-$(APP_ABI)
LOCAL_MODULE := gstreamer
LOCAL_SRC_FILES := libgstreamer_android.so
include $(PREBUILT_SHARED_LIBRARY)

View file

@ -1,5 +1,5 @@
NDK_TOOLCHAIN_VERSION := clang NDK_TOOLCHAIN_VERSION := clang
APP_MODULES := c++_shared servojni gstreamer APP_MODULES := c++_shared servojni
APP_PLATFORM := android-21 APP_PLATFORM := android-30
APP_STL:= c++_shared APP_STL := c++_shared
APP_ABI:= armeabi-v7a x86 APP_ABI := armeabi-v7a x86

View file

@ -1,18 +1,22 @@
apply plugin: 'com.android.application' plugins {
id 'com.android.application'
}
import java.util.regex.Matcher import java.util.regex.Matcher
import java.util.regex.Pattern import java.util.regex.Pattern
android { android {
compileSdkVersion 27 compileSdk 33
buildToolsVersion '27.0.3' buildToolsVersion "33.0.2"
namespace 'org.mozilla.servo'
buildDir = rootDir.absolutePath + "/../../../target/android/gradle/servoapp" buildDir = rootDir.absolutePath + "/../../../target/android/gradle/servoapp"
defaultConfig { defaultConfig {
applicationId "org.mozilla.servo" applicationId "org.mozilla.servo"
minSdkVersion 21 minSdk 30
targetSdkVersion 27 targetSdk 30
versionCode 1 versionCode 1
versionName "1.0.0" versionName "1.0.0"
} }
@ -26,11 +30,7 @@ android {
flavorDimensions "default" flavorDimensions "default"
productFlavors { productFlavors {
main { basic {
}
googlevr {
}
oculusvr {
} }
} }
@ -61,19 +61,6 @@ android {
} }
// Custom build types // Custom build types
armDebug {
initWith(debug)
ndk {
abiFilters getNDKAbi('arm')
}
}
armRelease {
initWith(release)
ndk {
abiFilters getNDKAbi('arm')
}
}
armv7Debug { armv7Debug {
initWith(debug) initWith(debug)
ndk { ndk {
@ -144,7 +131,6 @@ android {
} }
dependencies { dependencies {
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
if (findProject(':servoview-local')) { if (findProject(':servoview-local')) {
implementation project(':servoview-local') implementation project(':servoview-local')
} else { } else {

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto"
package="org.mozilla.servo">
<application android:label="Servo">
<activity android:name=".MainActivity"
android:label="Servo">
<meta-data android:name="android.app.lib_name" android:value="servo" />
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.intent.category.LAUNCHER"/>
<category android:name="com.google.intent.category.DAYDREAM"/>
<category android:name="com.google.intent.category.CARDBOARD"/>
</intent-filter>
</activity>
</application>
</manifest>
<!-- END_INCLUDE(manifest) -->

View file

@ -1,7 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) --> <!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto" <manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto">
package="org.mozilla.servo">
<uses-feature android:glEsVersion="0x00030000" android:required="true" /> <uses-feature android:glEsVersion="0x00030000" android:required="true" />
<uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/> <uses-feature android:name="android.hardware.bluetooth_le" android:required="true"/>

View file

@ -78,8 +78,7 @@ public class MainActivity extends Activity implements Servo.Client {
Intent intent = getIntent(); Intent intent = getIntent();
String args = intent.getStringExtra("servoargs"); String args = intent.getStringExtra("servoargs");
String log = intent.getStringExtra("servolog"); String log = intent.getStringExtra("servolog");
String gstdebug = intent.getStringExtra("gstdebug"); mServoView.setServoArgs(args, log);
mServoView.setServoArgs(args, log, gstdebug);
if (Intent.ACTION_VIEW.equals(intent.getAction())) { if (Intent.ACTION_VIEW.equals(intent.getAction())) {
mServoView.loadUri(intent.getData()); mServoView.loadUri(intent.getData());
@ -89,8 +88,10 @@ public class MainActivity extends Activity implements Servo.Client {
@Override @Override
protected void onDestroy() { protected void onDestroy() {
super.onDestroy(); super.onDestroy();
mMediaSession.hideMediaSessionControls(); if (mMediaSession != null) {
mMediaSession.hideMediaSessionControls();
}
} }
private void setupUrlField() { private void setupUrlField() {
@ -229,31 +230,31 @@ public class MainActivity extends Activity implements Servo.Client {
@Override @Override
public void onMediaSessionMetadata(String title, String artist, String album) { public void onMediaSessionMetadata(String title, String artist, String album) {
if (mMediaSession == null) { if (mMediaSession == null) {
mMediaSession = new MediaSession(mServoView, this, getApplicationContext()); mMediaSession = new MediaSession(mServoView, this, getApplicationContext());
} }
Log.d("onMediaSessionMetadata", title + " " + artist + " " + album); Log.d("onMediaSessionMetadata", title + " " + artist + " " + album);
mMediaSession.updateMetadata(title, artist, album); mMediaSession.updateMetadata(title, artist, album);
} }
@Override @Override
public void onMediaSessionPlaybackStateChange(int state) { public void onMediaSessionPlaybackStateChange(int state) {
Log.d("onMediaSessionPlaybackStateChange", String.valueOf(state)); Log.d("onMediaSessionPlaybackStateChange", String.valueOf(state));
if (mMediaSession == null) { if (mMediaSession == null) {
mMediaSession = new MediaSession(mServoView, this, getApplicationContext()); mMediaSession = new MediaSession(mServoView, this, getApplicationContext());
} }
mMediaSession.setPlaybackState(state); mMediaSession.setPlaybackState(state);
if (state == MediaSession.PLAYBACK_STATE_NONE) { if (state == MediaSession.PLAYBACK_STATE_NONE) {
mMediaSession.hideMediaSessionControls(); mMediaSession.hideMediaSessionControls();
return; return;
} }
if (state == MediaSession.PLAYBACK_STATE_PLAYING || if (state == MediaSession.PLAYBACK_STATE_PLAYING ||
state == MediaSession.PLAYBACK_STATE_PAUSED) { state == MediaSession.PLAYBACK_STATE_PAUSED) {
mMediaSession.showMediaSessionControls(); mMediaSession.showMediaSessionControls();
return; return;
} }
} }
@Override @Override

View file

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
@ -102,4 +102,4 @@
android:focusable="true"/> android:focusable="true"/>
</LinearLayout> </LinearLayout>
</android.support.constraint.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -1,18 +1,23 @@
apply plugin: 'com.android.library' plugins {
id 'com.android.library'
}
import groovy.io.FileType import groovy.io.FileType
import java.util.regex.Matcher import java.util.regex.Matcher
import java.util.regex.Pattern import java.util.regex.Pattern
android { android {
compileSdkVersion 27 compileSdk 33
buildToolsVersion '27.0.3' buildToolsVersion "33.0.2"
namespace 'org.mozilla.servoview'
buildDir = rootDir.absolutePath + "/../../../target/android/gradle/servoview" buildDir = rootDir.absolutePath + "/../../../target/android/gradle/servoview"
ndkPath = getNdkDir()
defaultConfig { defaultConfig {
minSdkVersion 18 minSdk 30
targetSdkVersion 27 targetSdk 30
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
} }
@ -26,24 +31,18 @@ android {
flavorDimensions "default" flavorDimensions "default"
productFlavors { productFlavors {
main { basic {
}
googlevr {
minSdkVersion 21
}
oculusvr {
minSdkVersion 21
} }
} }
splits { splits {
density { density {
enable false enable false
} }
abi { abi {
enable false enable false
} }
} }
buildTypes { buildTypes {
@ -59,65 +58,29 @@ android {
} }
// Custom build types // Custom build types
armDebug {
initWith(debug)
ndk {
abiFilters getNDKAbi('arm')
}
}
armRelease {
initWith(release)
ndk {
abiFilters getNDKAbi('arm')
}
}
armv7Debug { armv7Debug {
initWith(debug) initWith(debug)
ndk {
abiFilters getNDKAbi('armv7')
}
} }
armv7Release { armv7Release {
initWith(release) initWith(release)
ndk {
abiFilters getNDKAbi('armv7')
}
} }
arm64Debug { arm64Debug {
initWith(debug) initWith(debug)
ndk {
abiFilters getNDKAbi('arm64')
}
} }
arm64Release { arm64Release {
initWith(release) initWith(release)
ndk {
abiFilters getNDKAbi('arm64')
}
} }
x86Debug { x86Debug {
initWith(debug) initWith(debug)
ndk {
abiFilters getNDKAbi('x86')
}
} }
x86Release { x86Release {
initWith(release) initWith(release)
ndk {
abiFilters getNDKAbi('x86')
}
} }
} }
sourceSets { sourceSets {
main { main {
} }
armDebug {
jniLibs.srcDirs = [getJniLibsPath(true, 'arm')]
}
armRelease {
jniLibs.srcDirs = [getJniLibsPath(false, 'arm')]
}
armv7Debug { armv7Debug {
jniLibs.srcDirs = [getJniLibsPath(true, 'armv7')] jniLibs.srcDirs = [getJniLibsPath(true, 'armv7')]
} }
@ -145,34 +108,49 @@ android {
} }
} }
// Call our custom NDK Build task using flavor parameters.
// Call our custom NDK Build task using flavor parameters // This step is needed because the Android Gradle Plugin system's
// integration with native C/C++ shared objects (based on the
// `android.externalNativeBuild` dsl object) assumes that we
// actually execute compiler commands to produced the shared
// objects. We already have the libsimpleservo.so produced by rustc.
// We could simply copy the .so to the `sourceSet.jniLibs` folder
// to make AGP bundle it with the APK, but this doesn't copy the STL
// (libc++_shared.so) as well. So we use ndk-build as a glorified
// `cp` command to copy the libsimpleservo.so from target/<arch>
// to target/android and crucially also include libc++_shared.so
// as well.
//
// FIXME(mukilan): According to the AGP docs, we should not be
// relying on task names used by the plugin system to hook into
// the build process, but instead we should use officially supported
// extension points such as `androidComponents.beforeVariants`
tasks.all { tasks.all {
compileTask -> compileTask ->
Pattern pattern = Pattern.compile(/^compile[A-Z][\w\d]+([A-Z][\w\d]+)(Debug|Release)Ndk/) // This matches the task `mergeBasicArmv7DebugJniLibFolders`.
Pattern pattern = Pattern.compile(/^merge[A-Z][\w\d]+([A-Z][\w\d]+)(Debug|Release)JniLibFolders/)
Matcher matcher = pattern.matcher(compileTask.name) Matcher matcher = pattern.matcher(compileTask.name)
if (!matcher.find()) { if (!matcher.find()) {
return return
} }
def taskName = "ndkbuild" + compileTask.name def taskName = "ndkbuild" + compileTask.name
tasks.create(name: taskName, type: Exec) { tasks.create(name: taskName, type: Exec) {
def debug = compileTask.name.contains("Debug") def debug = compileTask.name.contains("Debug")
def arch = matcher.group(1) def arch = matcher.group(1)
commandLine getNdkDir(), commandLine getNdkDir() + "/ndk-build",
'APP_BUILD_SCRIPT=../jni/Android.mk', 'APP_BUILD_SCRIPT=../jni/Android.mk',
'NDK_APPLICATION_MK=../jni/Application.mk', 'NDK_APPLICATION_MK=../jni/Application.mk',
'NDK_LIBS_OUT=' + getJniLibsPath(debug, arch), 'NDK_LIBS_OUT=' + getJniLibsPath(debug, arch),
'NDK_OUT=' + getTargetDir(debug, arch) + '/apk/obj', 'NDK_DEBUG=' + (debug ? '1' : '0'),
'NDK_DEBUG=' + (debug ? '1' : '0'), 'APP_ABI=' + getNDKAbi(arch),
'APP_ABI=' + getNDKAbi(arch), 'NDK_LOG=1',
'SERVO_TARGET_DIR=' + getTargetDir(debug, arch) 'SERVO_TARGET_DIR=' + getNativeTargetDir(debug, arch)
} }
compileTask.dependsOn taskName compileTask.dependsOn taskName
} }
project.afterEvaluate { project.afterEvaluate {
android.libraryVariants.all { variant -> android.libraryVariants.all { variant ->
Pattern pattern = Pattern.compile(/^[\w\d]+([A-Z][\w\d]+)(Debug|Release)/) Pattern pattern = Pattern.compile(/^[\w\d]+([A-Z][\w\d]+)(Debug|Release)/)
@ -205,7 +183,7 @@ dependencies {
] ]
// Iterate all build types and dependencies // Iterate all build types and dependencies
// For each dependency call the proper implementation command and set the correct dependency path // For each dependency call the proper implementation command and set the correct dependency path
def list = ['arm', 'armv7', 'arm64', 'x86'] def list = ['armv7', 'arm64', 'x86']
for (arch in list) { for (arch in list) {
for (debug in [true, false]) { for (debug in [true, false]) {
String basePath = getTargetDir(debug, arch) + "/build" String basePath = getTargetDir(debug, arch) + "/build"
@ -220,10 +198,9 @@ dependencies {
} }
} }
googlevrImplementation 'com.google.vr:sdk-base:1.140.0' implementation 'androidx.appcompat:appcompat:1.6.1'
googlevrImplementation(name: 'GVRService', ext: 'aar') implementation 'com.google.android.material:material:1.9.0'
oculusvrImplementation(name: 'OVRService', ext: 'aar') implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
implementation 'com.android.support.constraint:constraint-layout:1.1.2'
} }
// folderFilter can be used to improve search performance // folderFilter can be used to improve search performance
@ -258,42 +235,3 @@ class ServoDependency {
public String fileName; public String fileName;
public String folderFilter; public String folderFilter;
} }
apply plugin: 'maven'
import org.gradle.api.internal.artifacts.publish.DefaultPublishArtifact
uploadArchives {
doFirst {
for ( arch in ["arm", "armv7", "arm64", "x86"] ) {
def target = getTargetDir(false, arch)
def aar = new File(target, "servoview.aar")
if (aar.exists()) {
def art = new DefaultPublishArtifact("servoview-" + arch, "aar", "aar", null, new Date(), aar);
project.artifacts.add('archives', art)
}
}
}
repositories.mavenDeployer {
repository(url: "file://localhost/${buildDir}/maven")
def cmd = "git rev-parse --short HEAD"
def proc = cmd.execute()
def commit = proc.text.trim()
def version = "0.0.1." + new Date().format('yyyyMMdd') + "." + commit
for ( arch_ in ["arm", "armv7", "arm64", "x86"] ) {
def arch = arch_
addFilter(arch) {artifact, file -> artifact.name == "servoview-" + arch}
pom(arch).artifactId = "servoview-" + arch
pom(arch).groupId = 'org.mozilla.servoview'
pom(arch).version = version
pom(arch).project {
licenses {
license {
name 'The Mozilla Public License, v. 2.0'
url 'https://mozilla.org/MPL/2.0/'
distribution 'repo'
}
}
}
}
}
}

View file

@ -1,19 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.mozilla.servoview">
<application>
<activity android:name=".MainActivity"
android:screenOrientation="landscape"
android:enableVrMode="@string/gvr_vr_mode_component"
android:resizeableActivity="false">
<!-- Intent filter that enables this app to be launched from the
Daydream Home menu. -->
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="com.google.intent.category.DAYDREAM"/>
</intent-filter>
</activity>
</application>
</manifest>
<!-- END_INCLUDE(manifest) -->

View file

@ -1,2 +1 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android" />
package="org.mozilla.servoview" />

View file

@ -6,7 +6,7 @@
package org.mozilla.servoview; package org.mozilla.servoview;
import android.app.Activity; import android.app.Activity;
import android.view.Surface;
/** /**
* Maps /ports/libsimpleservo API * Maps /ports/libsimpleservo API
*/ */
@ -14,13 +14,12 @@ import android.app.Activity;
public class JNIServo { public class JNIServo {
JNIServo() { JNIServo() {
System.loadLibrary("c++_shared"); System.loadLibrary("c++_shared");
System.loadLibrary("gstreamer_android");
System.loadLibrary("simpleservo"); System.loadLibrary("simpleservo");
} }
public native String version(); public native String version();
public native void init(Activity activity, ServoOptions options, Callbacks callbacks); public native void init(Activity activity, ServoOptions options, Callbacks callbacks, Surface surface);
public native void deinit(); public native void deinit();
@ -66,6 +65,9 @@ public class JNIServo {
public native void click(float x, float y); public native void click(float x, float y);
public native void pauseCompositor();
public native void resumeCompositor(Surface surface, ServoCoordinates coords);
public native void mediaSessionAction(int action); public native void mediaSessionAction(int action);
public static class ServoOptions { public static class ServoOptions {

View file

@ -8,6 +8,7 @@ package org.mozilla.servoview;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.util.Log; import android.util.Log;
import android.view.Surface;
import java.util.concurrent.Callable; import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask; import java.util.concurrent.FutureTask;
@ -30,21 +31,16 @@ public class Servo {
RunCallback runCallback, RunCallback runCallback,
GfxCallbacks gfxcb, GfxCallbacks gfxcb,
Client client, Client client,
Activity activity) { Activity activity,
Surface surface) {
mRunCallback = runCallback; mRunCallback = runCallback;
mServoCallbacks = new Callbacks(client, gfxcb); mServoCallbacks = new Callbacks(client, gfxcb);
mRunCallback.inGLThread(() -> { mRunCallback.inGLThread(() -> {
mJNI.init(activity, options, mServoCallbacks); mJNI.init(activity, options, mServoCallbacks, surface);
}); });
try {
GStreamer.init((Context) activity);
} catch (Exception e) {
e.printStackTrace();
}
} }
public void resetGfxCallbacks(GfxCallbacks gfxcb) { public void resetGfxCallbacks(GfxCallbacks gfxcb) {
@ -164,6 +160,13 @@ public class Servo {
mRunCallback.inGLThread(() -> mJNI.click(x, y)); mRunCallback.inGLThread(() -> mJNI.click(x, y));
} }
public void pauseCompositor() {
mRunCallback.inGLThread(() -> mJNI.pauseCompositor());
}
public void resumeCompositor(Surface surface, ServoCoordinates coords) {
mRunCallback.inGLThread(() -> mJNI.resumeCompositor(surface, coords));
}
public void suspend(boolean suspended) { public void suspend(boolean suspended) {
mSuspended = suspended; mSuspended = suspended;
} }

View file

@ -1,302 +0,0 @@
/* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
* 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/. */
package org.mozilla.servoview;
import android.app.Activity;
import android.net.Uri;
import android.opengl.EGL14;
import android.opengl.EGLConfig;
import android.opengl.EGLContext;
import android.opengl.EGLDisplay;
import android.opengl.EGLSurface;
import android.opengl.GLUtils;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.Surface;
import org.mozilla.servoview.JNIServo.ServoCoordinates;
import org.mozilla.servoview.JNIServo.ServoOptions;
import org.mozilla.servoview.Servo.Client;
import org.mozilla.servoview.Servo.GfxCallbacks;
import org.mozilla.servoview.Servo.RunCallback;
import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
import static android.opengl.EGL14.EGL_NO_CONTEXT;
import static android.opengl.EGL14.EGL_NO_SURFACE;
import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
public class ServoSurface {
private static final String LOGTAG = "ServoSurface";
private final GLThread mGLThread;
private final Handler mMainLooperHandler;
private Handler mGLLooperHandler;
private Surface mASurface;
private int mPadding;
private int mWidth;
private int mHeight;
private long mVRExternalContext;
private Servo mServo;
private Client mClient = null;
private String mServoArgs;
private String mServoLog;
private String mInitialUri;
private Activity mActivity;
public ServoSurface(Surface surface, int width, int height, int padding) {
mPadding = padding;
mWidth = width;
mHeight = height;
mASurface = surface;
mMainLooperHandler = new Handler(Looper.getMainLooper());
mGLThread = new GLThread();
}
public void onSurfaceChanged(Surface surface) {
mASurface = surface;
mGLThread.onSurfaceChanged();
}
public void setClient(Client client) {
mClient = client;
}
public void setServoArgs(String args, String log) {
mServoArgs = args;
mServoLog = log;
}
public void setActivity(Activity activity) {
mActivity = activity;
}
public void setVRExternalContext(long context) {
mVRExternalContext = context;
}
public void runLoop() {
mGLThread.start();
}
public void shutdown() {
Log.d(LOGTAG, "shutdown");
mServo.shutdown();
mServo = null;
mGLThread.shutdown();
try {
Log.d(LOGTAG, "Waiting for GL thread to shutdown");
mGLThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void reload() {
mServo.reload();
}
public void goBack() {
mServo.goBack();
}
public void goForward() {
mServo.goForward();
}
public void stop() {
mServo.stop();
}
public void loadUri(String uri) {
if (mServo != null) {
mServo.loadUri(uri);
} else {
mInitialUri = uri;
}
}
public void loadUri(Uri uri) {
loadUri(uri.toString());
}
public void scrollStart(int dx, int dy, int x, int y) {
mServo.scrollStart(dx, dy, x, y);
}
public void scroll(int dx, int dy, int x, int y) {
mServo.scroll(dx, dy, x, y);
}
public void scrollEnd(int dx, int dy, int x, int y) {
mServo.scrollEnd(dx, dy, x, y);
}
public void click(float x, float y) {
mServo.click(x, y);
}
public void onSurfaceResized(int width, int height) {
mWidth = width;
mHeight = height;
ServoCoordinates coords = new ServoCoordinates();
coords.x = mPadding;
coords.y = mPadding;
coords.width = width - 2 * mPadding;
coords.height = height - 2 * mPadding;
coords.fb_width = width;
coords.fb_height = height;
mServo.resize(coords);
}
static class GLSurface implements GfxCallbacks {
private EGLConfig[] mEGLConfigs;
private EGLDisplay mEglDisplay;
private EGLContext mEglContext;
private EGLSurface mEglSurface;
void throwGLError(String function) {
throwGLError(function, EGL14.eglGetError());
}
void throwGLError(String function, int error) {
throw new RuntimeException("Error: " + function + "() Failed " + GLUtils.getEGLErrorString(error));
}
GLSurface(Surface surface) {
mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
int[] version = new int[2];
if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) {
throwGLError("eglInitialize");
}
mEGLConfigs = new EGLConfig[1];
int[] configsCount = new int[1];
int[] configSpec = new int[]{
EGL14.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
EGL14.EGL_RED_SIZE, 8,
EGL14.EGL_GREEN_SIZE, 8,
EGL14.EGL_BLUE_SIZE, 8,
EGL14.EGL_ALPHA_SIZE, 8,
EGL14.EGL_DEPTH_SIZE, 24,
EGL14.EGL_STENCIL_SIZE, 0,
EGL14.EGL_NONE
};
if ((!EGL14.eglChooseConfig(mEglDisplay, configSpec, 0, mEGLConfigs, 0, 1, configsCount, 0)) || (configsCount[0] == 0)) {
throwGLError("eglChooseConfig");
}
if (mEGLConfigs[0] == null) {
throw new RuntimeException("Error: eglConfig() not Initialized");
}
int[] attrib_list = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL14.EGL_NONE};
mEglContext = EGL14.eglCreateContext(mEglDisplay, mEGLConfigs[0], EGL14.EGL_NO_CONTEXT, attrib_list, 0);
int glError = EGL14.eglGetError();
if (glError != EGL14.EGL_SUCCESS) {
throwGLError("eglCreateContext", glError);
}
mEglSurface = EGL14.eglCreateWindowSurface(mEglDisplay, mEGLConfigs[0], surface, new int[]{EGL14.EGL_NONE}, 0);
if (mEglSurface == null || mEglSurface == EGL14.EGL_NO_SURFACE) {
glError = EGL14.eglGetError();
if (glError == EGL14.EGL_BAD_NATIVE_WINDOW) {
Log.e(LOGTAG, "Error: createWindowSurface() Returned EGL_BAD_NATIVE_WINDOW.");
return;
}
throwGLError("createWindowSurface", glError);
}
makeCurrent();
}
public void makeCurrent() {
if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) {
throwGLError("eglMakeCurrent");
}
}
public void flushGLBuffers() {
EGL14.eglSwapBuffers(mEglDisplay, mEglSurface);
}
public void animationStateChanged(boolean animating) {
// FIXME
}
void destroy() {
Log.d(LOGTAG, "Destroying surface");
if (!EGL14.eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) {
throwGLError("eglMakeCurrent");
}
if (!EGL14.eglDestroyContext(mEglDisplay, mEglContext)) {
throwGLError("eglDestroyContext");
}
if (!EGL14.eglDestroySurface(mEglDisplay, mEglSurface)) {
throwGLError("eglDestroySurface");
}
if (!EGL14.eglTerminate(mEglDisplay)) {
throwGLError("eglTerminate");
}
}
}
class GLThread extends Thread implements RunCallback {
private GLSurface mSurface;
public void inGLThread(Runnable r) {
mGLLooperHandler.post(r);
}
public void inUIThread(Runnable r) {
mMainLooperHandler.post(r);
}
public void onSurfaceChanged() {
Log.d(LOGTAG, "GLThread::onSurfaceChanged");
mSurface.destroy();
mSurface = new GLSurface(mASurface);
mServo.resetGfxCallbacks(mSurface);
}
public void shutdown() {
Log.d(LOGTAG, "GLThread::shutdown");
mSurface.destroy();
mGLLooperHandler.getLooper().quitSafely();
}
public void run() {
Looper.prepare();
mSurface = new GLSurface(mASurface);
mGLLooperHandler = new Handler();
inUIThread(() -> {
ServoCoordinates coords = new ServoCoordinates();
coords.x = mPadding;
coords.y = mPadding;
coords.width = mWidth - 2 * mPadding;
coords.height = mHeight - 2 * mPadding;
coords.fb_width = mWidth;
coords.fb_height = mHeight;
ServoOptions options = new ServoOptions();
options.coordinates = coords;
options.args = mServoArgs;
options.density = 1;
options.url = mInitialUri;
options.logStr = mServoLog;
options.enableLogs = true;
options.enableSubpixelTextAntialiasing = false;
options.VRExternalContext = mVRExternalContext;
mServo = new Servo(options, this, mSurface, mClient, mActivity);
});
Looper.loop();
}
}
}

View file

@ -7,17 +7,15 @@ package org.mozilla.servoview;
import android.app.Activity; import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.opengl.GLES31;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics; import android.util.DisplayMetrics;
import android.util.Log; import android.util.Log;
import android.view.Choreographer; import android.view.Surface;
import android.view.GestureDetector; import android.view.SurfaceView;
import android.view.MotionEvent; import android.view.SurfaceHolder;
import android.view.ScaleGestureDetector;
import android.widget.OverScroller;
import org.mozilla.servoview.JNIServo.ServoCoordinates; import org.mozilla.servoview.JNIServo.ServoCoordinates;
import org.mozilla.servoview.JNIServo.ServoOptions; import org.mozilla.servoview.JNIServo.ServoOptions;
@ -25,41 +23,45 @@ import org.mozilla.servoview.Servo.Client;
import org.mozilla.servoview.Servo.GfxCallbacks; import org.mozilla.servoview.Servo.GfxCallbacks;
import org.mozilla.servoview.Servo.RunCallback; import org.mozilla.servoview.Servo.RunCallback;
import javax.microedition.khronos.egl.EGLConfig; import android.view.Choreographer;
import javax.microedition.khronos.opengles.GL10; import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.OverScroller;
public class ServoView extends GLSurfaceView import java.util.ArrayList;
implements
GestureDetector.OnGestureListener,
ScaleGestureDetector.OnScaleGestureListener,
Choreographer.FrameCallback,
GfxCallbacks,
RunCallback {
public class ServoView extends SurfaceView
implements
GfxCallbacks,
RunCallback,
Choreographer.FrameCallback,
GestureDetector.OnGestureListener,
ScaleGestureDetector.OnScaleGestureListener {
private static final String LOGTAG = "ServoView"; private static final String LOGTAG = "ServoView";
private GLThread mGLThread;
private Activity mActivity; private Handler mGLLooperHandler;
private Servo mServo; private Surface mASurface;
protected Servo mServo = null;
private Client mClient = null; private Client mClient = null;
private Uri mInitialUri = null;
private boolean mAnimating;
private String mServoArgs; private String mServoArgs;
private String mServoLog; private String mServoLog;
private String mGstDebug; private String mInitialUri;
private Activity mActivity;
private GestureDetector mGestureDetector; private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private OverScroller mScroller;
private int mLastX = 0; private int mLastX = 0;
private int mCurX = 0; private int mCurX = 0;
private int mLastY = 0; private int mLastY = 0;
private int mCurY = 0; private int mCurY = 0;
private boolean mFlinging; private boolean mFlinging;
private ScaleGestureDetector mScaleGestureDetector;
private OverScroller mScroller;
private boolean mZooming; private boolean mZooming;
private float mZoomFactor = 1; private float mZoomFactor = 1;
private boolean mRedrawing; private boolean mRedrawing;
private boolean mAnimating;
private boolean mPaused = false;
public ServoView(Context context) { public ServoView(Context context) {
super(context); super(context);
@ -71,79 +73,49 @@ public class ServoView extends GLSurfaceView
init(context); init(context);
} }
public void onDetachedFromWindow() {
mServo.shutdown();
mServo = null;
super.onDetachedFromWindow();
}
private void init(Context context) { private void init(Context context) {
mActivity = (Activity) context; mActivity = (Activity) context;
setFocusable(true); setFocusable(true);
setFocusableInTouchMode(true); setFocusableInTouchMode(true);
setClickable(true);
ArrayList view = new ArrayList();
view.add(this);
addTouchables(view);
setWillNotCacheDrawing(false); setWillNotCacheDrawing(false);
setEGLContextClientVersion(3);
setEGLConfigChooser(8, 8, 8, 8, 24, 0);
setPreserveEGLContextOnPause(true);
ServoGLRenderer mRenderer = new ServoGLRenderer(this);
setRenderer(mRenderer);
setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY);
initGestures(context); initGestures(context);
mGLThread = new GLThread(mActivity, this);
getHolder().addCallback(mGLThread);
mGLThread.start();
} }
public void setServoArgs(String args, String log, String gstdebug) {
public void setClient(Client client) {
mClient = client;
}
public void setServoArgs(String args, String log) {
mServoArgs = args; mServoArgs = args;
mServoLog = log; mServoLog = log;
mGstDebug = gstdebug;
} }
public void reload() {
mServo.reload(); // RunCallback
public void inGLThread(Runnable r) {
mGLLooperHandler.post(r);
} }
public void goBack() { public void inUIThread(Runnable r) {
mServo.goBack(); post(r);
} }
public void goForward() {
mServo.goForward();
}
public void stop() {
mServo.stop();
}
public void onSurfaceInvalidated(int width, int height) {
if (mServo != null) {
ServoCoordinates coords = new ServoCoordinates();
coords.width = width;
coords.height = height;
coords.fb_width = width;
coords.fb_height = height;
mServo.resize(coords);
mServo.refresh();
}
}
public void loadUri(Uri uri) {
if (mServo != null) {
mServo.loadUri(uri.toString());
} else {
mInitialUri = uri;
}
}
public void mediaSessionAction(int action) {
mServo.mediaSessionAction(action);
}
// GfxCallbacks
public void flushGLBuffers() { public void flushGLBuffers() {
requestRender();
} }
// Scroll and click // Scroll and click
public void animationStateChanged(boolean animating) { public void animationStateChanged(boolean animating) {
if (!mAnimating && animating) { if (!mAnimating && animating) {
post(() -> startLooping()); post(() -> startLooping());
@ -154,48 +126,6 @@ public class ServoView extends GLSurfaceView
public void makeCurrent() { public void makeCurrent() {
} }
public void inGLThread(Runnable f) {
queueEvent(f);
}
public void inUIThread(Runnable f) {
post(f);
}
public void onGLReady() {
ServoCoordinates coords = new ServoCoordinates();
coords.width = getWidth();
coords.height = getHeight();
coords.fb_width = getWidth();
coords.fb_height = getHeight();
ServoOptions options = new ServoOptions();
options.args = mServoArgs;
options.coordinates = coords;
options.enableLogs = true;
options.enableSubpixelTextAntialiasing = true;
DisplayMetrics metrics = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
options.density = metrics.density;
inGLThread(() -> {
String uri = mInitialUri == null ? null : mInitialUri.toString();
options.url = uri;
options.logStr = mServoLog;
options.gstDebugStr = mGstDebug;
mServo = new Servo(options, this, this, mClient, mActivity);
});
}
public void setClient(Client client) {
mClient = client;
}
private void initGestures(Context context) {
mGestureDetector = new GestureDetector(context, this);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mScroller = new OverScroller(context);
}
private void startLooping() { private void startLooping() {
// In case we were already drawing. // In case we were already drawing.
@ -253,6 +183,63 @@ public class ServoView extends GLSurfaceView
} }
} }
// Calls from Activity
public void onPause() {
if (mServo != null) {
mServo.suspend(true);
}
}
public void onResume() {
if (mServo != null) {
mServo.suspend(false);
}
}
public void reload() {
mServo.reload();
}
public void goBack() {
mServo.goBack();
}
public void goForward() {
mServo.goForward();
}
public void stop() {
mServo.stop();
}
public void loadUri(String uri) {
if (mServo != null) {
mServo.loadUri(uri);
} else {
mInitialUri = uri;
}
}
public void loadUri(Uri uri) {
loadUri(uri.toString());
}
public void scrollStart(int dx, int dy, int x, int y) {
mServo.scrollStart(dx, dy, x, y);
}
public void scroll(int dx, int dy, int x, int y) {
mServo.scroll(dx, dy, x, y);
}
public void scrollEnd(int dx, int dy, int x, int y) {
mServo.scrollEnd(dx, dy, x, y);
}
public void click(float x, float y) {
mServo.click(x, y);
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
mFlinging = true; mFlinging = true;
@ -275,20 +262,22 @@ public class ServoView extends GLSurfaceView
return true; return true;
} }
@Override
public boolean onTouchEvent(final MotionEvent e) { public boolean onTouchEvent(final MotionEvent e) {
mGestureDetector.onTouchEvent(e); mGestureDetector.onTouchEvent(e);
mScaleGestureDetector.onTouchEvent(e); mScaleGestureDetector.onTouchEvent(e);
int action = e.getActionMasked(); int action = e.getActionMasked();
float x = e.getX(); float x = e.getX();
float y = e.getY(); float y = e.getY();
int pointerIndex = e.getActionIndex(); int pointerIndex = e.getActionIndex();
int pointerId = e.getPointerId(pointerIndex); int pointerId = e.getPointerId(pointerIndex);
switch (action) { switch (action) {
case (MotionEvent.ACTION_DOWN): case (MotionEvent.ACTION_DOWN):
mServo.touchDown(x, y, pointerId); case (MotionEvent.ACTION_POINTER_DOWN):
mFlinging = false; mFlinging = false;
mScroller.forceFinished(true); mScroller.forceFinished(true);
mCurX = (int) x; mCurX = (int) x;
@ -299,32 +288,35 @@ public class ServoView extends GLSurfaceView
case (MotionEvent.ACTION_MOVE): case (MotionEvent.ACTION_MOVE):
mCurX = (int) x; mCurX = (int) x;
mCurY = (int) y; mCurY = (int) y;
mServo.touchMove(x, y, pointerId);
return true; return true;
case (MotionEvent.ACTION_UP): case (MotionEvent.ACTION_UP):
mServo.touchUp(x, y, pointerId); case (MotionEvent.ACTION_POINTER_UP):
return true;
case (MotionEvent.ACTION_CANCEL): case (MotionEvent.ACTION_CANCEL):
mServo.touchCancel(x, y, pointerId);
return true; return true;
default: default:
return true; return true;
} }
} }
public boolean onSingleTapUp(MotionEvent e) { // OnGestureListener
return false;
}
public void onLongPress(MotionEvent e) { public void onLongPress(MotionEvent e) {
} }
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
mServo.scroll((int) -distanceX, (int) -distanceY, (int) e1.getX(), (int) e1.getY());
return true; return true;
} }
public boolean onSingleTapUp(MotionEvent e) {
click(e.getX(), e.getY());
return false;
}
public void onShowPress(MotionEvent e) { public void onShowPress(MotionEvent e) {
} }
// OnScaleGestureListener
@Override @Override
public boolean onScaleBegin(ScaleGestureDetector detector) { public boolean onScaleBegin(ScaleGestureDetector detector) {
if (mScroller.isFinished()) { if (mScroller.isFinished()) {
@ -351,41 +343,81 @@ public class ServoView extends GLSurfaceView
mServo.pinchZoomEnd(mZoomFactor, 0, 0); mServo.pinchZoomEnd(mZoomFactor, 0, 0);
} }
private void initGestures(Context context) {
@Override mGestureDetector = new GestureDetector(context, this);
public void onPause() { mScaleGestureDetector = new ScaleGestureDetector(context, this);
super.onPause(); mScroller = new OverScroller(context);
if (mServo != null) {
mServo.suspend(true);
}
} }
@Override public void mediaSessionAction(int action) {
public void onResume() { mServo.mediaSessionAction(action);
super.onResume();
if (mServo != null) {
mServo.suspend(false);
}
} }
static class ServoGLRenderer implements Renderer { class GLThread extends Thread implements SurfaceHolder.Callback {
private Activity mActivity;
private final ServoView mView; private ServoView mServoView;
GLThread(Activity activity, ServoView servoView) {
ServoGLRenderer(ServoView view) { mActivity = activity;
mView = view; mServoView = servoView;
} }
public void onSurfaceCreated(GL10 unused, EGLConfig config) { public void surfaceCreated(SurfaceHolder holder) {
mView.onGLReady(); Log.d(LOGTAG, "GLThread::surfaceCreated");
ServoCoordinates coords = new ServoCoordinates();
coords.width = mServoView.getWidth();
coords.height = mServoView.getHeight();
coords.fb_width = mServoView.getWidth();
coords.fb_height = mServoView.getHeight();
Surface surface = holder.getSurface();
ServoOptions options = new ServoOptions();
options.args = mServoView.mServoArgs;
options.coordinates = coords;
options.enableLogs = true;
options.enableSubpixelTextAntialiasing = true;
DisplayMetrics metrics = new DisplayMetrics();
mActivity.getWindowManager().getDefaultDisplay().getMetrics(metrics);
options.density = metrics.density;
if (mServoView.mServo == null && !mPaused) {
mServoView.mServo = new Servo(
options, mServoView, mServoView, mClient, mActivity, surface);
} else {
mPaused = false;
mServoView.mServo.resumeCompositor(surface, coords);
}
} }
public void onDrawFrame(GL10 unused) { public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
Log.d(LOGTAG, "GLThread::surfaceChanged");
ServoCoordinates coords = new ServoCoordinates();
coords.width = width;
coords.height = height;
coords.fb_width = width;
coords.fb_height = height;
mServoView.mServo.resize(coords);
} }
public void onSurfaceChanged(GL10 gl, int width, int height) { public void surfaceDestroyed(SurfaceHolder holder) {
GLES31.glViewport(0, 0, width, height); Log.d(LOGTAG, "GLThread::surfaceDestroyed");
mView.onSurfaceInvalidated(width, height); mPaused = true;
mServoView.mServo.pauseCompositor();
}
public void shutdown() {
Log.d(LOGTAG, "GLThread::shutdown");
mGLLooperHandler.getLooper().quitSafely();
}
public void run() {
Looper.prepare();
mGLLooperHandler = new Handler();
Looper.loop();
} }
} }
} }

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- BEGIN_INCLUDE(manifest) -->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.mozilla.servoview">>
<application>
<meta-data android:name="com.samsung.android.vr.application.mode" android:value="vr_only"/>
<activity android:name=".MainActivity" android:screenOrientation="landscape">
</activity>
</application>
</manifest>
<!-- END_INCLUDE(manifest) -->

View file

@ -1,3 +1,19 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
include ':servoapp' include ':servoapp'
def userPropertiesFile = new File('user.properties') def userPropertiesFile = new File('user.properties')

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# 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/.
set -o errexit
set -o nounset
set -o pipefail
source ./support/android/fakeld/fake-ld.sh
export _GCC_PARAMS="${@}"
call_gcc "arch-arm" "armeabi" "arm-linux-androideabi"

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# 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/.
set -o errexit
set -o nounset
set -o pipefail
source ./support/android/fakeld/fake-ld.sh
export _GCC_PARAMS="${@}"
call_gcc "arch-arm64" "arm64-v8a" "aarch64-linux-android"

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# 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/.
set -o errexit
set -o nounset
set -o pipefail
source ./support/android/fakeld/fake-ld.sh
export _GCC_PARAMS="${@}"
call_gcc "arch-arm" "armeabi-v7a" "armv7-linux-androideabi"

View file

@ -1,14 +0,0 @@
#!/usr/bin/env bash
# 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/.
set -o errexit
set -o nounset
set -o pipefail
source ./support/android/fakeld/fake-ld.sh
export _GCC_PARAMS="${@}"
call_gcc "arch-x86" "x86" "i686-linux-android"

View file

@ -1,2 +0,0 @@
@echo off
gcc -mwindows %*

View file

@ -1,39 +0,0 @@
#!/usr/bin/env bash
# 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/.
set -o errexit
set -o nounset
set -o pipefail
call_gcc()
{
TARGET_DIR="${OUT_DIR}/../../.."
export _ANDROID_ARCH=$1
export _ANDROID_TARGET=$3
export ANDROID_SYSROOT="${ANDROID_NDK}/platforms/${ANDROID_PLATFORM}/${_ANDROID_ARCH}"
ANDROID_TOOLCHAIN=""
for host in "linux-x86_64" "linux-x86" "darwin-x86_64" "darwin-x86"; do
if [[ -d "${ANDROID_NDK}/toolchains/llvm/prebuilt/${host}/bin" ]]; then
ANDROID_TOOLCHAIN="${ANDROID_NDK}/toolchains/llvm/prebuilt/${host}/bin"
break
fi
done
ANDROID_CPU_ARCH_DIR=$2
ANDROID_CXX_LIBS="${ANDROID_NDK}/sources/cxx-stl/llvm-libc++/libs/${ANDROID_CPU_ARCH_DIR}"
echo "toolchain: ${ANDROID_TOOLCHAIN}"
echo "libs dir: ${ANDROID_CXX_LIBS}"
echo "sysroot: ${ANDROID_SYSROOT}"
echo "targetdir: ${TARGET_DIR}"
"${ANDROID_TOOLCHAIN}/clang" \
--sysroot="${ANDROID_SYSROOT}" \
--gcc-toolchain="${GCC_TOOLCHAIN}" \
--target="${_ANDROID_TARGET}" \
-L "${ANDROID_CXX_LIBS}" ${_GCC_PARAMS} -lc++
}