diff --git a/.gitignore b/.gitignore index d4ad27ff7b2..7ca621eaacf 100644 --- a/.gitignore +++ b/.gitignore @@ -24,6 +24,10 @@ Servo.app # IntelliJ .idea *.iws +*.iml + +#Gradle +.gradle # VSCode .vscode diff --git a/Cargo.lock b/Cargo.lock index 119302c8d0e..5439ff514d5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -39,11 +39,10 @@ source = "git+https://github.com/mmatyas/android-rs-injected-glue#1995be2c692d8d [[package]] name = "angle" -version = "0.1.2" -source = "git+https://github.com/servo/angle?branch=servo#99128001400771ee9c8a74dcf54cf6fe11b1e532" +version = "0.2.0" +source = "git+https://github.com/servo/angle?branch=servo#a1371e8a160128677af863d1d73f150862ba42b2" dependencies = [ "cmake 0.1.22 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.21 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -266,13 +265,6 @@ name = "browserhtml" version = "0.1.17" source = "git+https://github.com/browserhtml/browserhtml?branch=crate#7c66ae9a3e29d35230d5b9f16d19a562b1312c87" -[[package]] -name = "build-apk" -version = "0.0.1" -dependencies = [ - "walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)", -] - [[package]] name = "byteorder" version = "1.0.0" @@ -2240,7 +2232,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" name = "script" version = "0.0.1" dependencies = [ - "angle 0.1.2 (git+https://github.com/servo/angle?branch=servo)", + "angle 0.2.0 (git+https://github.com/servo/angle?branch=servo)", "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "atomic_refcell 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", "audio-video-metadata 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", @@ -2573,6 +2565,7 @@ dependencies = [ name = "servo_config" version = "0.0.1" dependencies = [ + "android_injected_glue 0.2.2 (git+https://github.com/mmatyas/android-rs-injected-glue)", "env_logger 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", "getopts 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", @@ -3324,7 +3317,7 @@ dependencies = [ "checksum alloc-no-stdlib 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b21f6ad9c9957eb5d70c3dee16d31c092b3cab339628f821766b05e6833d72b8" "checksum android_glue 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "d8289e9637439939cc92b1995b0972117905be88bc28116c86b64d6e589bcd38" "checksum android_injected_glue 0.2.2 (git+https://github.com/mmatyas/android-rs-injected-glue)" = "" -"checksum angle 0.1.2 (git+https://github.com/servo/angle?branch=servo)" = "" +"checksum angle 0.2.0 (git+https://github.com/servo/angle?branch=servo)" = "" "checksum ansi_term 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "23ac7c30002a5accbf7e8987d0632fa6de155b7c3d39d0067317a391e00a2ef6" "checksum antidote 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "34fde25430d87a9388dadbe6e34d7f72a462c8b43ac8d309b42b0a8505d7e2a5" "checksum app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a0c3b5be4ed53affe3e1a162b2e7ef9979bcaac80daa9026e9d7988c41e0e83" diff --git a/Cargo.toml b/Cargo.toml index a6862731aea..496e61e7f86 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,6 @@ members = [ "ports/cef", "ports/geckolib", "ports/servo", - "support/android/build-apk", ] [profile.dev] diff --git a/components/config/Cargo.toml b/components/config/Cargo.toml index 4af91cab66c..e8668a01973 100644 --- a/components/config/Cargo.toml +++ b/components/config/Cargo.toml @@ -27,3 +27,6 @@ env_logger = "0.4" [target.'cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))'.dependencies] xdg = "2.0" + +[target.'cfg(target_os = "android")'.dependencies] +android_injected_glue = {git = "https://github.com/mmatyas/android-rs-injected-glue"} diff --git a/components/config/basedir.rs b/components/config/basedir.rs index 5c7896a388b..521eb4e9dfd 100644 --- a/components/config/basedir.rs +++ b/components/config/basedir.rs @@ -6,8 +6,12 @@ //! For linux based platforms, it uses the XDG base directory spec but provides //! similar abstractions for non-linux platforms. +#[cfg(target_os = "android")] +use android_injected_glue; #[cfg(any(target_os = "macos", target_os = "windows"))] use std::env; +#[cfg(target_os = "android")] +use std::ffi::CStr; use std::path::PathBuf; #[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))] use xdg; @@ -20,8 +24,12 @@ pub fn default_config_dir() -> Option { } #[cfg(target_os = "android")] +#[allow(unsafe_code)] pub fn default_config_dir() -> Option { - Some(PathBuf::from("/sdcard/servo")) + let dir = unsafe { + CStr::from_ptr((*android_injected_glue::get_app().activity).externalDataPath) + }; + Some(PathBuf::from(dir.to_str().unwrap())) } #[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))] @@ -32,8 +40,12 @@ pub fn default_data_dir() -> Option { } #[cfg(target_os = "android")] +#[allow(unsafe_code)] pub fn default_data_dir() -> Option { - Some(PathBuf::from("/sdcard/servo")) + let dir = unsafe { + CStr::from_ptr((*android_injected_glue::get_app().activity).internalDataPath) + }; + Some(PathBuf::from(dir.to_str().unwrap())) } #[cfg(all(unix, not(target_os = "macos"), not(target_os = "ios"), not(target_os = "android")))] @@ -44,8 +56,14 @@ pub fn default_cache_dir() -> Option { } #[cfg(target_os = "android")] +#[allow(unsafe_code)] pub fn default_cache_dir() -> Option { - Some(PathBuf::from("/sdcard/servo")) + // TODO: Use JNI to call context.getCacheDir(). + // There is no equivalent function in NDK/NativeActivity. + let dir = unsafe { + CStr::from_ptr((*android_injected_glue::get_app().activity).externalDataPath) + }; + Some(PathBuf::from(dir.to_str().unwrap())) } #[cfg(target_os = "macos")] diff --git a/components/config/lib.rs b/components/config/lib.rs index a3de20ad73a..1ff26a406bc 100644 --- a/components/config/lib.rs +++ b/components/config/lib.rs @@ -4,6 +4,8 @@ #![deny(unsafe_code)] +#[cfg(target_os = "android")] +extern crate android_injected_glue; extern crate euclid; extern crate getopts; #[macro_use] extern crate lazy_static; diff --git a/components/config/resource_files.rs b/components/config/resource_files.rs index 4a155ecf51e..89b9924642e 100644 --- a/components/config/resource_files.rs +++ b/components/config/resource_files.rs @@ -2,8 +2,12 @@ * 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/. */ +#[cfg(target_os = "android")] +use android_injected_glue; #[cfg(not(target_os = "android"))] use std::env; +#[cfg(target_os = "android")] +use std::ffi::CStr; use std::fs::File; use std::io::{self, Read}; use std::path::{Path, PathBuf}; @@ -21,8 +25,12 @@ pub fn set_resources_path(path: Option) { } #[cfg(target_os = "android")] +#[allow(unsafe_code)] pub fn resources_dir_path() -> io::Result { - Ok(PathBuf::from("/sdcard/servo/")) + let dir = unsafe { + CStr::from_ptr((*android_injected_glue::get_app().activity).externalDataPath) + }; + Ok(PathBuf::from(dir.to_str().unwrap())) } #[cfg(not(target_os = "android"))] diff --git a/etc/ci/buildbot_steps.yml b/etc/ci/buildbot_steps.yml index 96ec70fc6a0..c3b413742f3 100644 --- a/etc/ci/buildbot_steps.yml +++ b/etc/ci/buildbot_steps.yml @@ -94,7 +94,7 @@ linux-nightly: android: - ./mach clean-nightlies --keep 3 --force - env SERVO_RUSTC_LLVM_ASSERTIONS=1 ./mach build --android --dev - - env SERVO_RUSTC_LLVM_ASSERTIONS=1 ./mach package --android --dev + - env SERVO_RUSTC_LLVM_ASSERTIONS=1 ANDROID_SDK=/home/servo/android/sdk/r25.2.3 ./mach package --android --dev - bash ./etc/ci/lockfile_changed.sh - bash ./etc/ci/manifest_changed.sh - python ./etc/ci/check_dynamic_symbols.py @@ -102,7 +102,7 @@ android: android-nightly: - ./mach clean-nightlies --keep 3 --force - env SERVO_RUSTC_LLVM_ASSERTIONS=1 ./mach build --android --release - - env SERVO_RUSTC_LLVM_ASSERTIONS=1 ./mach package --android --release + - env SERVO_RUSTC_LLVM_ASSERTIONS=1 ANDROID_SDK=/home/servo/android/sdk/r25.2.3 ./mach package --android --release - ./etc/ci/upload_nightly.sh android arm32: diff --git a/ports/glutin/window.rs b/ports/glutin/window.rs index aa8d573224b..4b0ceb65666 100644 --- a/ports/glutin/window.rs +++ b/ports/glutin/window.rs @@ -940,14 +940,6 @@ impl Window { } } -// WindowProxy is not implemented for android yet - -#[cfg(target_os = "android")] -fn create_window_proxy(_: &Window) -> Option { - None -} - -#[cfg(not(target_os = "android"))] fn create_window_proxy(window: &Window) -> Option { match window.kind { WindowKind::Window(ref window) => { diff --git a/ports/servo/main.rs b/ports/servo/main.rs index b24e550ff2d..420d17ffc4d 100644 --- a/ports/servo/main.rs +++ b/ports/servo/main.rs @@ -33,6 +33,7 @@ extern crate sig; use backtrace::Backtrace; use servo::Browser; use servo::compositing::windowing::WindowEvent; +use servo::config; use servo::config::opts::{self, ArgumentParsingResult}; use servo::config::servo_version; use std::env; @@ -219,8 +220,9 @@ fn args() -> Vec { use std::fs::File; use std::io::{BufRead, BufReader}; - const PARAMS_FILE: &'static str = "/sdcard/servo/android_params"; - match File::open(PARAMS_FILE) { + let mut params_file = config::basedir::default_config_dir().unwrap(); + params_file.push("android_params"); + match File::open(params_file.to_str().unwrap()) { Ok(f) => { let mut vec = Vec::new(); let file = BufReader::new(&f); @@ -236,7 +238,7 @@ fn args() -> Vec { }, Err(e) => { debug!("Failed to open params file '{}': {}", - PARAMS_FILE, + params_file.to_str().unwrap(), Error::description(&e)); vec!["servo".to_owned(), "http://en.wikipedia.org/wiki/Rust".to_owned()] }, diff --git a/python/servo/package_commands.py b/python/servo/package_commands.py index 9abffc603e5..63921037c25 100644 --- a/python/servo/package_commands.py +++ b/python/servo/package_commands.py @@ -28,7 +28,6 @@ from servo.command_base import ( BuildNotFound, cd, CommandBase, - find_dep_path_newest, is_macosx, is_windows, get_browserhtml_path, @@ -143,34 +142,12 @@ class PackageCommands(CommandBase): target_dir = path.dirname(binary_path) if android: if dev: - env["NDK_DEBUG"] = "1" - env["ANT_FLAVOR"] = "debug" - dev_flag = "-d" + task_name = "assembleArmDebug" else: - env["ANT_FLAVOR"] = "release" - dev_flag = "" - - output_apk = "{}.apk".format(binary_path) - - dir_to_apk = path.join(target_dir, "apk") - if path.exists(dir_to_apk): - print("Cleaning up from previous packaging") - delete(dir_to_apk) - shutil.copytree(path.join(dir_to_root, "support", "android", "apk"), dir_to_apk) - - blurdroid_path = find_dep_path_newest('blurdroid', binary_path) - if blurdroid_path is None: - print("Could not find blurdroid package; perhaps you haven't built Servo.") - return 1 - else: - dir_to_libs = path.join(dir_to_apk, "libs") - if not path.exists(dir_to_libs): - os.makedirs(dir_to_libs) - shutil.copy2(blurdroid_path + '/out/blurdroid.jar', dir_to_libs) + task_name = "assembleArmRelease" try: - with cd(path.join("support", "android", "build-apk")): - subprocess.check_call(["cargo", "run", "--", dev_flag, "-o", output_apk, "-t", target_dir, - "-r", dir_to_root], env=env) + with cd(path.join("support", "android", "apk")): + subprocess.check_call(["./gradlew", "--no-daemon", 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/python/servo/post_build_commands.py b/python/servo/post_build_commands.py index d6637601697..0f3c9e9b7df 100644 --- a/python/servo/post_build_commands.py +++ b/python/servo/post_build_commands.py @@ -83,11 +83,12 @@ class PostBuildCommands(CommandBase): return script = [ "am force-stop com.mozilla.servo", - "echo servo >/sdcard/servo/android_params" + "echo servo >/sdcard/Android/data/com.mozilla.servo/files/android_params" ] for param in params: script += [ - "echo '%s' >>/sdcard/servo/android_params" % param.replace("'", "\\'") + "echo '%s' >>/sdcard/Android/data/com.mozilla.servo/files/android_params" + % param.replace("'", "\\'") ] script += [ "am start com.mozilla.servo/com.mozilla.servo.MainActivity", diff --git a/resources/prefs.json b/resources/prefs.json index 04df51d0604..0bd519b2557 100644 --- a/resources/prefs.json +++ b/resources/prefs.json @@ -63,5 +63,7 @@ "session-history.max-length": 20, "shell.builtin-key-shortcuts.enabled": true, "shell.homepage": "https://servo.org", + "shell.keep_screen_on.enabled": false, + "shell.native-orientation": "both", "shell.native-titlebar.enabled": true } diff --git a/support/android/apk/app/build.gradle b/support/android/apk/app/build.gradle new file mode 100644 index 00000000000..dd16b1ffc26 --- /dev/null +++ b/support/android/apk/app/build.gradle @@ -0,0 +1,299 @@ +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 + +android { + compileSdkVersion 25 + buildToolsVersion "25.0.2" + + defaultConfig { + applicationId "com.mozilla.servo" + minSdkVersion 18 + targetSdkVersion 25 + versionCode 1 + versionName "1.0.0" + jackOptions { + enabled true + } + } + + compileOptions { + incremental false + } + + 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 { + 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') + } + } + } + + // Ignore default 'debug' and 'release' build types + variantFilter { variant -> + if(variant.buildType.name.equals('release') || variant.buildType.name.equals('debug')) { + variant.setIgnore(true); + } + } + + // Define apk output directory + applicationVariants.all { variant -> + variant.outputs.each { output -> + def name = variant.buildType.name + output.outputFile = new File(getApkPath(isDebug(name), getArch(name))) + } + } + + // Call our custom NDK Build task using flavor parameters + tasks.all { + compileTask -> + Pattern pattern = Pattern.compile(/^transformJackWithJackFor([\w\d]+)(Debug|Release)/); + Matcher matcher = pattern.matcher(compileTask.name); + // You can use this alternative pattern when jackCompiler is disabled + // Pattern pattern = Pattern.compile(/^compile([\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 + } +} + +dependencies { + //Dependency list + def deps = [ + new ServoDependency("blurdroid.jar", "blurdroid") + ] + + // Iterate all build types and dependencies + // For each dependency call the proper compile 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") + "Compile" + + for (ServoDependency dep: deps) { + String path = findDependencyPath(basePath, dep.fileName, dep.folderFilter) + if (path) { + "${cmd}" files(path) + } + } + } + } +} + +// Utility methods +String getTargetDir(boolean debug, String arch) { + def basePath = project.rootDir.getParentFile().getParentFile().getParentFile().absolutePath + return basePath + '/target/' + getRustTarget(arch) + '/' + (debug ? 'debug' : 'release') +} + +String getApkPath(boolean debug, String arch) { + return getTargetDir(debug, arch) + '/servo.apk' +} + +String getJniLibsPath(boolean debug, String arch) { + return getTargetDir(debug, arch) + '/apk/jniLibs' +} + +String getArch(String buildType) { + return buildType.replaceAll(/(Debug|Release)/, '') +} + +boolean isDebug(String buildType) { + return buildType.contains("Debug") +} + +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 'x86' + default: throw new GradleException("Invalid target architecture " + arch) + } +} + +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 +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 { + public ServoDependency(String fileName, String folderFilter = null) { + this.fileName = fileName; + this.folderFilter = folderFilter; + } + public String fileName; + public String folderFilter; +} diff --git a/support/android/apk/AndroidManifest.xml b/support/android/apk/app/src/main/AndroidManifest.xml similarity index 89% rename from support/android/apk/AndroidManifest.xml rename to support/android/apk/app/src/main/AndroidManifest.xml index 9f2eea71e97..355c8d4efc4 100644 --- a/support/android/apk/AndroidManifest.xml +++ b/support/android/apk/app/src/main/AndroidManifest.xml @@ -1,13 +1,9 @@ - + - - - + @@ -16,9 +12,10 @@ - + android:configChanges="orientation|keyboardHidden|screenSize"> + diff --git a/support/android/apk/app/src/main/java/com/mozilla/servo/MainActivity.java b/support/android/apk/app/src/main/java/com/mozilla/servo/MainActivity.java new file mode 100644 index 00000000000..f27964575e9 --- /dev/null +++ b/support/android/apk/app/src/main/java/com/mozilla/servo/MainActivity.java @@ -0,0 +1,299 @@ +package com.mozilla.servo; +import android.annotation.TargetApi; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageInfo; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.PowerManager; +import android.preference.PreferenceManager; +import android.util.Log; +import android.view.View; +import android.view.WindowManager; +import android.webkit.URLUtil; + +import com.mozilla.servo.BuildConfig; + +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) { + try { + extractAssets(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + 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 = 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); + + // Handle keep screen on preference + if (keepScreenOn) { + keepScreenOn(); + } + + // Handle full screen preference + if (mFullScreen) { + addFullScreenListener(); + } + } + + @Override + protected void onStop() { + Log.d(LOGTAG, "onStop"); + super.onStop(); + } + + @Override + protected void onPause() { + Log.d(LOGTAG, "onPause"); + super.onPause(); + } + + @Override + protected void onResume() { + 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 { + flags |= View.SYSTEM_UI_FLAG_LOW_PROFILE; + } + getWindow().getDecorView().setSystemUiVisibility(flags); + } + + @TargetApi(19) + private int getImmersiveFlag() { + return View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; + } + + 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(); + } + } + }); + } + + 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)); + } + } + } + + 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(); + } + } + + private boolean needsToExtractAssets(String path) { + SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); + int version = BuildConfig.VERSION_CODE; + + if (!new File(path).exists()) { + // Assets folder doesn't exist, resources need to be copied + prefs.edit().putInt(PREF_KEY_RESOURCES_SYNC, version).apply(); + return true; + } + + if (version != prefs.getInt(PREF_KEY_RESOURCES_SYNC, -1)) { + // Also force a reextract when the version changes and the resources may be updated + // This can be improved by generating a hash or version number of the resources + // instead of using version code of the app + prefs.edit().putInt(PREF_KEY_RESOURCES_SYNC, version).apply(); + return true; + } + return false; + } + + private File getAppDataDir() { + File file = getExternalFilesDir(null); + return file != null ? file : getFilesDir(); + } + /** + * extracts assets/ in the APK to /sdcard/servo. + */ + private void extractAssets() throws IOException { + String path = getAppDataDir().getAbsolutePath(); + if (!needsToExtractAssets(path)) { + return; + } + + ZipFile zipFile = null; + File targetDir = new File(path); + try { + zipFile = new ZipFile(this.getApplicationInfo().sourceDir); + for (Enumeration e = zipFile.entries(); e.hasMoreElements(); ) { + ZipEntry entry = e.nextElement(); + if (entry.isDirectory() || !entry.getName().startsWith("assets/")) { + continue; + } + File targetFile = new File(targetDir, entry.getName().substring("assets/".length())); + targetFile.getParentFile().mkdirs(); + byte[] tempBuffer = new byte[(int)entry.getSize()]; + BufferedInputStream is = null; + FileOutputStream os = null; + try { + is = new BufferedInputStream(zipFile.getInputStream(entry)); + os = new FileOutputStream(targetFile); + is.read(tempBuffer); + os.write(tempBuffer); + } finally { + try { + if (is != null) { + is.close(); + } + if (os != null) { + os.close(); + } + } catch (Exception ex) { + Log.e(LOGTAG, Log.getStackTraceString(ex)); + } + } + } + } finally { + try { + if (zipFile != null) { + zipFile.close(); + } + } catch (Exception e) { + Log.e(LOGTAG, Log.getStackTraceString(e)); + } + } + } + + 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)); + } + } +} diff --git a/support/android/apk/res/mipmap/servo.png b/support/android/apk/app/src/main/res/mipmap/servo.png similarity index 100% rename from support/android/apk/res/mipmap/servo.png rename to support/android/apk/app/src/main/res/mipmap/servo.png diff --git a/support/android/apk/build.gradle b/support/android/apk/build.gradle new file mode 100644 index 00000000000..1f497e94744 --- /dev/null +++ b/support/android/apk/build.gradle @@ -0,0 +1,17 @@ +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + repositories { + jcenter() + } + dependencies { + classpath 'com.android.tools.build:gradle:2.2.3' + } +} + +allprojects { + repositories { + jcenter() + } + + buildDir = rootDir.absolutePath + "/../../../target/gradle" +} diff --git a/support/android/apk/build.xml b/support/android/apk/build.xml deleted file mode 100644 index 3d1611517a8..00000000000 --- a/support/android/apk/build.xml +++ /dev/null @@ -1,92 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/support/android/apk/gradle/wrapper/gradle-wrapper.jar b/support/android/apk/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..13372aef5e2 Binary files /dev/null and b/support/android/apk/gradle/wrapper/gradle-wrapper.jar differ diff --git a/support/android/apk/gradle/wrapper/gradle-wrapper.properties b/support/android/apk/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..dbdc05d274d --- /dev/null +++ b/support/android/apk/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.14.1-all.zip diff --git a/support/android/apk/gradlew b/support/android/apk/gradlew new file mode 100755 index 00000000000..9d82f789151 --- /dev/null +++ b/support/android/apk/gradlew @@ -0,0 +1,160 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/support/android/apk/gradlew.bat b/support/android/apk/gradlew.bat new file mode 100644 index 00000000000..aec99730b4e --- /dev/null +++ b/support/android/apk/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/support/android/apk/jni/Android.mk b/support/android/apk/jni/Android.mk index d922b052b4e..c3e5ecdd29b 100644 --- a/support/android/apk/jni/Android.mk +++ b/support/android/apk/jni/Android.mk @@ -12,11 +12,11 @@ # See the License for the specific language governing permissions and # limitations under the License. # -LOCAL_PATH := $(call my-dir) + +MY_LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) +LOCAL_PATH:= $(SERVO_TARGET_DIR) LOCAL_MODULE := servo -LOCAL_SRC_FILES := $(TARGET_ARCH_ABI)/libmain.so +LOCAL_SRC_FILES := libservo.so include $(PREBUILT_SHARED_LIBRARY) - -# $(call import-module) diff --git a/support/android/apk/jni/Application.mk b/support/android/apk/jni/Application.mk index 794d4261d5a..f54d4b98c01 100644 --- a/support/android/apk/jni/Application.mk +++ b/support/android/apk/jni/Application.mk @@ -1,2 +1,4 @@ -APP_ABI := armeabi -APP_PLATFORM := $(ANDROID_PLATFORM) +NDK_TOOLCHAIN_VERSION := 4.9 +APP_MODULES := c++_shared servo +APP_PLATFORM := android-18 +APP_STL:= c++_shared diff --git a/support/android/apk/settings.gradle b/support/android/apk/settings.gradle new file mode 100644 index 00000000000..e7b4def49cb --- /dev/null +++ b/support/android/apk/settings.gradle @@ -0,0 +1 @@ +include ':app' diff --git a/support/android/apk/src/com/mozilla/servo/MainActivity.java b/support/android/apk/src/com/mozilla/servo/MainActivity.java deleted file mode 100644 index 31973499cc0..00000000000 --- a/support/android/apk/src/com/mozilla/servo/MainActivity.java +++ /dev/null @@ -1,118 +0,0 @@ -package com.mozilla.servo; -import android.app.NativeActivity; -import android.content.Intent; -import android.os.Bundle; -import android.util.Log; -import java.io.BufferedInputStream; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -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="ServoWrapper"; - static { - Log.i(LOGTAG, "Loading the NativeActivity"); - // libmain.so contains all of Servo native code with the injected glue. - System.loadLibrary("main"); - Log.i(LOGTAG, "libmain.so loaded"); - } - - private void set_url(String url) { - try { - PrintStream out = new PrintStream(new FileOutputStream("/sdcard/servo/android_params")); - 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 (FileNotFoundException e) { - } - } - - @Override - public void onCreate(Bundle savedInstanceState) { - if (needsToExtractAssets()) { - try { - extractAssets(); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - super.onCreate(savedInstanceState); - final Intent intent = getIntent(); - if (intent.getAction().equals(Intent.ACTION_VIEW)) { - final String url = intent.getDataString(); - Log.d(LOGTAG, "Received url "+url); - set_url(url); - } - } - - @Override - protected void onStop() { - super.onStop(); // Always call the superclass method first - - Log.d(LOGTAG, "got onStop; finishing servo activity"); - finish(); - - // Glutin and the Java wrapper libraries that we use currently do not support restoring - // Servo after Android has sent it to the background, as the resources were reclaimed. - // Until we either address that in glutin or move to a library that supports recreating - // the native resources after being restored, we just forcibly shut Servo down when it - // is sent to the background. - int pid = android.os.Process.myPid(); - android.os.Process.killProcess(pid); - System.exit(0); - } - - private boolean needsToExtractAssets() { - // todo: also force a reextract when the resources are updated. - return !(new File("/sdcard/servo").exists()); - } - - /** - * extracts assets/ in the APK to /sdcard/servo. - */ - private void extractAssets() throws IOException { - ZipFile zipFile = null; - File targetDir = new File("/sdcard/servo"); - try { - zipFile = new ZipFile(this.getApplicationInfo().sourceDir); - for (Enumeration e = zipFile.entries(); e.hasMoreElements(); ) { - ZipEntry entry = e.nextElement(); - if (entry.isDirectory() || !entry.getName().startsWith("assets/")) { - continue; - } - File targetFile = new File(targetDir, entry.getName().substring("assets/".length())); - targetFile.getParentFile().mkdirs(); - byte[] tempBuffer = new byte[(int)entry.getSize()]; - BufferedInputStream is = null; - FileOutputStream os = null; - try { - is = new BufferedInputStream(zipFile.getInputStream(entry)); - os = new FileOutputStream(targetFile); - is.read(tempBuffer); - os.write(tempBuffer); - } finally { - if (is != null) is.close(); - if (os != null) os.close(); - } - } - } finally { - if (zipFile != null) zipFile.close(); - } - } -} diff --git a/support/android/build-apk/Cargo.lock b/support/android/build-apk/Cargo.lock deleted file mode 100644 index 86f35c0c8f3..00000000000 --- a/support/android/build-apk/Cargo.lock +++ /dev/null @@ -1,35 +0,0 @@ -[root] -name = "build-apk" -version = "0.0.1" -dependencies = [ - "walkdir 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "kernel32-sys" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "walkdir" -version = "0.1.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -dependencies = [ - "kernel32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", - "winapi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)", -] - -[[package]] -name = "winapi" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" - -[[package]] -name = "winapi-build" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" - diff --git a/support/android/build-apk/Cargo.toml b/support/android/build-apk/Cargo.toml deleted file mode 100644 index d1d2878e877..00000000000 --- a/support/android/build-apk/Cargo.toml +++ /dev/null @@ -1,16 +0,0 @@ -[package] - -name = "build-apk" -version = "0.0.1" -authors = ["Pierre Krieger ", "The Servo Project Developers"] -license = "MPL-2.0" - -[[bin]] -name = "build-apk" -path = "src/main.rs" -test = false -doc = false -bench = false - -[dependencies] -walkdir = "1.0" diff --git a/support/android/build-apk/src/main.rs b/support/android/build-apk/src/main.rs deleted file mode 100644 index 10a140540ff..00000000000 --- a/support/android/build-apk/src/main.rs +++ /dev/null @@ -1,299 +0,0 @@ -/* This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ - -extern crate walkdir; - -use std::collections::{HashMap, HashSet}; -use std::env; -use std::fs; -use std::fs::DirBuilder; -use std::path::{Path, PathBuf}; -use std::process; -use std::process::{Command, Stdio}; -use walkdir::WalkDir; - -fn main() { - let (args, passthrough) = parse_arguments(); - - // Find all the native shared libraries that exist in the target directory. - let mut native_shared_libs = find_native_libs(&args); - - // Get the SDK path from the ANDROID_HOME env. - let sdk_path = env::var("ANDROID_HOME").ok().expect("Please set the ANDROID_HOME environment variable"); - let sdk_path = Path::new(&sdk_path); - - // Get the NDK path from NDK_HOME env. - let ndk_path = env::var("NDK_HOME").ok().expect("Please set the NDK_HOME environment variable"); - let ndk_path = Path::new(&ndk_path); - - // Get the target android platform from ANDROID_PLATFORM env. Expecting "android-{version}" - let android_platform = env::var("ANDROID_PLATFORM") - .ok() - .expect("Please set the ANDROID_PLATFORM environment variable"); - - // Add the C++ runtime .so - { - let libcpp_base_path = ndk_path.join("sources").join("cxx-stl").join("llvm-libc++").join("libs"); - let libcpp_filename = "libc++_shared.so"; - let libcpp_path = libcpp_base_path.join("armeabi").join(libcpp_filename); - native_shared_libs.insert(libcpp_filename.to_string(), libcpp_path); - } - - // Get the standalone NDK path from NDK_STANDALONE env. - // let standalone_path = env::var("NDK_STANDALONE").ok().unwrap_or("/opt/ndk_standalone".to_string()); - // let standalone_path = Path::new(&standalone_path); - - let debug = passthrough.contains(&"-d".to_string()); - - // Set the build directory that will contain all the necessary files to create the apk - let directory = args.target_path.join("apk"); - let resdir = args.root_path.join("resources/"); - - // executing ndk-build - env::set_var("V", "1"); - if debug { - env::set_var("NDK_DEBUG", "1"); - env::set_var("APP_OPTIM", "0"); - } else { - // Overrides android:debuggable propery in the .xml file. - env::set_var("APP_OPTIM", "1"); - } - - // Copy libservo.so into the jni folder for inclusion in the build - // TODO: pass/detect target architecture - { - let source = &args.target_path.join("libservo.so"); - let target_dir = &directory.join("jni").join("armeabi"); - let _ = DirBuilder::new().recursive(true).create(target_dir); - let target = target_dir.join("libmain.so"); - println!("Copying the file {:?} to {:?}", source, target); - fs::copy(source, target).unwrap(); - } - - let ndkcmd = Command::new(ndk_path.join("ndk-build")) - .arg("-B") - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .current_dir(directory.clone()) - .status(); - if ndkcmd.is_err() || ndkcmd.unwrap().code().unwrap() != 0 { - println!("Error while executing program `ndk-build`, or missing program."); - process::exit(1); - } - - // Copy the additional native libs into the libs directory. - for (name, path) in native_shared_libs.iter() { - let target = &directory.join("libs").join("armeabi").join(name); - println!("Copying the file {:?} to {:?}", name, target); - fs::copy(path, target).unwrap(); - } - - // Copy over the resources - let cpcmd = Command::new("cp") - .arg("-R") - .arg(&resdir) - .arg(&directory.join("assets")) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .current_dir(directory.clone()) - .status(); - if cpcmd.is_err() || cpcmd.unwrap().code().unwrap() != 0 { - println!("Error while copying files from the resources dir to the assets dir"); - process::exit(1); - } - - // Update the project - let androidcmd = Command::new(sdk_path.join("tools").join("android")) - .arg("update") - .arg("project") - .arg("--name") - .arg("Servo") - .arg("--target") - .arg(&android_platform) - .arg("--path") - .arg(".") - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .current_dir(directory.clone()) - .status(); - if androidcmd.is_err() || androidcmd.unwrap().code().unwrap() != 0 { - println!("Error while updating the project with the android command"); - process::exit(1); - } - - // Build the APK - let mut antcmd = Command::new("ant"); - if debug { - antcmd.arg("debug"); - } else { - antcmd.arg("release"); - } - let antresult = antcmd.stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .current_dir(directory.clone()) - .status(); - if antresult.is_err() || antresult.unwrap().code().unwrap() != 0 { - println!("Error while executing program `ant`, or missing program."); - process::exit(1); - } - - // Copying apk file to the requested output - // Release builds also need to be signed. For now, we use a simple debug - // signing key. - if debug { - fs::copy(&directory.join("bin").join("Servo-debug.apk"), &args.output).unwrap(); - } else { - let keystore_dir = env::home_dir().expect("Please have a home directory"); - let keystore_dir = Path::new(&keystore_dir).join(".keystore"); - let keytoolcmd = Command::new("keytool") - .arg("-list") - .arg("-storepass") - .arg("android") - .arg("-alias") - .arg("androiddebugkey") - .arg("-keystore") - .arg(&keystore_dir) - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .current_dir(directory.clone()) - .status(); - if keytoolcmd.is_err() || keytoolcmd.unwrap().code().unwrap() != 0 { - let keytoolcreatecmd = Command::new("keytool") - .arg("-genkeypair") - .arg("-keystore") - .arg(&keystore_dir) - .arg("-storepass") - .arg("android") - .arg("-alias") - .arg("androiddebugkey") - .arg("-keypass") - .arg("android") - .arg("-dname") - .arg("CN=Android Debug,O=Android,C=US") - .arg("-keyalg") - .arg("RSA") - .arg("-validity") - .arg("365") - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .current_dir(directory.clone()) - .status(); - if keytoolcreatecmd.is_err() || keytoolcreatecmd.unwrap().code().unwrap() != 0 { - println!("Error while using `keytool` to create the debug keystore."); - process::exit(1); - } - } - - let jarsigncmd = Command::new("jarsigner") - .arg("-digestalg") - .arg("SHA1") - .arg("-sigalg") - .arg("MD5withRSA") - .arg("-storepass") - .arg("android") - .arg("-keystore") - .arg(&keystore_dir) - .arg(&directory.join("bin").join("Servo-release-unsigned.apk")) - .arg("androiddebugkey") - .stdout(Stdio::inherit()) - .stderr(Stdio::inherit()) - .current_dir(directory.clone()) - .status(); - if jarsigncmd.is_err() || jarsigncmd.unwrap().code().unwrap() != 0 { - println!("Error while using `jarsign` to sign the APK."); - process::exit(1); - } - - fs::copy(&directory.join("bin").join("Servo-release-unsigned.apk"), - &args.output) - .unwrap(); - } - -} - -struct Args { - output: PathBuf, - root_path: PathBuf, - target_path: PathBuf, - shared_libraries: HashSet, -} - -fn parse_arguments() -> (Args, Vec) { - let mut result_output = None; - let mut result_root_path = None; - let mut result_target_path = None; - let mut result_shared_libraries = HashSet::new(); - let mut result_passthrough = Vec::new(); - - let args = env::args(); - let mut args = args.skip(1); - - loop { - let arg = match args.next() { - None => { - return (Args { - output: result_output.expect("Could not find -o argument"), - root_path: result_root_path.expect("Could not find -r enlistment root argument"), - target_path: result_target_path.expect("Could not find -t target path argument"), - shared_libraries: result_shared_libraries, - }, - result_passthrough) - }, - Some(arg) => arg, - }; - - match &*arg { - "-o" => { - result_output = Some(PathBuf::from(args.next().expect("-o must be followed by the output name"))); - }, - "-r" => { - result_root_path = Some(PathBuf::from(args.next() - .expect("-r must be followed by the enlistment root directory"))); - }, - "-t" => { - result_target_path = Some(PathBuf::from(args.next() - .expect("-t must be followed by the target output directory"))); - }, - "-l" => { - let name = args.next().expect("-l must be followed by a library name"); - result_shared_libraries.insert(vec!["lib", &name, ".so"].concat()); - - // Also pass these through. - result_passthrough.push(arg); - result_passthrough.push(name); - }, - _ => { - if arg.starts_with("-l") { - result_shared_libraries.insert(vec!["lib", &arg[2..], ".so"].concat()); - } - result_passthrough.push(arg) - }, - }; - } -} - -fn find_native_libs(args: &Args) -> HashMap { - let mut native_shared_libs: HashMap = HashMap::new(); - - // Add requested .so files - for dir_entry in WalkDir::new(&args.target_path) { - let dir_entry = dir_entry.unwrap(); - let path = dir_entry.path(); - - match (path.file_name(), path.extension()) { - (Some(file_name), Some(extension)) => { - let file_name = file_name.to_str().unwrap(); - - if file_name.starts_with("lib") && extension == "so" && args.shared_libraries.contains(file_name) { - println!("Adding the file {:?}", file_name); - native_shared_libs.insert(file_name.to_string(), path.to_path_buf().clone()); - break; - } - }, - _ => {}, - } - } - - native_shared_libs -}