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,18 +1,23 @@
apply plugin: 'com.android.library'
plugins {
id 'com.android.library'
}
import groovy.io.FileType
import java.util.regex.Matcher
import java.util.regex.Pattern
android {
compileSdkVersion 27
buildToolsVersion '27.0.3'
compileSdk 33
buildToolsVersion "33.0.2"
namespace 'org.mozilla.servoview'
buildDir = rootDir.absolutePath + "/../../../target/android/gradle/servoview"
ndkPath = getNdkDir()
defaultConfig {
minSdkVersion 18
targetSdkVersion 27
minSdk 30
targetSdk 30
versionCode 1
versionName "1.0"
}
@ -26,24 +31,18 @@ android {
flavorDimensions "default"
productFlavors {
main {
}
googlevr {
minSdkVersion 21
}
oculusvr {
minSdkVersion 21
basic {
}
}
splits {
density {
enable false
}
abi {
enable false
}
}
splits {
density {
enable false
}
abi {
enable false
}
}
buildTypes {
@ -59,65 +58,29 @@ 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')
}
}
}
sourceSets {
main {
}
armDebug {
jniLibs.srcDirs = [getJniLibsPath(true, 'arm')]
}
armRelease {
jniLibs.srcDirs = [getJniLibsPath(false, 'arm')]
}
armv7Debug {
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 {
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)
if (!matcher.find()) {
return
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)
commandLine getNdkDir() + "/ndk-build",
'APP_BUILD_SCRIPT=../jni/Android.mk',
'NDK_APPLICATION_MK=../jni/Application.mk',
'NDK_LIBS_OUT=' + getJniLibsPath(debug, arch),
'NDK_DEBUG=' + (debug ? '1' : '0'),
'APP_ABI=' + getNDKAbi(arch),
'NDK_LOG=1',
'SERVO_TARGET_DIR=' + getNativeTargetDir(debug, arch)
}
compileTask.dependsOn taskName
}
project.afterEvaluate {
android.libraryVariants.all { variant ->
Pattern pattern = Pattern.compile(/^[\w\d]+([A-Z][\w\d]+)(Debug|Release)/)
@ -205,7 +183,7 @@ dependencies {
]
// 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']
def list = ['armv7', 'arm64', 'x86']
for (arch in list) {
for (debug in [true, false]) {
String basePath = getTargetDir(debug, arch) + "/build"
@ -220,10 +198,9 @@ dependencies {
}
}
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 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:1.9.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
}
// folderFilter can be used to improve search performance
@ -258,42 +235,3 @@ class ServoDependency {
public String fileName;
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"
package="org.mozilla.servoview" />
<manifest xmlns:android="http://schemas.android.com/apk/res/android" />

View file

@ -6,7 +6,7 @@
package org.mozilla.servoview;
import android.app.Activity;
import android.view.Surface;
/**
* Maps /ports/libsimpleservo API
*/
@ -14,13 +14,12 @@ import android.app.Activity;
public class JNIServo {
JNIServo() {
System.loadLibrary("c++_shared");
System.loadLibrary("gstreamer_android");
System.loadLibrary("simpleservo");
}
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();
@ -66,6 +65,9 @@ public class JNIServo {
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 static class ServoOptions {

View file

@ -8,6 +8,7 @@ package org.mozilla.servoview;
import android.app.Activity;
import android.content.Context;
import android.util.Log;
import android.view.Surface;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
@ -30,21 +31,16 @@ public class Servo {
RunCallback runCallback,
GfxCallbacks gfxcb,
Client client,
Activity activity) {
Activity activity,
Surface surface) {
mRunCallback = runCallback;
mServoCallbacks = new Callbacks(client, gfxcb);
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) {
@ -164,6 +160,13 @@ public class Servo {
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) {
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.content.Context;
import android.net.Uri;
import android.opengl.GLES31;
import android.opengl.GLSurfaceView;
import android.util.AttributeSet;
import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.Choreographer;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.OverScroller;
import android.view.Surface;
import android.view.SurfaceView;
import android.view.SurfaceHolder;
import org.mozilla.servoview.JNIServo.ServoCoordinates;
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.RunCallback;
import javax.microedition.khronos.egl.EGLConfig;
import javax.microedition.khronos.opengles.GL10;
import android.view.Choreographer;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.widget.OverScroller;
public class ServoView extends GLSurfaceView
implements
GestureDetector.OnGestureListener,
ScaleGestureDetector.OnScaleGestureListener,
Choreographer.FrameCallback,
GfxCallbacks,
RunCallback {
import java.util.ArrayList;
public class ServoView extends SurfaceView
implements
GfxCallbacks,
RunCallback,
Choreographer.FrameCallback,
GestureDetector.OnGestureListener,
ScaleGestureDetector.OnScaleGestureListener {
private static final String LOGTAG = "ServoView";
private Activity mActivity;
private Servo mServo;
private GLThread mGLThread;
private Handler mGLLooperHandler;
private Surface mASurface;
protected Servo mServo = null;
private Client mClient = null;
private Uri mInitialUri = null;
private boolean mAnimating;
private String mServoArgs;
private String mServoLog;
private String mGstDebug;
private String mInitialUri;
private Activity mActivity;
private GestureDetector mGestureDetector;
private ScaleGestureDetector mScaleGestureDetector;
private OverScroller mScroller;
private int mLastX = 0;
private int mCurX = 0;
private int mLastY = 0;
private int mCurY = 0;
private boolean mFlinging;
private ScaleGestureDetector mScaleGestureDetector;
private OverScroller mScroller;
private boolean mZooming;
private float mZoomFactor = 1;
private boolean mRedrawing;
private boolean mAnimating;
private boolean mPaused = false;
public ServoView(Context context) {
super(context);
@ -71,79 +73,49 @@ public class ServoView extends GLSurfaceView
init(context);
}
public void onDetachedFromWindow() {
mServo.shutdown();
mServo = null;
super.onDetachedFromWindow();
}
private void init(Context context) {
mActivity = (Activity) context;
setFocusable(true);
setFocusableInTouchMode(true);
setClickable(true);
ArrayList view = new ArrayList();
view.add(this);
addTouchables(view);
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);
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;
mServoLog = log;
mGstDebug = gstdebug;
}
public void reload() {
mServo.reload();
// RunCallback
public void inGLThread(Runnable r) {
mGLLooperHandler.post(r);
}
public void goBack() {
mServo.goBack();
public void inUIThread(Runnable r) {
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() {
requestRender();
}
// Scroll and click
public void animationStateChanged(boolean animating) {
if (!mAnimating && animating) {
post(() -> startLooping());
@ -154,48 +126,6 @@ public class ServoView extends GLSurfaceView
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() {
// 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) {
mFlinging = true;
@ -275,20 +262,22 @@ public class ServoView extends GLSurfaceView
return true;
}
@Override
public boolean onTouchEvent(final MotionEvent e) {
mGestureDetector.onTouchEvent(e);
mScaleGestureDetector.onTouchEvent(e);
int action = e.getActionMasked();
float x = e.getX();
float y = e.getY();
int pointerIndex = e.getActionIndex();
int pointerId = e.getPointerId(pointerIndex);
switch (action) {
case (MotionEvent.ACTION_DOWN):
mServo.touchDown(x, y, pointerId);
case (MotionEvent.ACTION_POINTER_DOWN):
mFlinging = false;
mScroller.forceFinished(true);
mCurX = (int) x;
@ -299,32 +288,35 @@ public class ServoView extends GLSurfaceView
case (MotionEvent.ACTION_MOVE):
mCurX = (int) x;
mCurY = (int) y;
mServo.touchMove(x, y, pointerId);
return true;
case (MotionEvent.ACTION_UP):
mServo.touchUp(x, y, pointerId);
case (MotionEvent.ACTION_POINTER_UP):
return true;
case (MotionEvent.ACTION_CANCEL):
mServo.touchCancel(x, y, pointerId);
return true;
default:
return true;
}
}
public boolean onSingleTapUp(MotionEvent e) {
return false;
}
// OnGestureListener
public void onLongPress(MotionEvent e) {
}
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;
}
public boolean onSingleTapUp(MotionEvent e) {
click(e.getX(), e.getY());
return false;
}
public void onShowPress(MotionEvent e) {
}
// OnScaleGestureListener
@Override
public boolean onScaleBegin(ScaleGestureDetector detector) {
if (mScroller.isFinished()) {
@ -351,41 +343,81 @@ public class ServoView extends GLSurfaceView
mServo.pinchZoomEnd(mZoomFactor, 0, 0);
}
@Override
public void onPause() {
super.onPause();
if (mServo != null) {
mServo.suspend(true);
}
private void initGestures(Context context) {
mGestureDetector = new GestureDetector(context, this);
mScaleGestureDetector = new ScaleGestureDetector(context, this);
mScroller = new OverScroller(context);
}
@Override
public void onResume() {
super.onResume();
if (mServo != null) {
mServo.suspend(false);
}
public void mediaSessionAction(int action) {
mServo.mediaSessionAction(action);
}
static class ServoGLRenderer implements Renderer {
private final ServoView mView;
ServoGLRenderer(ServoView view) {
mView = view;
class GLThread extends Thread implements SurfaceHolder.Callback {
private Activity mActivity;
private ServoView mServoView;
GLThread(Activity activity, ServoView servoView) {
mActivity = activity;
mServoView = servoView;
}
public void onSurfaceCreated(GL10 unused, EGLConfig config) {
mView.onGLReady();
public void surfaceCreated(SurfaceHolder holder) {
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) {
GLES31.glViewport(0, 0, width, height);
mView.onSurfaceInvalidated(width, height);
public void surfaceDestroyed(SurfaceHolder holder) {
Log.d(LOGTAG, "GLThread::surfaceDestroyed");
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) -->