diff --git a/ports/libsimpleservo/src/api.rs b/ports/libsimpleservo/src/api.rs index a271dda59e45..57c6d644500a 100644 --- a/ports/libsimpleservo/src/api.rs +++ b/ports/libsimpleservo/src/api.rs @@ -39,6 +39,9 @@ pub trait HostTrait { /// Will be called from the thread used for the init call. /// Will be called when the GL buffer has been updated. fn flush(&self); + /// Will be called before drawing. + /// Time to make the targetted GL context current. + fn make_current(&self); /// Page starts loading. /// "Reload button" should be disabled. /// "Stop button" should be enabled. @@ -353,6 +356,7 @@ impl WindowMethods for ServoCallbacks { _height: Length, ) -> bool { debug!("WindowMethods::prepare_for_composite"); + self.host_callbacks.make_current(); true } diff --git a/ports/libsimpleservo/src/capi.rs b/ports/libsimpleservo/src/capi.rs index cbd4f36108c2..89ffe408759e 100644 --- a/ports/libsimpleservo/src/capi.rs +++ b/ports/libsimpleservo/src/capi.rs @@ -28,6 +28,7 @@ fn call(f: F) where F: Fn(&mut ServoGlue) -> Result<(), &'static str> { #[repr(C)] pub struct CHostCallbacks { pub flush: extern fn(), + pub make_current: extern fn(), pub on_load_started: extern fn(), pub on_load_ended: extern fn(), pub on_title_changed: extern fn(title: *const c_char), @@ -223,6 +224,11 @@ impl HostTrait for HostCallbacks { (self.0.flush)(); } + fn make_current(&self) { + debug!("make_current"); + (self.0.make_current)(); + } + fn on_load_started(&self) { debug!("on_load_ended"); (self.0.on_load_started)(); diff --git a/ports/libsimpleservo/src/jniapi.rs b/ports/libsimpleservo/src/jniapi.rs index 84649659e81a..6f8653f774ed 100644 --- a/ports/libsimpleservo/src/jniapi.rs +++ b/ports/libsimpleservo/src/jniapi.rs @@ -36,21 +36,19 @@ where } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_version(env: JNIEnv, _class: JClass) -> jstring { +pub fn Java_com_mozilla_servoview_JNIServo_version(env: JNIEnv, _class: JClass) -> jstring { let v = api::servo_version(); let output = env.new_string(v).expect("Couldn't create java string"); output.into_inner() } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_init( +pub fn Java_com_mozilla_servoview_JNIServo_init( env: JNIEnv, _: JClass, activity: JObject, args: JString, url: JString, - wakeup_obj: JObject, - readfile_obj: JObject, callbacks_obj: JObject, width: jint, height: jint, @@ -80,9 +78,11 @@ pub fn Java_com_mozilla_servoview_NativeServo_init( Some(env.get_string(url).expect("Couldn't get java string").into()) }; - let wakeup = Box::new(WakeupCallback::new(wakeup_obj, &env)); - let readfile = Box::new(ReadFileCallback::new(readfile_obj, &env)); - let callbacks = Box::new(HostCallbacks::new(callbacks_obj, &env)); + let callbacks_ref = env.new_global_ref(callbacks_obj).unwrap(); + + let wakeup = Box::new(WakeupCallback::new(callbacks_ref.clone(), &env)); + let readfile = Box::new(ReadFileCallback::new(callbacks_ref.clone(), &env)); + let callbacks = Box::new(HostCallbacks::new(callbacks_ref, &env)); gl_glue::egl::init().and_then(|gl| { api::init( @@ -100,7 +100,7 @@ pub fn Java_com_mozilla_servoview_NativeServo_init( } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_setBatchMode( +pub fn Java_com_mozilla_servoview_JNIServo_setBatchMode( env: JNIEnv, _: JClass, batch: jboolean, @@ -110,7 +110,7 @@ pub fn Java_com_mozilla_servoview_NativeServo_setBatchMode( } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_resize( +pub fn Java_com_mozilla_servoview_JNIServo_resize( env: JNIEnv, _: JClass, width: jint, @@ -121,38 +121,38 @@ pub fn Java_com_mozilla_servoview_NativeServo_resize( } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_performUpdates(env: JNIEnv, _class: JClass) { +pub fn Java_com_mozilla_servoview_JNIServo_performUpdates(env: JNIEnv, _class: JClass) { debug!("performUpdates"); call(env, |s| s.perform_updates()); } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_loadUri(env: JNIEnv, _class: JClass, url: JString) { +pub fn Java_com_mozilla_servoview_JNIServo_loadUri(env: JNIEnv, _class: JClass, url: JString) { debug!("loadUri"); let url: String = env.get_string(url).unwrap().into(); call(env, |s| s.load_uri(&url)); } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_reload(env: JNIEnv, _class: JClass) { +pub fn Java_com_mozilla_servoview_JNIServo_reload(env: JNIEnv, _class: JClass) { debug!("reload"); call(env, |s| s.reload()); } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_goBack(env: JNIEnv, _class: JClass) { +pub fn Java_com_mozilla_servoview_JNIServo_goBack(env: JNIEnv, _class: JClass) { debug!("goBack"); call(env, |s| s.go_back()); } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_goForward(env: JNIEnv, _class: JClass) { +pub fn Java_com_mozilla_servoview_JNIServo_goForward(env: JNIEnv, _class: JClass) { debug!("goForward"); call(env, |s| s.go_forward()); } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_scrollStart( +pub fn Java_com_mozilla_servoview_JNIServo_scrollStart( env: JNIEnv, _: JClass, dx: jint, @@ -165,7 +165,7 @@ pub fn Java_com_mozilla_servoview_NativeServo_scrollStart( } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_scrollEnd( +pub fn Java_com_mozilla_servoview_JNIServo_scrollEnd( env: JNIEnv, _: JClass, dx: jint, @@ -179,7 +179,7 @@ pub fn Java_com_mozilla_servoview_NativeServo_scrollEnd( #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_scroll( +pub fn Java_com_mozilla_servoview_JNIServo_scroll( env: JNIEnv, _: JClass, dx: jint, @@ -192,7 +192,7 @@ pub fn Java_com_mozilla_servoview_NativeServo_scroll( } #[no_mangle] -pub fn Java_com_mozilla_servoview_NativeServo_click(env: JNIEnv, _: JClass, x: jint, y: jint) { +pub fn Java_com_mozilla_servoview_JNIServo_click(env: JNIEnv, _: JClass, x: jint, y: jint) { debug!("click"); call(env, |s| s.click(x as u32, y as u32)); } @@ -203,12 +203,9 @@ pub struct WakeupCallback { } impl WakeupCallback { - pub fn new(jobject: JObject, env: &JNIEnv) -> WakeupCallback { + pub fn new(callback: GlobalRef, env: &JNIEnv) -> WakeupCallback { let jvm = Arc::new(env.get_java_vm().unwrap()); - WakeupCallback { - callback: env.new_global_ref(jobject).unwrap(), - jvm, - } + WakeupCallback { callback, jvm } } } @@ -233,9 +230,9 @@ pub struct ReadFileCallback { } impl ReadFileCallback { - pub fn new(jobject: JObject, env: &JNIEnv) -> ReadFileCallback { + pub fn new(callback: GlobalRef, env: &JNIEnv) -> ReadFileCallback { let jvm = env.get_java_vm().unwrap(); - let callback = Mutex::new(env.new_global_ref(jobject).unwrap()); + let callback = Mutex::new(callback); ReadFileCallback { callback, jvm } } } @@ -260,12 +257,9 @@ impl ReadFileTrait for ReadFileCallback { } impl HostCallbacks { - pub fn new(jobject: JObject, env: &JNIEnv) -> HostCallbacks { + pub fn new(callbacks: GlobalRef, env: &JNIEnv) -> HostCallbacks { let jvm = env.get_java_vm().unwrap(); - HostCallbacks { - callbacks: env.new_global_ref(jobject).unwrap(), - jvm, - } + HostCallbacks { callbacks, jvm } } } @@ -277,6 +271,13 @@ impl HostTrait for HostCallbacks { .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 on_load_started(&self) { debug!("on_load_started"); let env = self.jvm.get_env().unwrap(); diff --git a/python/servo/package_commands.py b/python/servo/package_commands.py index 57fd986fe1a1..f84ba4669e09 100644 --- a/python/servo/package_commands.py +++ b/python/servo/package_commands.py @@ -214,10 +214,12 @@ def package(self, release=False, dev=False, android=None, debug=False, debugger= if flavor is not None: flavor_name = flavor.title() - task_name = "assemble" + flavor_name + build_type + build_mode + variant = ":assemble" + flavor_name + build_type + build_mode + apk_task_name = ":servoapp" + variant + aar_task_name = ":servoview" + variant try: with cd(path.join("support", "android", "apk")): - subprocess.check_call(["./gradlew", "--no-daemon", task_name], env=env) + subprocess.check_call(["./gradlew", "--no-daemon", apk_task_name, aar_task_name], env=env) except subprocess.CalledProcessError as e: print("Packaging Android exited with return value %d" % e.returncode) return e.returncode diff --git a/support/android/apk/build.gradle b/support/android/apk/build.gradle index 8a93ba9b041d..262b81ff55de 100644 --- a/support/android/apk/build.gradle +++ b/support/android/apk/build.gradle @@ -1,4 +1,5 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. +import org.apache.tools.ant.taskdefs.condition.Os + buildscript { repositories { jcenter() @@ -17,6 +18,68 @@ allprojects { } google() } +} + +// Utility methods +String getTargetDir(boolean debug, String arch) { + def basePath = project.rootDir.getParentFile().getParentFile().getParentFile().absolutePath + return basePath + '/target/' + getSubTargetDir(debug, arch) +} + +String getSubTargetDir(boolean debug, String arch) { + return getRustTarget(arch) + '/' + (debug ? 'debug' : 'release') +} + +String getJniLibsPath(boolean debug, String arch) { + return getTargetDir(debug, arch) + '/apk/jniLibs' +} + +static String getRustTarget(String arch) { + switch (arch.toLowerCase()) { + case 'arm' : return 'arm-linux-androideabi' + case 'armv7' : return 'armv7-linux-androideabi' + case 'arm64' : return 'aarch64-linux-android' + case 'x86' : return 'i686-linux-android' + default: throw new GradleException("Invalid target architecture " + arch) + } +} - buildDir = rootDir.absolutePath + "/../../../target/gradle" +static String getNDKAbi(String arch) { + switch (arch.toLowerCase()) { + case 'arm' : return 'armeabi' + case 'armv7' : return 'armeabi-v7a' + case 'arm64' : return 'arm64-v8a' + case 'x86' : return 'x86' + default: throw new GradleException("Invalid target architecture " + arch) + } +} + +String getNdkDir() { + // Read environment variable used in rust build system + String ndkDir = System.getenv('ANDROID_NDK') + if (ndkDir == 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 + def rootDir = project.rootDir + def localProperties = new File(rootDir, "local.properties") + Properties properties = new Properties() + localProperties.withInputStream { instr -> + properties.load(instr) + } + + ndkDir = properties.getProperty('ndk.dir') + } + + def cmd = Os.isFamily(Os.FAMILY_WINDOWS) ? 'ndk-build.cmd' : 'ndk-build' + def ndkbuild = new File(ndkDir + '/' + cmd) + if (!ndkbuild.exists()) { + throw new GradleException("Please set a valid NDK_HOME environment variable" + + "or ndk.dir path in local.properties file"); + } + return ndkbuild.absolutePath } diff --git a/support/android/apk/jni/Android.mk b/support/android/apk/jni/Android.mk index b16da56f57ea..eac2eac79a37 100644 --- a/support/android/apk/jni/Android.mk +++ b/support/android/apk/jni/Android.mk @@ -17,6 +17,6 @@ MY_LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_PATH:= $(SERVO_TARGET_DIR) -LOCAL_MODULE := servo +LOCAL_MODULE := servojni LOCAL_SRC_FILES := libsimpleservo.so include $(PREBUILT_SHARED_LIBRARY) diff --git a/support/android/apk/jni/Application.mk b/support/android/apk/jni/Application.mk index f54d4b98c016..3d71715554b5 100644 --- a/support/android/apk/jni/Application.mk +++ b/support/android/apk/jni/Application.mk @@ -1,4 +1,4 @@ NDK_TOOLCHAIN_VERSION := 4.9 -APP_MODULES := c++_shared servo +APP_MODULES := c++_shared servojni APP_PLATFORM := android-18 APP_STL:= c++_shared diff --git a/support/android/apk/servoapp/build.gradle b/support/android/apk/servoapp/build.gradle index 5568bd9e065b..45b3f57db464 100644 --- a/support/android/apk/servoapp/build.gradle +++ b/support/android/apk/servoapp/build.gradle @@ -1,7 +1,5 @@ apply plugin: 'com.android.application' -import groovy.io.FileType -import org.apache.tools.ant.taskdefs.condition.Os import java.util.regex.Matcher import java.util.regex.Pattern @@ -9,6 +7,8 @@ android { compileSdkVersion 27 buildToolsVersion '27.0.3' + buildDir = rootDir.absolutePath + "/../../../target/gradle/servoapp" + defaultConfig { applicationId "com.mozilla.servo" minSdkVersion 18 @@ -17,24 +17,12 @@ android { versionName "1.0.0" } - compileOptions { - incremental false - } - - splits { - density { - enable false - } - abi { - enable false - } - } - compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } + // Share all of that with servoview flavorDimensions "default" productFlavors { @@ -48,41 +36,25 @@ android { } } + splits { + density { + enable false + } + abi { + enable false + } + } + sourceSets { main { java.srcDirs = ['src/main/java'] assets.srcDirs = ['../../../../resources'] } - armDebug { - jniLibs.srcDirs = [getJniLibsPath(true, 'arm')] - } - armRelease { - jniLibs.srcDirs = [getJniLibsPath(false, 'arm')] - } - armv7Debug { - jniLibs.srcDirs = [getJniLibsPath(true, 'armv7')] - } - armv7Release { - jniLibs.srcDirs = [getJniLibsPath(false, 'armv7')] - } - arm64Debug { - jniLibs.srcDirs = [getJniLibsPath(true, 'arm64')] - } - arm64Release { - jniLibs.srcDirs = [getJniLibsPath(false, 'arm64')] - } - x86Debug { - jniLibs.srcDirs = [getJniLibsPath(true, 'x86')] - } - x86Release { - jniLibs.srcDirs = [getJniLibsPath(false, 'x86')] - } } + buildTypes { - // Default debug and release build types are used as templates debug { - jniDebuggable true } release { @@ -94,51 +66,28 @@ android { // Custom build types armDebug { initWith(debug) - ndk { - abiFilters getNDKAbi('arm') - } } + armRelease { initWith(release) - ndk { - abiFilters getNDKAbi('arm') - } } armv7Debug { initWith(debug) - ndk { - abiFilters getNDKAbi('armv7') - } } armv7Release { initWith(release) - ndk { - abiFilters getNDKAbi('armv7') - } } arm64Debug { initWith(debug) - ndk { - abiFilters getNDKAbi('arm64') - } } arm64Release { initWith(release) - ndk { - abiFilters getNDKAbi('arm64') - } } x86Debug { initWith(debug) - ndk { - abiFilters getNDKAbi('x86') - } } x86Release { initWith(release) - ndk { - abiFilters getNDKAbi('x86') - } } } @@ -149,165 +98,31 @@ android { } } - // Define apk output directory - applicationVariants.all { variant -> - variant.outputs.all { output -> + project.afterEvaluate { + android.applicationVariants.all { variant -> Pattern pattern = Pattern.compile(/^[\w\d]+([A-Z][\w\d]+)(Debug|Release)/) Matcher matcher = pattern.matcher(variant.name) if (!matcher.find()) { - throw "Invalid variant name for output" + throw new GradleException("Invalid variant name for output: " + variant.name) } def arch = matcher.group(1) def debug = variant.name.contains("Debug") - def path = "../../../../../" + getSubTargetDir(debug, arch) + "/servoapp.apk"; - outputFileName = new File(path) - } - } - - // Call our custom NDK Build task using flavor parameters - tasks.all { - compileTask -> - Pattern pattern = Pattern.compile(/^compile[A-Z][\w\d]+([A-Z][\w\d]+)(Debug|Release)/) - Matcher matcher = pattern.matcher(compileTask.name) - if (!matcher.find()) { - return - } - - def taskName = "ndkbuild" + compileTask.name - tasks.create(name: taskName, type: Exec) { - def debug = compileTask.name.contains("Debug") - def arch = matcher.group(1) - commandLine getNdkDir(), - 'APP_BUILD_SCRIPT=../jni/Android.mk', - 'NDK_APPLICATION_MK=../jni/Application.mk', - 'NDK_LIBS_OUT=' + getJniLibsPath(debug, arch), - 'NDK_OUT=' + getTargetDir(debug, arch) + '/apk/obj', - 'NDK_DEBUG=' + (debug ? '1' : '0'), - 'APP_ABI=' + getNDKAbi(arch), - 'SERVO_TARGET_DIR=' + getTargetDir(debug, arch) - } - - compileTask.dependsOn taskName + def finalFolder = getTargetDir(debug, arch) + def finalFile = new File(finalFolder, "servoapp.apk") + variant.outputs.all { output -> + Task copyAndRenameAPKTask = project.task("copyAndRename${variant.name.capitalize()}APK", type: Copy) { + from output.outputFile.getParent() + into finalFolder + include output.outputFileName + rename(output.outputFileName, finalFile.getName()) + } + variant.assemble.finalizedBy(copyAndRenameAPKTask) + } + } } } dependencies { - //Dependency list - def deps = [ - new ServoDependency("blurdroid.jar", "blurdroid") - ] - // Iterate all build types and dependencies - // For each dependency call the proper implementation command and set the correct dependency path - def list = ['arm', 'armv7', 'arm64', 'x86'] - for (arch in list) { - for (debug in [true, false]) { - String basePath = getTargetDir(debug, arch) + "/build" - String cmd = arch + (debug ? "Debug" : "Release") + "Implementation" - - for (ServoDependency dep : deps) { - String path = findDependencyPath(basePath, dep.fileName, dep.folderFilter) - if (path) { - "${cmd}" files(path) - } - } - } - } - googlevrImplementation 'com.google.vr:sdk-base:1.140.0' - googlevrImplementation(name: 'GVRService', ext: 'aar') - oculusvrImplementation(name: 'OVRService', ext: 'aar') implementation 'com.android.support.constraint:constraint-layout:1.1.2' + implementation project(':servoview') } - -// Utility methods -String getTargetDir(boolean debug, String arch) { - def basePath = project.rootDir.getParentFile().getParentFile().getParentFile().absolutePath - return basePath + '/target/' + getSubTargetDir(debug, arch) -} - -String getSubTargetDir(boolean debug, String arch) { - return getRustTarget(arch) + '/' + (debug ? 'debug' : 'release') -} - -String getJniLibsPath(boolean debug, String arch) { - return getTargetDir(debug, arch) + '/apk/jniLibs' -} - -static String getRustTarget(String arch) { - switch (arch.toLowerCase()) { - case 'arm' : return 'arm-linux-androideabi' - case 'armv7' : return 'armv7-linux-androideabi' - case 'arm64' : return 'aarch64-linux-android' - case 'x86' : return 'i686-linux-android' - default: throw new GradleException("Invalid target architecture " + arch) - } -} - -static String getNDKAbi(String arch) { - switch (arch.toLowerCase()) { - case 'arm' : return 'armeabi' - case 'armv7' : return 'armeabi-v7a' - case 'arm64' : return 'arm64-v8a' - case 'x86' : return 'x86' - default: throw new GradleException("Invalid target architecture " + arch) - } -} - -String getNdkDir() { - // Read environment variable used in rust build system - String ndkDir = System.getenv('ANDROID_NDK') - if (ndkDir == null) { - ndkDir = System.getenv('ANDROID_NDK_HOME') - } - if (ndkDir == null) { - // Fallback to ndkDir in local.properties - def rootDir = project.rootDir - def localProperties = new File(rootDir, "local.properties") - Properties properties = new Properties() - localProperties.withInputStream { instr -> - properties.load(instr) - } - - ndkDir = properties.getProperty('ndk.dir') - } - - def cmd = Os.isFamily(Os.FAMILY_WINDOWS) ? 'ndk-build.cmd' : 'ndk-build' - def ndkbuild = new File(ndkDir + '/' + cmd) - if (!ndkbuild.exists()) { - throw new GradleException("Please set a valid NDK_HOME environment variable" + - "or ndk.dir path in local.properties file"); - } - return ndkbuild.absolutePath -} - -// folderFilter can be used to improve search performance -static String findDependencyPath(String basePath, String filename, String folderFilter) { - File path = new File(basePath); - if (!path.exists()) { - return '' - } - - if (folderFilter) { - path.eachDir { - if (it.name.contains(folderFilter)) { - path = new File(it.absolutePath) - } - } - } - def result = '' - path.eachFileRecurse(FileType.FILES) { - if(it.name.equals(filename)) { - result = it.absolutePath - } - } - - return result -} - -class ServoDependency { - ServoDependency(String fileName, String folderFilter = null) { - this.fileName = fileName; - this.folderFilter = folderFilter; - } - public String fileName; - public String folderFilter; -} \ No newline at end of file diff --git a/support/android/apk/servoapp/src/main/java/com/mozilla/servo/MainActivity.java b/support/android/apk/servoapp/src/main/java/com/mozilla/servo/MainActivity.java index 266bf0ad4df3..1d042151a3a2 100644 --- a/support/android/apk/servoapp/src/main/java/com/mozilla/servo/MainActivity.java +++ b/support/android/apk/servoapp/src/main/java/com/mozilla/servo/MainActivity.java @@ -3,17 +3,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - package com.mozilla.servo; import android.app.Activity; import android.content.Context; -import android.content.Intent; import android.net.Uri; import android.os.Bundle; import android.system.ErrnoException; import android.system.Os; -import android.util.Log; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; @@ -23,10 +20,11 @@ import android.widget.ProgressBar; import com.mozilla.servoview.ServoView; +import com.mozilla.servoview.Servo; import java.io.File; -public class MainActivity extends Activity implements ServoView.Client { +public class MainActivity extends Activity implements Servo.Client { private static final String LOGTAG = "MainActivity"; @@ -44,18 +42,18 @@ protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); - mServoView = (ServoView)findViewById(R.id.servoview); - mBackButton = (Button)findViewById(R.id.backbutton); - mFwdButton = (Button)findViewById(R.id.forwardbutton); - mReloadButton = (Button)findViewById(R.id.reloadbutton); - mStopButton = (Button)findViewById(R.id.stopbutton); - mUrlField = (EditText)findViewById(R.id.urlfield); - mProgressBar = (ProgressBar)findViewById(R.id.progressbar); + mServoView = findViewById(R.id.servoview); + mBackButton = findViewById(R.id.backbutton); + mFwdButton = findViewById(R.id.forwardbutton); + mReloadButton = findViewById(R.id.reloadbutton); + mStopButton = findViewById(R.id.stopbutton); + mUrlField = findViewById(R.id.urlfield); + mProgressBar = findViewById(R.id.progressbar); - mServoView.setClient(this); mBackButton.setEnabled(false); mFwdButton.setEnabled(false); + mServoView.setClient(this); mServoView.requestFocus(); if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) { @@ -69,9 +67,7 @@ protected void onCreate(Bundle savedInstanceState) { } String args = getIntent().getStringExtra("servoargs"); - if (args != null) { - mServoView.setServoArgs(args); - } + mServoView.setServoArgs(args); setupUrlField(); } @@ -108,18 +104,16 @@ private void loadUrlFromField() { mServoView.loadUri(Uri.parse(uri)); } + // From activity_main.xml: public void onReloadClicked(View v) { mServoView.reload(); } - public void onBackClicked(View v) { mServoView.goBack(); } - public void onForwardClicked(View v) { mServoView.goForward(); } - public void onStopClicked(View v) { mServoView.stop(); } diff --git a/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoGLRenderer.java b/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoGLRenderer.java deleted file mode 100644 index dc06c14e512a..000000000000 --- a/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoGLRenderer.java +++ /dev/null @@ -1,32 +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 http://mozilla.org/MPL/2.0/. */ - -package com.mozilla.servoview; - -import android.opengl.GLES31; -import android.opengl.GLSurfaceView; -import javax.microedition.khronos.egl.EGLConfig; -import javax.microedition.khronos.opengles.GL10; - -public class ServoGLRenderer implements GLSurfaceView.Renderer { - - private final ServoView mView; - - ServoGLRenderer(ServoView view) { - mView = view; - } - - public void onSurfaceCreated(GL10 unused, EGLConfig config) { - mView.onGLReady(); - } - - public void onDrawFrame(GL10 unused) { - } - - public void onSurfaceChanged(GL10 unused, int width, int height) { - GLES31.glViewport(0, 0, width, height); - mView.onSurfaceResized(width, height); - } -} diff --git a/support/android/apk/servoview/build.gradle b/support/android/apk/servoview/build.gradle new file mode 100644 index 000000000000..17ec22830680 --- /dev/null +++ b/support/android/apk/servoview/build.gradle @@ -0,0 +1,258 @@ +apply plugin: 'com.android.library' + +import groovy.io.FileType +import java.util.regex.Matcher +import java.util.regex.Pattern + +android { + compileSdkVersion 27 + buildToolsVersion '27.0.3' + + buildDir = rootDir.absolutePath + "/../../../target/gradle/servoview" + + defaultConfig { + minSdkVersion 18 + targetSdkVersion 27 + versionCode 1 + versionName "1.0" + } + + compileOptions { + incremental false + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + flavorDimensions "default" + + productFlavors { + main { + } + googlevr { + minSdkVersion 21 + } + oculusvr { + minSdkVersion 21 + } + } + + splits { + density { + enable false + } + abi { + enable false + } + } + + + buildTypes { + // Default debug and release build types are used as templates + debug { + jniDebuggable true + } + + release { + signingConfig signingConfigs.debug // Change this to sign with a production key + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + + // Custom build types + armDebug { + initWith(debug) + ndk { + abiFilters getNDKAbi('arm') + } + } + armRelease { + initWith(release) + ndk { + abiFilters getNDKAbi('arm') + } + } + armv7Debug { + initWith(debug) + ndk { + abiFilters getNDKAbi('armv7') + } + } + armv7Release { + initWith(release) + ndk { + abiFilters getNDKAbi('armv7') + } + } + arm64Debug { + initWith(debug) + ndk { + abiFilters getNDKAbi('arm64') + } + } + arm64Release { + initWith(release) + ndk { + abiFilters getNDKAbi('arm64') + } + } + x86Debug { + initWith(debug) + ndk { + abiFilters getNDKAbi('x86') + } + } + x86Release { + initWith(release) + ndk { + abiFilters getNDKAbi('x86') + } + } + } + + sourceSets { + armDebug { + jniLibs.srcDirs = [getJniLibsPath(true, 'arm')] + } + armRelease { + jniLibs.srcDirs = [getJniLibsPath(false, 'arm')] + } + armv7Debug { + jniLibs.srcDirs = [getJniLibsPath(true, 'armv7')] + } + armv7Release { + jniLibs.srcDirs = [getJniLibsPath(false, 'armv7')] + } + arm64Debug { + jniLibs.srcDirs = [getJniLibsPath(true, 'arm64')] + } + arm64Release { + jniLibs.srcDirs = [getJniLibsPath(false, 'arm64')] + } + x86Debug { + jniLibs.srcDirs = [getJniLibsPath(true, 'x86')] + } + x86Release { + jniLibs.srcDirs = [getJniLibsPath(false, 'x86')] + } + } + + // Ignore default 'debug' and 'release' build types + variantFilter { variant -> + if(variant.buildType.name.equals('release') || variant.buildType.name.equals('debug')) { + variant.setIgnore(true); + } + } + + + // Call our custom NDK Build task using flavor parameters + tasks.all { + compileTask -> + Pattern pattern = Pattern.compile(/^compile[A-Z][\w\d]+([A-Z][\w\d]+)(Debug|Release)/) + Matcher matcher = pattern.matcher(compileTask.name) + if (!matcher.find()) { + return + } + + def taskName = "ndkbuild" + compileTask.name + tasks.create(name: taskName, type: Exec) { + def debug = compileTask.name.contains("Debug") + def arch = matcher.group(1) + commandLine getNdkDir(), + 'APP_BUILD_SCRIPT=../jni/Android.mk', + 'NDK_APPLICATION_MK=../jni/Application.mk', + 'NDK_LIBS_OUT=' + getJniLibsPath(debug, arch), + 'NDK_OUT=' + getTargetDir(debug, arch) + '/apk/obj', + 'NDK_DEBUG=' + (debug ? '1' : '0'), + 'APP_ABI=' + getNDKAbi(arch), + 'SERVO_TARGET_DIR=' + getTargetDir(debug, arch) + } + + compileTask.dependsOn taskName + } + + + project.afterEvaluate { + android.libraryVariants.all { variant -> + Pattern pattern = Pattern.compile(/^[\w\d]+([A-Z][\w\d]+)(Debug|Release)/) + Matcher matcher = pattern.matcher(variant.name) + if (!matcher.find()) { + throw new GradleException("Invalid variant name for output: " + variant.name) + } + def arch = matcher.group(1) + def debug = variant.name.contains("Debug") + def finalFolder = getTargetDir(debug, arch) + def finalFile = new File(finalFolder, "servoview.aar") + variant.outputs.all { output -> + Task copyAndRenameAARTask = project.task("copyAndRename${variant.name.capitalize()}AAR", type: Copy) { + from output.outputFile.getParent() + into finalFolder + include output.outputFileName + rename(output.outputFileName, finalFile.getName()) + } + variant.assemble.finalizedBy(copyAndRenameAARTask) + } + } + } +} + +dependencies { + + //Dependency list + def deps = [ + new ServoDependency("blurdroid.jar", "blurdroid") + ] + // Iterate all build types and dependencies + // For each dependency call the proper implementation command and set the correct dependency path + def list = ['arm', 'armv7', 'arm64', 'x86'] + for (arch in list) { + for (debug in [true, false]) { + String basePath = getTargetDir(debug, arch) + "/build" + String cmd = arch + (debug ? "Debug" : "Release") + "Implementation" + + for (ServoDependency dep : deps) { + String path = findDependencyPath(basePath, dep.fileName, dep.folderFilter) + if (path) { + "${cmd}" files(path) + } + } + } + } + + googlevrImplementation 'com.google.vr:sdk-base:1.140.0' + googlevrImplementation(name: 'GVRService', ext: 'aar') + oculusvrImplementation(name: 'OVRService', ext: 'aar') + implementation 'com.android.support.constraint:constraint-layout:1.1.2' +} + +// folderFilter can be used to improve search performance +static String findDependencyPath(String basePath, String filename, String folderFilter) { + File path = new File(basePath); + if (!path.exists()) { + return '' + } + + if (folderFilter) { + path.eachDir { + if (it.name.contains(folderFilter)) { + path = new File(it.absolutePath) + } + } + } + def result = '' + path.eachFileRecurse(FileType.FILES) { + if(it.name.equals(filename)) { + result = it.absolutePath + } + } + + return result +} + +class ServoDependency { + ServoDependency(String fileName, String folderFilter = null) { + this.fileName = fileName; + this.folderFilter = folderFilter; + } + public String fileName; + public String folderFilter; +} diff --git a/support/android/apk/servoview/proguard-rules.pro b/support/android/apk/servoview/proguard-rules.pro new file mode 100644 index 000000000000..f1b424510da5 --- /dev/null +++ b/support/android/apk/servoview/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/support/android/apk/servoapp/src/googlevr/AndroidManifest.xml b/support/android/apk/servoview/src/googlevr/AndroidManifest.xml similarity index 95% rename from support/android/apk/servoapp/src/googlevr/AndroidManifest.xml rename to support/android/apk/servoview/src/googlevr/AndroidManifest.xml index f0bcffd707fd..c656c860e014 100644 --- a/support/android/apk/servoapp/src/googlevr/AndroidManifest.xml +++ b/support/android/apk/servoview/src/googlevr/AndroidManifest.xml @@ -1,6 +1,6 @@ - + diff --git a/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/NativeServo.java b/support/android/apk/servoview/src/main/java/com/mozilla/servoview/JNIServo.java similarity index 82% rename from support/android/apk/servoapp/src/main/java/com/mozilla/servoview/NativeServo.java rename to support/android/apk/servoview/src/main/java/com/mozilla/servoview/JNIServo.java index 51f27b24d6f7..13c62fe93729 100644 --- a/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/NativeServo.java +++ b/support/android/apk/servoview/src/main/java/com/mozilla/servoview/JNIServo.java @@ -10,48 +10,65 @@ /** * Maps /ports/libsimpleservo API */ -public class NativeServo { +@SuppressWarnings("JniMissingFunction") +public class JNIServo { + JNIServo() { + System.loadLibrary("c++_shared"); + System.loadLibrary("simpleservo"); + } + public native String version(); + public native void init(Activity activity, String args, String url, - WakeupCallback wakeup, - ReadFileCallback readfile, - ServoCallbacks callbacks, + Callbacks callbacks, int width, int height, boolean log); + public native void setBatchMode(boolean mode); + public native void performUpdates(); + public native void resize(int width, int height); + public native void reload(); + public native void stop(); + public native void goBack(); + public native void goForward(); + public native void loadUri(String uri); + public native void scrollStart(int dx, int dy, int x, int y); + public native void scroll(int dx, int dy, int x, int y); - public native void scrollEnd(int dx, int dy, int x, int y); - public native void click(int x, int y); - NativeServo() { - System.loadLibrary("c++_shared"); - System.loadLibrary("simpleservo"); - } + public native void scrollEnd(int dx, int dy, int x, int y); - public interface ReadFileCallback { - byte[] readfile(String file); - } + public native void click(int x, int y); - public interface WakeupCallback { + public interface Callbacks { void wakeup(); - } - public interface ServoCallbacks { void flush(); + + void makeCurrent(); + + void onAnimatingChanged(boolean animating); + void onLoadStarted(); + void onLoadEnded(); + void onTitleChanged(String title); + void onUrlChanged(String url); + void onHistoryChanged(boolean canGoBack, boolean canGoForward); - void onAnimatingChanged(boolean animating); + + byte[] readfile(String file); } } + diff --git a/support/android/apk/servoview/src/main/java/com/mozilla/servoview/Servo.java b/support/android/apk/servoview/src/main/java/com/mozilla/servoview/Servo.java new file mode 100644 index 000000000000..3f07dbb50ac8 --- /dev/null +++ b/support/android/apk/servoview/src/main/java/com/mozilla/servoview/Servo.java @@ -0,0 +1,173 @@ +/* -*- 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 http://mozilla.org/MPL/2.0/. */ + +package com.mozilla.servoview; + +import android.app.Activity; +import android.content.res.AssetManager; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; + +public class Servo { + private static final String LOGTAG = "Servo"; + private AssetManager mAssetMgr; + private JNIServo mJNI = new JNIServo(); + private RunCallback mRunCallback; + + public Servo( + RunCallback runCallback, + GfxCallbacks gfxcb, + Client client, + Activity activity, + String args, String url, + int width, int height, boolean log) { + + mRunCallback = runCallback; + + mAssetMgr = activity.getResources().getAssets(); + + Callbacks cbs = new Callbacks(client, gfxcb); + + mRunCallback.inGLThread(() -> { + mJNI.init(activity, args, url, cbs, width, height, log); + }); + } + + public String version() { + return mJNI.version(); + } + + public void setBatchMode(boolean mode) { + mRunCallback.inGLThread(() -> mJNI.setBatchMode(mode)); + } + + public void resize(int width, int height) { + mRunCallback.inGLThread(() -> mJNI.resize(width, height)); + } + + public void reload() { + mRunCallback.inGLThread(() -> mJNI.reload()); + } + + public void stop() { + mRunCallback.inGLThread(() -> mJNI.stop()); + } + + public void goBack() { + mRunCallback.inGLThread(() -> mJNI.goBack()); + } + + public void goForward() { + mRunCallback.inGLThread(() -> mJNI.goForward()); + } + + public void loadUri(String uri) { + mRunCallback.inGLThread(() -> mJNI.loadUri(uri)); + } + + public void scrollStart(int dx, int dy, int x, int y) { + mRunCallback.inGLThread(() -> mJNI.scrollStart(dx, dy, x, y)); + } + + public void scroll(int dx, int dy, int x, int y) { + mRunCallback.inGLThread(() -> mJNI.scroll(dx, dy, x, y)); + } + + public void scrollEnd(int dx, int dy, int x, int y) { + mRunCallback.inGLThread(() -> mJNI.scrollEnd(dx, dy, x, y)); + } + + public void click(int x, int y) { + mRunCallback.inGLThread(() -> mJNI.click(x, y)); + } + + public interface Client { + void onLoadStarted(); + + void onLoadEnded(); + + void onTitleChanged(String title); + + void onUrlChanged(String url); + + void onHistoryChanged(boolean canGoBack, boolean canGoForward); + } + + public interface RunCallback { + void inGLThread(Runnable f); + + void inUIThread(Runnable f); + } + + public interface GfxCallbacks { + void flushGLBuffers(); + + void animationStateChanged(boolean animating); + + void makeCurrent(); + } + + private class Callbacks implements JNIServo.Callbacks, Client { + + private final GfxCallbacks mGfxCb; + Client mClient; + + Callbacks(Client client, GfxCallbacks gfxcb) { + mClient = client; + mGfxCb = gfxcb; + } + + public void wakeup() { + mRunCallback.inGLThread(() -> mJNI.performUpdates()); + } + + public void flush() { + mRunCallback.inUIThread(() -> mGfxCb.flushGLBuffers()); + } + + public void makeCurrent() { + mRunCallback.inUIThread(() -> mGfxCb.makeCurrent()); + } + + public void onAnimatingChanged(boolean animating) { + mRunCallback.inUIThread(() -> mGfxCb.animationStateChanged(animating)); + } + + public void onLoadStarted() { + mRunCallback.inUIThread(() -> mClient.onLoadStarted()); + } + + public void onLoadEnded() { + mRunCallback.inUIThread(() -> mClient.onLoadEnded()); + } + + public void onTitleChanged(String title) { + mRunCallback.inUIThread(() -> mClient.onTitleChanged(title)); + } + + public void onUrlChanged(String url) { + mRunCallback.inUIThread(() -> mClient.onUrlChanged(url)); + } + + public void onHistoryChanged(boolean canGoBack, boolean canGoForward) { + mRunCallback.inUIThread(() -> mClient.onHistoryChanged(canGoBack, canGoForward)); + } + + public byte[] readfile(String file) { + try { + InputStream stream = mAssetMgr.open(file); + byte[] bytes = new byte[stream.available()]; + stream.read(bytes); + stream.close(); + return bytes; + } catch (IOException e) { + Log.e(LOGTAG, e.getMessage()); + return null; + } + } + } +} diff --git a/support/android/apk/servoview/src/main/java/com/mozilla/servoview/ServoSurface.java b/support/android/apk/servoview/src/main/java/com/mozilla/servoview/ServoSurface.java new file mode 100644 index 000000000000..13547d43a939 --- /dev/null +++ b/support/android/apk/servoview/src/main/java/com/mozilla/servoview/ServoSurface.java @@ -0,0 +1,186 @@ +package com.mozilla.servoview; + +import android.annotation.SuppressLint; +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.os.Message; +import android.util.Log; + +import com.mozilla.servoview.Servo.Client; +import com.mozilla.servoview.Servo.GfxCallbacks; +import com.mozilla.servoview.Servo.RunCallback; + +import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION; +import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT; + +public class ServoSurface { + private final GLThread mGLThread; + private final Handler mMainLooperHandler; + private Handler mGLLooperHandler; + private Surface mASurface; + private int mWidth; + private int mHeight; + private Servo mServo; + private Client mClient = null; + private String mServoArgs = ""; + private Uri mInitialUri = null; + private Activity mActivity; + + public ServoSurface(Surface surface, int width, int height) { + mWidth = width; + mHeight = height; + mASurface = surface; + mMainLooperHandler = new Handler(Looper.getMainLooper()); + mGLThread = new GLThread(); + } + + public void setClient(Client client) { + mClient = client; + } + + public void setServoArgs(String args) { + mServoArgs = args != null ? args : ""; + } + + public void setActivity(Activity activity) { + mActivity = activity; + } + + public void runLoop() { + mGLThread.start(); + } + + public void reload() { + mServo.reload(); + } + + public void goBack() { + mServo.goBack(); + } + + public void goForward() { + mServo.goForward(); + } + + public void stop() { + mServo.stop(); + } + + public void onSurfaceResized(int width, int height) { + mServo.resize(width, height); + } + + public void loadUri(Uri uri) { + if (mServo != null) { + mServo.loadUri(uri.toString()); + } else { + mInitialUri = uri; + } + } + + static class Surface implements GfxCallbacks { + private static final String LOGTAG = "ServoSurface"; + + private EGLConfig[] mEGLConfigs; + private EGLDisplay mEglDisplay; + private EGLContext mEglContext; + private EGLSurface mEglSurface; + + Surface(Surface surface) { + mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY); + int[] version = new int[2]; + if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) { + throw new RuntimeException("Error: eglInitialize() Failed " + GLUtils.getEGLErrorString(EGL14.eglGetError())); + } + 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, 0, + EGL14.EGL_STENCIL_SIZE, 0, + EGL14.EGL_NONE + }; + if ((!EGL14.eglChooseConfig(mEglDisplay, configSpec, 0, mEGLConfigs, 0, 1, configsCount, 0)) || (configsCount[0] == 0)) { + throw new IllegalArgumentException("Error: eglChooseConfig() Failed " + GLUtils.getEGLErrorString(EGL14.eglGetError())); + } + 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) { + throw new RuntimeException("Error: eglCreateContext() Failed " + GLUtils.getEGLErrorString(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; + } + throw new RuntimeException("Error: createWindowSurface() Failed " + GLUtils.getEGLErrorString(glError)); + } + + flushGLBuffers(); + } + + + public void makeCurrent() { + if (!EGL14.eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface, mEglContext)) { + throw new RuntimeException("Error: eglMakeCurrent() Failed " + GLUtils.getEGLErrorString(EGL14.eglGetError())); + } + } + + public void flushGLBuffers() { + EGL14.eglSwapBuffers(mEglDisplay, mEglSurface); + } + + public void animationStateChanged(boolean animating) { + // FIXME + } + + } + + class GLThread extends Thread implements RunCallback { + + public void inGLThread(Runnable r) { + mGLLooperHandler.post(r); + } + + public void inUIThread(Runnable r) { + mMainLooperHandler.post(r); + } + + // FIXME: HandlerLeak + @SuppressLint("HandlerLeak") + public void run() { + Looper.prepare(); + + Surface surface = new Surface(mASurface); + + final boolean showLogs = true; + String uri = mInitialUri == null ? null : mInitialUri.toString(); + mServo = new Servo(this, surface, mClient, mActivity, mServoArgs, uri, mWidth, mHeight, showLogs); + + mGLLooperHandler = new Handler() { + public void handleMessage(Message msg) { + } + }; + + Looper.loop(); + } + } +} diff --git a/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoView.java b/support/android/apk/servoview/src/main/java/com/mozilla/servoview/ServoView.java similarity index 58% rename from support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoView.java rename to support/android/apk/servoview/src/main/java/com/mozilla/servoview/ServoView.java index 2cf2c257a78c..9999568a2a39 100644 --- a/support/android/apk/servoapp/src/main/java/com/mozilla/servoview/ServoView.java +++ b/support/android/apk/servoview/src/main/java/com/mozilla/servoview/ServoView.java @@ -6,30 +6,45 @@ package com.mozilla.servoview; import android.app.Activity; -import android.os.Build; import android.content.Context; -import android.content.res.AssetManager; import android.net.Uri; +import android.opengl.GLES31; import android.opengl.GLSurfaceView; import android.util.AttributeSet; -import android.util.Log; import android.view.Choreographer; import android.view.GestureDetector; import android.view.MotionEvent; import android.widget.OverScroller; -import java.io.IOException; -import java.io.InputStream; -public class ServoView extends GLSurfaceView implements GestureDetector.OnGestureListener, Choreographer.FrameCallback { +import com.mozilla.servoview.Servo.Client; +import com.mozilla.servoview.Servo.GfxCallbacks; +import com.mozilla.servoview.Servo.RunCallback; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +public class ServoView extends GLSurfaceView + implements + GestureDetector.OnGestureListener, + Choreographer.FrameCallback, + GfxCallbacks, + RunCallback { private static final String LOGTAG = "ServoView"; private Activity mActivity; - private NativeServo mServo; + private Servo mServo; private Client mClient = null; private Uri mInitialUri = null; private boolean mAnimating; private String mServoArgs = ""; + private GestureDetector mGestureDetector; + private OverScroller mScroller; + private int mLastX = 0; + private int mCurX = 0; + private int mLastY = 0; + private int mCurY = 0; + private boolean mFlinging; public ServoView(Context context, AttributeSet attrs) { super(context, attrs); @@ -41,149 +56,87 @@ public ServoView(Context context, AttributeSet attrs) { setEGLConfigChooser(8, 8, 8, 8, 24, 0); ServoGLRenderer mRenderer = new ServoGLRenderer(this); setRenderer(mRenderer); - mServo = new NativeServo(); setRenderMode(GLSurfaceView.RENDERMODE_WHEN_DIRTY); initGestures(context); } public void setServoArgs(String args) { - mServoArgs = args; + mServoArgs = args != null ? args : ""; } public void reload() { - queueEvent(() -> mServo.reload()); + mServo.reload(); } public void goBack() { - queueEvent(() -> mServo.goBack()); + mServo.goBack(); } public void goForward() { - queueEvent(() -> mServo.goForward()); + mServo.goForward(); } public void stop() { - queueEvent(() -> mServo.stop()); + mServo.stop(); } public void onSurfaceResized(int width, int height) { - queueEvent(() -> mServo.resize(width, height)); + if (mServo != null) { + mServo.resize(width, height); + } } public void loadUri(Uri uri) { if (mServo != null) { - queueEvent(() -> mServo.loadUri(uri.toString())); + mServo.loadUri(uri.toString()); } else { mInitialUri = uri; } } - class WakeupCallback implements NativeServo.WakeupCallback { - public void wakeup() { - queueEvent(() -> mServo.performUpdates()); - }; - } - - class ReadFileCallback implements NativeServo.ReadFileCallback { - public byte[] readfile(String file) { - try { - AssetManager assetMgr = getContext().getResources().getAssets(); - InputStream stream = assetMgr.open(file); - byte[] bytes = new byte[stream.available()]; - stream.read(bytes); - stream.close(); - return bytes; - } catch (IOException e) { - Log.e(LOGTAG, e.getMessage()); - return null; - } - } + public void flushGLBuffers() { + requestRender(); } - class ServoCallbacks implements NativeServo.ServoCallbacks { - public void flush() { - requestRender(); - } - - public void onLoadStarted() { - if (mClient != null) { - post(() -> mClient.onLoadStarted()); - } - } - - public void onLoadEnded() { - if (mClient != null) { - post(() -> mClient.onLoadEnded()); - } - } + // Scroll and click - public void onTitleChanged(final String title) { - if (mClient != null) { - post(() -> mClient.onTitleChanged(title)); - } + public void animationStateChanged(boolean animating) { + if (!mAnimating && animating) { + post(() -> Choreographer.getInstance().postFrameCallback(ServoView.this)); } + mAnimating = animating; + } - public void onUrlChanged(final String url) { - if (mClient != null) { - post(() -> mClient.onUrlChanged(url)); - } - } + public void makeCurrent() { + } - public void onHistoryChanged(final boolean canGoBack, final boolean canGoForward) { - if (mClient != null) { - post(() -> mClient.onHistoryChanged(canGoBack, canGoForward)); - } - } + public void inGLThread(Runnable f) { + queueEvent(f); + } - public void onAnimatingChanged(final boolean animating) { - if (!mAnimating && animating) { - post(() -> Choreographer.getInstance().postFrameCallback(ServoView.this)); - } - mAnimating = animating; - } + public void inUIThread(Runnable f) { + post(f); } public void onGLReady() { - final WakeupCallback c1 = new WakeupCallback(); - final ReadFileCallback c2 = new ReadFileCallback(); - final ServoCallbacks c3 = new ServoCallbacks(); final boolean showLogs = true; int width = getWidth(); int height = getHeight(); - queueEvent(() -> { + inGLThread(() -> { String uri = mInitialUri == null ? null : mInitialUri.toString(); - mServo.init(mActivity, mServoArgs, uri, c1, c2, c3, width, height, showLogs); + mServo = new Servo(this, this, mClient, mActivity, mServoArgs, uri, width, height, showLogs); }); } - public interface Client { - void onLoadStarted(); - void onLoadEnded(); - void onTitleChanged(String title); - void onUrlChanged(String url); - void onHistoryChanged(boolean canGoBack, boolean canGoForward); - } - public void setClient(Client client) { mClient = client; } - // Scroll and click - - private GestureDetector mGestureDetector; - private OverScroller mScroller; - private int mLastX = 0; - private int mCurX = 0; - private int mLastY = 0; - private int mCurY = 0; - private boolean mFlinging; - private void initGestures(Context context) { mGestureDetector = new GestureDetector(context, this); mScroller = new OverScroller(context); } - @Override public void doFrame(long frameTimeNanos) { if (mScroller.isFinished() && mFlinging) { @@ -230,7 +183,7 @@ public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float ve mLastX = mCurX; mCurY = velocityY < 0 ? mPageHeight : 0; mLastY = mCurY; - mScroller.fling(mCurX, mCurY, (int)velocityX, (int)velocityY, 0, mPageWidth, 0, mPageHeight); + mScroller.fling(mCurX, mCurY, (int) velocityX, (int) velocityY, 0, mPageWidth, 0, mPageHeight); return true; } @@ -243,19 +196,19 @@ public boolean onTouchEvent(final MotionEvent e) { mGestureDetector.onTouchEvent(e); int action = e.getActionMasked(); - switch(action) { + switch (action) { case (MotionEvent.ACTION_DOWN): - mCurX = (int)e.getX(); + mCurX = (int) e.getX(); mLastX = mCurX; - mCurY = (int)e.getY(); + mCurY = (int) e.getY(); mLastY = mCurY; mScroller.forceFinished(true); queueEvent(() -> mServo.scrollStart(0, 0, mCurX, mCurY)); Choreographer.getInstance().postFrameCallback(this); return true; case (MotionEvent.ACTION_MOVE): - mCurX = (int)e.getX(); - mCurY = (int)e.getY(); + mCurX = (int) e.getX(); + mCurY = (int) e.getY(); return true; case (MotionEvent.ACTION_UP): case (MotionEvent.ACTION_CANCEL): @@ -270,12 +223,38 @@ public boolean onTouchEvent(final MotionEvent e) { } public boolean onSingleTapUp(MotionEvent e) { - queueEvent(() -> mServo.click((int)e.getX(), (int)e.getY())); + queueEvent(() -> mServo.click((int) e.getX(), (int) e.getY())); return false; } - public void onLongPress(MotionEvent e) { } - public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { return true; } - public void onShowPress(MotionEvent e) { } + public void onLongPress(MotionEvent e) { + } + + public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { + return true; + } + + public void onShowPress(MotionEvent e) { + } + + static class ServoGLRenderer implements Renderer { + private final ServoView mView; + + ServoGLRenderer(ServoView view) { + mView = view; + } + + public void onSurfaceCreated(GL10 unused, EGLConfig config) { + mView.onGLReady(); + } + + public void onDrawFrame(GL10 unused) { + } + + public void onSurfaceChanged(GL10 unused, int width, int height) { + GLES31.glViewport(0, 0, width, height); + mView.onSurfaceResized(width, height); + } + } } diff --git a/support/android/apk/servoview/src/main/res/values/strings.xml b/support/android/apk/servoview/src/main/res/values/strings.xml new file mode 100644 index 000000000000..3f959010c8af --- /dev/null +++ b/support/android/apk/servoview/src/main/res/values/strings.xml @@ -0,0 +1,3 @@ + + servoview + diff --git a/support/android/apk/servoapp/src/oculusvr/AndroidManifest.xml b/support/android/apk/servoview/src/oculusvr/AndroidManifest.xml similarity index 91% rename from support/android/apk/servoapp/src/oculusvr/AndroidManifest.xml rename to support/android/apk/servoview/src/oculusvr/AndroidManifest.xml index 9b8e8817b3d2..c5b9ffdfc7d9 100644 --- a/support/android/apk/servoapp/src/oculusvr/AndroidManifest.xml +++ b/support/android/apk/servoview/src/oculusvr/AndroidManifest.xml @@ -1,6 +1,6 @@ -> +> diff --git a/support/android/apk/settings.gradle b/support/android/apk/settings.gradle index 9c4a80cb7921..196244ebecae 100644 --- a/support/android/apk/settings.gradle +++ b/support/android/apk/settings.gradle @@ -1 +1 @@ -include ':servoapp' +include ':servoapp', ':servoview'