mirror of
https://github.com/servo/servo.git
synced 2025-08-05 05:30:08 +01:00
new android port: minimal Android app that relies on libsimpleservo
This commit is contained in:
parent
baf6635a4c
commit
be6b5f9aad
11 changed files with 605 additions and 209 deletions
|
@ -211,6 +211,9 @@ dependencies {
|
||||||
googlevrCompile 'com.google.vr:sdk-base:1.70.0'
|
googlevrCompile 'com.google.vr:sdk-base:1.70.0'
|
||||||
googlevrCompile(name:'GVRService', ext:'aar')
|
googlevrCompile(name:'GVRService', ext:'aar')
|
||||||
oculusvrCompile(name:'OVRService', ext:'aar')
|
oculusvrCompile(name:'OVRService', ext:'aar')
|
||||||
|
|
||||||
|
// compile is deprecated. Will become "implementation" once we upgrade graddle.
|
||||||
|
compile 'com.android.support.constraint:constraint-layout:1.0.0'
|
||||||
}
|
}
|
||||||
|
|
||||||
// Utility methods
|
// Utility methods
|
||||||
|
|
|
@ -3,13 +3,13 @@
|
||||||
<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="com.mozilla.servo">
|
package="com.mozilla.servo">
|
||||||
|
|
||||||
<uses-feature android:glEsVersion="0x00020000" 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"/>
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH" />
|
<uses-permission android:name="android.permission.BLUETOOTH" />
|
||||||
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
<uses-permission android:name="android.permission.BLUETOOTH_ADMIN"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application android:label="Servo" android:icon="@mipmap/servo">
|
<application android:label="Servo" android:icon="@mipmap/servo">
|
||||||
<activity android:name=".MainActivity"
|
<activity android:name=".MainActivity"
|
||||||
|
|
|
@ -1,248 +1,160 @@
|
||||||
|
/* -*- 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.servo;
|
package com.mozilla.servo;
|
||||||
import android.annotation.TargetApi;
|
|
||||||
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.net.Uri;
|
||||||
import android.content.pm.ActivityInfo;
|
|
||||||
import android.content.pm.PackageInfo;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Environment;
|
import android.system.ErrnoException;
|
||||||
import android.os.Handler;
|
import android.system.Os;
|
||||||
import android.os.PowerManager;
|
|
||||||
import android.preference.PreferenceManager;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.SurfaceView;
|
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.WindowManager;
|
import android.view.inputmethod.EditorInfo;
|
||||||
|
import android.view.inputmethod.InputMethodManager;
|
||||||
import android.webkit.URLUtil;
|
import android.webkit.URLUtil;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.Button;
|
||||||
|
import android.widget.EditText;
|
||||||
|
import android.widget.ProgressBar;
|
||||||
|
|
||||||
import com.mozilla.servo.BuildConfig;
|
import com.mozilla.servoview.ServoView;
|
||||||
|
|
||||||
import org.json.JSONException;
|
|
||||||
import org.json.JSONObject;
|
|
||||||
|
|
||||||
import java.io.BufferedInputStream;
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.FileOutputStream;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.io.InputStreamReader;
|
|
||||||
import java.io.PrintStream;
|
|
||||||
import java.lang.System;
|
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.zip.ZipEntry;
|
|
||||||
import java.util.zip.ZipFile;
|
|
||||||
|
|
||||||
|
public class MainActivity extends Activity implements ServoView.Client {
|
||||||
|
|
||||||
public class MainActivity extends android.app.NativeActivity {
|
private static final String LOGTAG = "MainActivity";
|
||||||
private static final String LOGTAG = "Servo";
|
|
||||||
private boolean mFullScreen = false;
|
|
||||||
private static final String PREF_KEY_RESOURCES_SYNC = "res_sync_v";
|
|
||||||
|
|
||||||
static {
|
ServoView mServoView;
|
||||||
Log.i(LOGTAG, "Loading the NativeActivity");
|
Button mBackButton;
|
||||||
|
Button mFwdButton;
|
||||||
|
Button mReloadButton;
|
||||||
|
Button mStopButton;
|
||||||
|
EditText mUrlField;
|
||||||
|
ProgressBar mProgressBar;
|
||||||
|
|
||||||
// Libaries should be loaded in reverse dependency order
|
|
||||||
System.loadLibrary("c++_shared");
|
|
||||||
System.loadLibrary("servo");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
protected void onCreate(Bundle savedInstanceState) {
|
||||||
final Intent intent = getIntent();
|
|
||||||
if (intent != null && intent.getAction().equals(Intent.ACTION_VIEW)) {
|
|
||||||
final String url = intent.getDataString();
|
|
||||||
if (url != null && URLUtil.isValidUrl(url)) {
|
|
||||||
Log.d(LOGTAG, "Received url "+url);
|
|
||||||
set_url(url);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
JSONObject preferences = loadPreferences();
|
|
||||||
|
|
||||||
boolean keepScreenOn = false;
|
|
||||||
|
|
||||||
if (BuildConfig.FLAVOR.contains("vr")) {
|
|
||||||
// Force fullscreen mode and keep screen on for VR experiences.
|
|
||||||
keepScreenOn = true;
|
|
||||||
mFullScreen = true;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
keepScreenOn = preferences.optBoolean("shell.keep_screen_on.enabled", false);
|
|
||||||
mFullScreen = !preferences.optBoolean("shell.native-titlebar.enabled", false);
|
|
||||||
|
|
||||||
String orientation = preferences.optString("shell.native-orientation", "both");
|
|
||||||
|
|
||||||
// Handle orientation preference
|
|
||||||
if (orientation.equalsIgnoreCase("portrait")) {
|
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
|
|
||||||
}
|
|
||||||
else if (orientation.equalsIgnoreCase("landscape")) {
|
|
||||||
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
|
setContentView(R.layout.activity_main);
|
||||||
|
|
||||||
// NativeActivity ignores the Android view hierarchy because it’s designed
|
mServoView = (ServoView)findViewById(R.id.servoview);
|
||||||
// to take over the surface from the window to directly draw to it.
|
mBackButton = (Button)findViewById(R.id.backbutton);
|
||||||
// Inject a custom SurfaceView in order to support adding views on top of the browser.
|
mFwdButton = (Button)findViewById(R.id.forwardbutton);
|
||||||
// (e.g. Native Banners, Daydream GVRLayout or other native views)
|
mReloadButton = (Button)findViewById(R.id.reloadbutton);
|
||||||
getWindow().takeSurface(null);
|
mStopButton = (Button)findViewById(R.id.stopbutton);
|
||||||
FrameLayout layout = new FrameLayout(this);
|
mUrlField = (EditText)findViewById(R.id.urlfield);
|
||||||
layout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
|
mProgressBar = (ProgressBar)findViewById(R.id.progressbar);
|
||||||
FrameLayout.LayoutParams.MATCH_PARENT));
|
|
||||||
SurfaceView nativeSurface = new SurfaceView(this);
|
|
||||||
nativeSurface.getHolder().addCallback(this);
|
|
||||||
layout.addView(nativeSurface, new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.MATCH_PARENT));
|
|
||||||
setContentView(layout);
|
|
||||||
|
|
||||||
// Handle keep screen on preference
|
mServoView.setClient(this);
|
||||||
if (keepScreenOn) {
|
mBackButton.setEnabled(false);
|
||||||
keepScreenOn();
|
mFwdButton.setEnabled(false);
|
||||||
|
|
||||||
|
mServoView.requestFocus();
|
||||||
|
|
||||||
|
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
|
||||||
|
File sdcard = getExternalFilesDir("");
|
||||||
|
String host = sdcard.toPath().resolve("android_hosts").toString();
|
||||||
|
try {
|
||||||
|
Os.setenv("HOST_FILE", host, false);
|
||||||
|
} catch (ErrnoException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle full screen preference
|
String args = getIntent().getStringExtra("servoargs");
|
||||||
if (mFullScreen) {
|
if (args != null) {
|
||||||
addFullScreenListener();
|
mServoView.setServoArgs(args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setupUrlField();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void setupUrlField() {
|
||||||
protected void onStop() {
|
mUrlField.setOnEditorActionListener((v, actionId, event) -> {
|
||||||
Log.d(LOGTAG, "onStop");
|
if (actionId == EditorInfo.IME_ACTION_DONE) {
|
||||||
super.onStop();
|
loadUrlFromField();
|
||||||
|
mServoView.requestFocus();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
});
|
||||||
|
mUrlField.setOnFocusChangeListener((v, hasFocus) -> {
|
||||||
|
if(v.getId() == R.id.urlfield && !hasFocus) {
|
||||||
|
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
assert imm != null;
|
||||||
|
imm.hideSoftInputFromWindow(v.getWindowToken(), 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private void loadUrlFromField() {
|
||||||
protected void onPause() {
|
String text = mUrlField.getText().toString();
|
||||||
Log.d(LOGTAG, "onPause");
|
text = text.trim();
|
||||||
super.onPause();
|
String uri;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (text.contains(" ") || !text.contains(".")) {
|
||||||
protected void onResume() {
|
uri = URLUtil.composeSearchUrl(text, "https://duckduckgo.com/html/?q=%s", "%s");
|
||||||
Log.d(LOGTAG, "onPause");
|
|
||||||
if (mFullScreen) {
|
|
||||||
setFullScreen();
|
|
||||||
}
|
|
||||||
super.onResume();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onWindowFocusChanged(boolean hasFocus) {
|
|
||||||
super.onWindowFocusChanged(hasFocus);
|
|
||||||
if (hasFocus && mFullScreen) {
|
|
||||||
setFullScreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// keep the device's screen turned on and bright.
|
|
||||||
private void keepScreenOn() {
|
|
||||||
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dim toolbar and make the view fullscreen
|
|
||||||
private void setFullScreen() {
|
|
||||||
int flags = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
|
|
||||||
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
|
|
||||||
| View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
|
|
||||||
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // Hides navigation bar
|
|
||||||
| View.SYSTEM_UI_FLAG_FULLSCREEN; // Hides status bar
|
|
||||||
if( android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.KITKAT) {
|
|
||||||
flags |= getImmersiveFlag();
|
|
||||||
} else {
|
} else {
|
||||||
flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
|
uri = URLUtil.guessUrl(text);
|
||||||
}
|
}
|
||||||
getWindow().getDecorView().setSystemUiVisibility(flags);
|
|
||||||
|
mServoView.loadUri(Uri.parse(uri));
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(19)
|
public void onReloadClicked(View v) {
|
||||||
private int getImmersiveFlag() {
|
mServoView.reload();
|
||||||
return View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addFullScreenListener() {
|
public void onBackClicked(View v) {
|
||||||
View decorView = getWindow().getDecorView();
|
mServoView.goBack();
|
||||||
decorView.setOnSystemUiVisibilityChangeListener(
|
|
||||||
new View.OnSystemUiVisibilityChangeListener() {
|
|
||||||
public void onSystemUiVisibilityChange(int visibility) {
|
|
||||||
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
|
|
||||||
setFullScreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private String loadAsset(String file) {
|
public void onForwardClicked(View v) {
|
||||||
InputStream is = null;
|
mServoView.goForward();
|
||||||
BufferedReader reader = null;
|
|
||||||
try {
|
|
||||||
is = getAssets().open(file);
|
|
||||||
reader = new BufferedReader(new InputStreamReader(is));
|
|
||||||
StringBuilder result = new StringBuilder();
|
|
||||||
String line;
|
|
||||||
while ((line = reader.readLine()) != null) {
|
|
||||||
result.append(line).append('\n');
|
|
||||||
}
|
|
||||||
return result.toString();
|
|
||||||
} catch (IOException e) {
|
|
||||||
Log.e(LOGTAG, Log.getStackTraceString(e));
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
finally {
|
|
||||||
try {
|
|
||||||
if (reader != null) {
|
|
||||||
reader.close();
|
|
||||||
}
|
|
||||||
if (is != null) {
|
|
||||||
is.close();
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(LOGTAG, Log.getStackTraceString(e));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private JSONObject loadPreferences() {
|
public void onStopClicked(View v) {
|
||||||
String json = loadAsset("prefs.json");
|
mServoView.stop();
|
||||||
try {
|
|
||||||
return new JSONObject(json);
|
|
||||||
} catch (JSONException e) {
|
|
||||||
Log.e(LOGTAG, Log.getStackTraceString(e));
|
|
||||||
return new JSONObject();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private File getAppDataDir() {
|
@Override
|
||||||
File file = getExternalFilesDir(null);
|
public void onLoadStarted() {
|
||||||
return file != null ? file : getFilesDir();
|
mReloadButton.setEnabled(false);
|
||||||
|
mStopButton.setEnabled(true);
|
||||||
|
mReloadButton.setVisibility(View.GONE);
|
||||||
|
mStopButton.setVisibility(View.VISIBLE);
|
||||||
|
mProgressBar.setVisibility(View.VISIBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void set_url(String url) {
|
@Override
|
||||||
try {
|
public void onLoadEnded() {
|
||||||
File file = new File(getAppDataDir() + "/android_params");
|
mReloadButton.setEnabled(true);
|
||||||
if (!file.exists()) {
|
mStopButton.setEnabled(false);
|
||||||
file.createNewFile();
|
mReloadButton.setVisibility(View.VISIBLE);
|
||||||
}
|
mStopButton.setVisibility(View.GONE);
|
||||||
PrintStream out = new PrintStream(new FileOutputStream(file, false));
|
mProgressBar.setVisibility(View.INVISIBLE);
|
||||||
out.println("# The first line here should be the \"servo\" argument (without quotes) and the");
|
|
||||||
out.println("# last should be the URL to load.");
|
|
||||||
out.println("# Blank lines and those beginning with a '#' are ignored.");
|
|
||||||
out.println("# Each line should be a separate parameter as would be parsed by the shell.");
|
|
||||||
out.println("# For example, \"servo -p 10 http://en.wikipedia.org/wiki/Rust\" would take 4");
|
|
||||||
out.println("# lines (the \"-p\" and \"10\" are separate even though they are related).");
|
|
||||||
out.println("servo");
|
|
||||||
out.println("-w");
|
|
||||||
String absUrl = url.replace("file:///storage/emulated/0/", "/sdcard/");
|
|
||||||
out.println(absUrl);
|
|
||||||
out.flush();
|
|
||||||
out.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e(LOGTAG, Log.getStackTraceString(e));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onTitleChanged(String title) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUrlChanged(String url) {
|
||||||
|
mUrlField.setText(url);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onHistoryChanged(boolean canGoBack, boolean canGoForward) {
|
||||||
|
mBackButton.setEnabled(canGoBack);
|
||||||
|
mFwdButton.setEnabled(canGoForward);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,57 @@
|
||||||
|
/* -*- 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Maps /ports/libsimpleservo API
|
||||||
|
*/
|
||||||
|
public class NativeServo {
|
||||||
|
public native String version();
|
||||||
|
public native void init(Activity activity,
|
||||||
|
String args,
|
||||||
|
String url,
|
||||||
|
WakeupCallback wakeup,
|
||||||
|
ReadFileCallback readfile,
|
||||||
|
ServoCallbacks 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 interface ReadFileCallback {
|
||||||
|
byte[] readfile(String file);
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface WakeupCallback {
|
||||||
|
void wakeup();
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface ServoCallbacks {
|
||||||
|
void flush();
|
||||||
|
void onLoadStarted();
|
||||||
|
void onLoadEnded();
|
||||||
|
void onTitleChanged(String title);
|
||||||
|
void onUrlChanged(String url);
|
||||||
|
void onHistoryChanged(boolean canGoBack, boolean canGoForward);
|
||||||
|
void onAnimatingChanged(boolean animating);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
/* -*- 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,281 @@
|
||||||
|
/* -*- 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.os.Build;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
import android.net.Uri;
|
||||||
|
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 {
|
||||||
|
|
||||||
|
private static final String LOGTAG = "ServoView";
|
||||||
|
|
||||||
|
private Activity mActivity;
|
||||||
|
private NativeServo mServo;
|
||||||
|
private Client mClient = null;
|
||||||
|
private Uri mInitialUri = null;
|
||||||
|
private boolean mAnimating;
|
||||||
|
private String mServoArgs = "";
|
||||||
|
|
||||||
|
public ServoView(Context context, AttributeSet attrs) {
|
||||||
|
super(context, attrs);
|
||||||
|
mActivity = (Activity) context;
|
||||||
|
setFocusable(true);
|
||||||
|
setFocusableInTouchMode(true);
|
||||||
|
setWillNotCacheDrawing(false);
|
||||||
|
setEGLContextClientVersion(3);
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void reload() {
|
||||||
|
queueEvent(() -> mServo.reload());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void goBack() {
|
||||||
|
queueEvent(() -> mServo.goBack());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void goForward() {
|
||||||
|
queueEvent(() -> mServo.goForward());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void stop() {
|
||||||
|
queueEvent(() -> mServo.stop());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onSurfaceResized(int width, int height) {
|
||||||
|
queueEvent(() -> mServo.resize(width, height));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadUri(Uri uri) {
|
||||||
|
if (mServo != null) {
|
||||||
|
queueEvent(() -> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onTitleChanged(final String title) {
|
||||||
|
if (mClient != null) {
|
||||||
|
post(() -> mClient.onTitleChanged(title));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onUrlChanged(final String url) {
|
||||||
|
if (mClient != null) {
|
||||||
|
post(() -> mClient.onUrlChanged(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onHistoryChanged(final boolean canGoBack, final boolean canGoForward) {
|
||||||
|
if (mClient != null) {
|
||||||
|
post(() -> mClient.onHistoryChanged(canGoBack, canGoForward));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void onAnimatingChanged(final boolean animating) {
|
||||||
|
if (!mAnimating && animating) {
|
||||||
|
post(() -> Choreographer.getInstance().postFrameCallback(ServoView.this));
|
||||||
|
}
|
||||||
|
mAnimating = animating;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(() -> {
|
||||||
|
String uri = mInitialUri == null ? null : mInitialUri.toString();
|
||||||
|
mServo.init(mActivity, mServoArgs, uri, c1, c2, c3, 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) {
|
||||||
|
mFlinging = false;
|
||||||
|
queueEvent(() -> mServo.scrollEnd(0, 0, mCurX, mCurY));
|
||||||
|
if (!mAnimating) {
|
||||||
|
// Not scrolling. Not animating. We don't need to schedule
|
||||||
|
// another frame.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mFlinging) {
|
||||||
|
mScroller.computeScrollOffset();
|
||||||
|
mCurX = mScroller.getCurrX();
|
||||||
|
mCurY = mScroller.getCurrY();
|
||||||
|
}
|
||||||
|
|
||||||
|
int dx = mCurX - mLastX;
|
||||||
|
int dy = mCurY - mLastY;
|
||||||
|
|
||||||
|
mLastX = mCurX;
|
||||||
|
mLastY = mCurY;
|
||||||
|
|
||||||
|
if (dx != 0 || dy != 0) {
|
||||||
|
queueEvent(() -> mServo.scroll(dx, dy, mCurX, mCurY));
|
||||||
|
} else {
|
||||||
|
if (mAnimating) {
|
||||||
|
requestRender();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Choreographer.getInstance().postFrameCallback(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) {
|
||||||
|
mFlinging = true;
|
||||||
|
|
||||||
|
// FIXME: magic values
|
||||||
|
// https://github.com/servo/servo/issues/20361
|
||||||
|
int mPageWidth = 80000;
|
||||||
|
int mPageHeight = 80000;
|
||||||
|
mCurX = velocityX < 0 ? mPageWidth : 0;
|
||||||
|
mLastX = mCurX;
|
||||||
|
mCurY = velocityY < 0 ? mPageHeight : 0;
|
||||||
|
mLastY = mCurY;
|
||||||
|
mScroller.fling(mCurX, mCurY, (int)velocityX, (int)velocityY, 0, mPageWidth, 0, mPageHeight);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onDown(MotionEvent e) {
|
||||||
|
mScroller.forceFinished(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onTouchEvent(final MotionEvent e) {
|
||||||
|
mGestureDetector.onTouchEvent(e);
|
||||||
|
|
||||||
|
int action = e.getActionMasked();
|
||||||
|
switch(action) {
|
||||||
|
case (MotionEvent.ACTION_DOWN):
|
||||||
|
mCurX = (int)e.getX();
|
||||||
|
mLastX = mCurX;
|
||||||
|
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();
|
||||||
|
return true;
|
||||||
|
case (MotionEvent.ACTION_UP):
|
||||||
|
case (MotionEvent.ACTION_CANCEL):
|
||||||
|
if (!mFlinging) {
|
||||||
|
queueEvent(() -> mServo.scrollEnd(0, 0, mCurX, mCurY));
|
||||||
|
Choreographer.getInstance().removeFrameCallback(this);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean onSingleTapUp(MotionEvent e) {
|
||||||
|
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) { }
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,94 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
tools:context="com.mozilla.servo.MainActivity">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintHorizontal_bias="0.5"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent">
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="50dp"
|
||||||
|
android:orientation="horizontal">
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/backbutton"
|
||||||
|
style="@android:style/Widget.Material.Button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:onClick="onBackClicked"
|
||||||
|
android:text="Back"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/forwardbutton"
|
||||||
|
style="@android:style/Widget.Material.Button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:onClick="onForwardClicked"
|
||||||
|
android:text="Fwd"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/urlfield"
|
||||||
|
style="@android:style/Widget.Material.EditText"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:ems="10"
|
||||||
|
android:hint="URL or Search"
|
||||||
|
android:inputType="textUri"
|
||||||
|
android:selectAllOnFocus="true"
|
||||||
|
android:singleLine="true"
|
||||||
|
android:imeOptions="actionDone"/>
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/progressbar"
|
||||||
|
style="@android:style/Widget.ProgressBar"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:indeterminate="true" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/stopbutton"
|
||||||
|
style="@android:style/Widget.Material.Button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:onClick="onStopClicked"
|
||||||
|
android:text="Stop"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/reloadbutton"
|
||||||
|
style="@android:style/Widget.Material.Button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:onClick="onReloadClicked"
|
||||||
|
android:text="Rld"
|
||||||
|
android:textSize="10sp" />
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<com.mozilla.servoview.ServoView
|
||||||
|
android:id="@+id/servoview"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:focusable="true"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
</android.support.constraint.ConstraintLayout>
|
6
support/android/apk/app/src/main/res/values/colors.xml
Normal file
6
support/android/apk/app/src/main/res/values/colors.xml
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="colorPrimary">#3F51B5</color>
|
||||||
|
<color name="colorPrimaryDark">#303F9F</color>
|
||||||
|
<color name="colorAccent">#FF4081</color>
|
||||||
|
</resources>
|
3
support/android/apk/app/src/main/res/values/strings.xml
Normal file
3
support/android/apk/app/src/main/res/values/strings.xml
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
<resources>
|
||||||
|
<string name="app_name">Servo</string>
|
||||||
|
</resources>
|
8
support/android/apk/app/src/main/res/values/styles.xml
Normal file
8
support/android/apk/app/src/main/res/values/styles.xml
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
<resources>
|
||||||
|
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="AppTheme" parent="android:Theme.Holo.Light.DarkActionBar">
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
|
||||||
|
</resources>
|
|
@ -18,5 +18,5 @@ MY_LOCAL_PATH := $(call my-dir)
|
||||||
include $(CLEAR_VARS)
|
include $(CLEAR_VARS)
|
||||||
LOCAL_PATH:= $(SERVO_TARGET_DIR)
|
LOCAL_PATH:= $(SERVO_TARGET_DIR)
|
||||||
LOCAL_MODULE := servo
|
LOCAL_MODULE := servo
|
||||||
LOCAL_SRC_FILES := libservo.so
|
LOCAL_SRC_FILES := libsimpleservo.so
|
||||||
include $(PREBUILT_SHARED_LIBRARY)
|
include $(PREBUILT_SHARED_LIBRARY)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue