Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Revisit how the Android port works. #20912

Merged
merged 6 commits into from Jul 31, 2018

new android port: minimal Android app that relies on libsimpleservo

  • Loading branch information
paulrouget committed Jul 31, 2018
commit be6b5f9aadc5113181693c779c3c222bda233969
@@ -211,6 +211,9 @@ dependencies {
googlevrCompile 'com.google.vr:sdk-base:1.70.0'
googlevrCompile(name:'GVRService', 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
@@ -3,13 +3,13 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" android:installLocation="auto"
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-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_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">
<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;
import android.annotation.TargetApi;

import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.ActivityInfo;
import android.content.pm.PackageInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.os.Handler;
import android.os.PowerManager;
import android.preference.PreferenceManager;
import android.system.ErrnoException;
import android.system.Os;
import android.util.Log;
import android.view.SurfaceView;
import android.view.View;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
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.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 android.app.NativeActivity {
private static final String LOGTAG = "Servo";
private boolean mFullScreen = false;
private static final String PREF_KEY_RESOURCES_SYNC = "res_sync_v";

static {
Log.i(LOGTAG, "Loading the NativeActivity");

// Libaries should be loaded in reverse dependency order
System.loadLibrary("c++_shared");
System.loadLibrary("servo");
}

@Override
public 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);
}
}
public class MainActivity extends Activity implements ServoView.Client {

JSONObject preferences = loadPreferences();
private static final String LOGTAG = "MainActivity";

boolean keepScreenOn = false;
ServoView mServoView;
Button mBackButton;
Button mFwdButton;
Button mReloadButton;
Button mStopButton;
EditText mUrlField;
ProgressBar mProgressBar;

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);
}
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

// NativeActivity ignores the Android view hierarchy because it’s designed
// to take over the surface from the window to directly draw to it.
// Inject a custom SurfaceView in order to support adding views on top of the browser.
// (e.g. Native Banners, Daydream GVRLayout or other native views)
getWindow().takeSurface(null);
FrameLayout layout = new FrameLayout(this);
layout.setLayoutParams(new FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
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
if (keepScreenOn) {
keepScreenOn();
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.setClient(this);
mBackButton.setEnabled(false);
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
if (mFullScreen) {
addFullScreenListener();
String args = getIntent().getStringExtra("servoargs");
if (args != null) {
mServoView.setServoArgs(args);
}
}

@Override
protected void onStop() {
Log.d(LOGTAG, "onStop");
super.onStop();
setupUrlField();
}

@Override
protected void onPause() {
Log.d(LOGTAG, "onPause");
super.onPause();
private void setupUrlField() {
mUrlField.setOnEditorActionListener((v, actionId, event) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
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
protected void onResume() {
Log.d(LOGTAG, "onPause");
if (mFullScreen) {
setFullScreen();
private void loadUrlFromField() {
String text = mUrlField.getText().toString();
text = text.trim();
String uri;

if (text.contains(" ") || !text.contains(".")) {
uri = URLUtil.composeSearchUrl(text, "https://duckduckgo.com/html/?q=%s", "%s");
} else {
uri = URLUtil.guessUrl(text);
}
super.onResume();

mServoView.loadUri(Uri.parse(uri));
}

@Override
public void onWindowFocusChanged(boolean hasFocus) {
super.onWindowFocusChanged(hasFocus);
if (hasFocus && mFullScreen) {
setFullScreen();
}
public void onReloadClicked(View v) {
mServoView.reload();
}

// keep the device's screen turned on and bright.
private void keepScreenOn() {
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
public void onBackClicked(View v) {
mServoView.goBack();
}

// 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 {
flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE;
}
getWindow().getDecorView().setSystemUiVisibility(flags);
public void onForwardClicked(View v) {
mServoView.goForward();
}

@TargetApi(19)
private int getImmersiveFlag() {
return View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
public void onStopClicked(View v) {
mServoView.stop();
}

private void addFullScreenListener() {
View decorView = getWindow().getDecorView();
decorView.setOnSystemUiVisibilityChangeListener(
new View.OnSystemUiVisibilityChangeListener() {
public void onSystemUiVisibilityChange(int visibility) {
if ((visibility & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0) {
setFullScreen();
}
}
});
@Override
public void onLoadStarted() {
mReloadButton.setEnabled(false);
mStopButton.setEnabled(true);
mReloadButton.setVisibility(View.GONE);
mStopButton.setVisibility(View.VISIBLE);
mProgressBar.setVisibility(View.VISIBLE);
}

private String loadAsset(String file) {
InputStream is = null;
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));
}
}
@Override
public void onLoadEnded() {
mReloadButton.setEnabled(true);
mStopButton.setEnabled(false);
mReloadButton.setVisibility(View.VISIBLE);
mStopButton.setVisibility(View.GONE);
mProgressBar.setVisibility(View.INVISIBLE);
}

private JSONObject loadPreferences() {
String json = loadAsset("prefs.json");
try {
return new JSONObject(json);
} catch (JSONException e) {
Log.e(LOGTAG, Log.getStackTraceString(e));
return new JSONObject();
}
@Override
public void onTitleChanged(String title) {
}

private File getAppDataDir() {
File file = getExternalFilesDir(null);
return file != null ? file : getFilesDir();
@Override
public void onUrlChanged(String url) {
mUrlField.setText(url);
}

private void set_url(String url) {
try {
File file = new File(getAppDataDir() + "/android_params");
if (!file.exists()) {
file.createNewFile();
}
PrintStream out = new PrintStream(new FileOutputStream(file, false));
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 onHistoryChanged(boolean canGoBack, boolean canGoForward) {
mBackButton.setEnabled(canGoBack);
mFwdButton.setEnabled(canGoForward);
}

}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.