diff --git a/LICENSE b/LICENSE
deleted file mode 100644
index 0a04128..0000000
--- a/LICENSE
+++ /dev/null
@@ -1,165 +0,0 @@
- GNU LESSER GENERAL PUBLIC LICENSE
- Version 3, 29 June 2007
-
- Copyright (C) 2007 Free Software Foundation, Inc.
- Everyone is permitted to copy and distribute verbatim copies
- of this license document, but changing it is not allowed.
-
-
- This version of the GNU Lesser General Public License incorporates
-the terms and conditions of version 3 of the GNU General Public
-License, supplemented by the additional permissions listed below.
-
- 0. Additional Definitions.
-
- As used herein, "this License" refers to version 3 of the GNU Lesser
-General Public License, and the "GNU GPL" refers to version 3 of the GNU
-General Public License.
-
- "The Library" refers to a covered work governed by this License,
-other than an Application or a Combined Work as defined below.
-
- An "Application" is any work that makes use of an interface provided
-by the Library, but which is not otherwise based on the Library.
-Defining a subclass of a class defined by the Library is deemed a mode
-of using an interface provided by the Library.
-
- A "Combined Work" is a work produced by combining or linking an
-Application with the Library. The particular version of the Library
-with which the Combined Work was made is also called the "Linked
-Version".
-
- The "Minimal Corresponding Source" for a Combined Work means the
-Corresponding Source for the Combined Work, excluding any source code
-for portions of the Combined Work that, considered in isolation, are
-based on the Application, and not on the Linked Version.
-
- The "Corresponding Application Code" for a Combined Work means the
-object code and/or source code for the Application, including any data
-and utility programs needed for reproducing the Combined Work from the
-Application, but excluding the System Libraries of the Combined Work.
-
- 1. Exception to Section 3 of the GNU GPL.
-
- You may convey a covered work under sections 3 and 4 of this License
-without being bound by section 3 of the GNU GPL.
-
- 2. Conveying Modified Versions.
-
- If you modify a copy of the Library, and, in your modifications, a
-facility refers to a function or data to be supplied by an Application
-that uses the facility (other than as an argument passed when the
-facility is invoked), then you may convey a copy of the modified
-version:
-
- a) under this License, provided that you make a good faith effort to
- ensure that, in the event an Application does not supply the
- function or data, the facility still operates, and performs
- whatever part of its purpose remains meaningful, or
-
- b) under the GNU GPL, with none of the additional permissions of
- this License applicable to that copy.
-
- 3. Object Code Incorporating Material from Library Header Files.
-
- The object code form of an Application may incorporate material from
-a header file that is part of the Library. You may convey such object
-code under terms of your choice, provided that, if the incorporated
-material is not limited to numerical parameters, data structure
-layouts and accessors, or small macros, inline functions and templates
-(ten or fewer lines in length), you do both of the following:
-
- a) Give prominent notice with each copy of the object code that the
- Library is used in it and that the Library and its use are
- covered by this License.
-
- b) Accompany the object code with a copy of the GNU GPL and this license
- document.
-
- 4. Combined Works.
-
- You may convey a Combined Work under terms of your choice that,
-taken together, effectively do not restrict modification of the
-portions of the Library contained in the Combined Work and reverse
-engineering for debugging such modifications, if you also do each of
-the following:
-
- a) Give prominent notice with each copy of the Combined Work that
- the Library is used in it and that the Library and its use are
- covered by this License.
-
- b) Accompany the Combined Work with a copy of the GNU GPL and this license
- document.
-
- c) For a Combined Work that displays copyright notices during
- execution, include the copyright notice for the Library among
- these notices, as well as a reference directing the user to the
- copies of the GNU GPL and this license document.
-
- d) Do one of the following:
-
- 0) Convey the Minimal Corresponding Source under the terms of this
- License, and the Corresponding Application Code in a form
- suitable for, and under terms that permit, the user to
- recombine or relink the Application with a modified version of
- the Linked Version to produce a modified Combined Work, in the
- manner specified by section 6 of the GNU GPL for conveying
- Corresponding Source.
-
- 1) Use a suitable shared library mechanism for linking with the
- Library. A suitable mechanism is one that (a) uses at run time
- a copy of the Library already present on the user's computer
- system, and (b) will operate properly with a modified version
- of the Library that is interface-compatible with the Linked
- Version.
-
- e) Provide Installation Information, but only if you would otherwise
- be required to provide such information under section 6 of the
- GNU GPL, and only to the extent that such information is
- necessary to install and execute a modified version of the
- Combined Work produced by recombining or relinking the
- Application with a modified version of the Linked Version. (If
- you use option 4d0, the Installation Information must accompany
- the Minimal Corresponding Source and Corresponding Application
- Code. If you use option 4d1, you must provide the Installation
- Information in the manner specified by section 6 of the GNU GPL
- for conveying Corresponding Source.)
-
- 5. Combined Libraries.
-
- You may place library facilities that are a work based on the
-Library side by side in a single library together with other library
-facilities that are not Applications and are not covered by this
-License, and convey such a combined library under terms of your
-choice, if you do both of the following:
-
- a) Accompany the combined library with a copy of the same work based
- on the Library, uncombined with any other library facilities,
- conveyed under the terms of this License.
-
- b) Give prominent notice with the combined library that part of it
- is a work based on the Library, and explaining where to find the
- accompanying uncombined form of the same work.
-
- 6. Revised Versions of the GNU Lesser General Public License.
-
- The Free Software Foundation may publish revised and/or new versions
-of the GNU Lesser General Public License from time to time. Such new
-versions will be similar in spirit to the present version, but may
-differ in detail to address new problems or concerns.
-
- Each version is given a distinguishing version number. If the
-Library as you received it specifies that a certain numbered version
-of the GNU Lesser General Public License "or any later version"
-applies to it, you have the option of following the terms and
-conditions either of that published version or of any later version
-published by the Free Software Foundation. If the Library as you
-received it does not specify a version number of the GNU Lesser
-General Public License, you may choose any version of the GNU Lesser
-General Public License ever published by the Free Software Foundation.
-
- If the Library as you received it specifies that a proxy can decide
-whether future versions of the GNU Lesser General Public License shall
-apply, that proxy's public statement of acceptance of any version is
-permanent authorization for you to choose that version for the
-Library.
diff --git a/README.md b/README.md
deleted file mode 100644
index cd36e2b..0000000
--- a/README.md
+++ /dev/null
@@ -1,52 +0,0 @@
-
-
-# LibreSpeed Android Template
-The Speedtest Android template allows you to configure and distribute an Android app that performs a speedtest using your existing [LibreSpeed](https://github.com/librespeed/speedtest) server(s).
-
-The template is easy to configure, customize and distribute.
-
-## Try it
-
-[](https://f-droid.org/packages/com.dosse.speedtest/)
-
-Alternatively, you can [download a demo APK](https://downloads.fdossena.com/geth.php?r=speedtest-android-apk)
-
-## Compatibility
-Android 4.0.3 and up (SDK 15), all architectures.
-
-## Features
-* Download
-* Upload
-* Ping
-* Jitter
-* IP Address, ISP, distance from server (optional)
-* Telemetry (optional)
-* Results sharing (optional)
-* Multiple Points of Test (optional)
-
-
-
-## Server requirements
-One or more servers with [LibreSpeed](https://github.com/librespeed/speedtest) installed.
-
-## Donate
-[](https://liberapay.com/fdossena/donate)
-[Donate with PayPal](https://www.paypal.me/sineisochronic)
-
-## License
-Copyright (C) 2019 Federico Dossena
-
-This program is free software: you can redistribute it and/or modify
-it under the terms of the GNU Lesser General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-This program is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU Lesser General Public License
-along with this program. If not, see .
diff --git a/Speedtest-Android/.gitignore b/Speedtest-Android/.gitignore
deleted file mode 100644
index 603b140..0000000
--- a/Speedtest-Android/.gitignore
+++ /dev/null
@@ -1,14 +0,0 @@
-*.iml
-.gradle
-/local.properties
-/.idea/caches
-/.idea/libraries
-/.idea/modules.xml
-/.idea/workspace.xml
-/.idea/navEditor.xml
-/.idea/assetWizardSettings.xml
-.DS_Store
-/build
-/captures
-.externalNativeBuild
-.cxx
diff --git a/Speedtest-Android/.idea/codeStyles/Project.xml b/Speedtest-Android/.idea/codeStyles/Project.xml
deleted file mode 100644
index 681f41a..0000000
--- a/Speedtest-Android/.idea/codeStyles/Project.xml
+++ /dev/null
@@ -1,116 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
- xmlns:android
-
- ^$
-
-
-
-
-
-
-
-
- xmlns:.*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*:id
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- .*:name
-
- http://schemas.android.com/apk/res/android
-
-
-
-
-
-
-
-
- name
-
- ^$
-
-
-
-
-
-
-
-
- style
-
- ^$
-
-
-
-
-
-
-
-
- .*
-
- ^$
-
-
- BY_NAME
-
-
-
-
-
-
- .*
-
- http://schemas.android.com/apk/res/android
-
-
- ANDROID_ATTRIBUTE_ORDER
-
-
-
-
-
-
- .*
-
- .*
-
-
- BY_NAME
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Speedtest-Android/.idea/gradle.xml b/Speedtest-Android/.idea/gradle.xml
deleted file mode 100644
index d291b3d..0000000
--- a/Speedtest-Android/.idea/gradle.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Speedtest-Android/.idea/misc.xml b/Speedtest-Android/.idea/misc.xml
deleted file mode 100644
index 37a7509..0000000
--- a/Speedtest-Android/.idea/misc.xml
+++ /dev/null
@@ -1,9 +0,0 @@
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Speedtest-Android/.idea/runConfigurations.xml b/Speedtest-Android/.idea/runConfigurations.xml
deleted file mode 100644
index 7f68460..0000000
--- a/Speedtest-Android/.idea/runConfigurations.xml
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Speedtest-Android/.idea/vcs.xml b/Speedtest-Android/.idea/vcs.xml
deleted file mode 100644
index 6c0b863..0000000
--- a/Speedtest-Android/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Speedtest-Android/app/.gitignore b/Speedtest-Android/app/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/Speedtest-Android/app/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/Speedtest-Android/app/build.gradle b/Speedtest-Android/app/build.gradle
deleted file mode 100644
index b664645..0000000
--- a/Speedtest-Android/app/build.gradle
+++ /dev/null
@@ -1,24 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 28
- buildToolsVersion "29.0.0"
- defaultConfig {
- applicationId "your.name.here.speedtest"
- minSdkVersion 15
- targetSdkVersion 28
- versionCode 5
- versionName '1.1.3'
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
- }
- }
-}
-
-dependencies {
- implementation fileTree(dir: 'libs', include: ['*.jar'])
-}
diff --git a/Speedtest-Android/app/proguard-rules.pro b/Speedtest-Android/app/proguard-rules.pro
deleted file mode 100644
index f1b4245..0000000
--- a/Speedtest-Android/app/proguard-rules.pro
+++ /dev/null
@@ -1,21 +0,0 @@
-# Add project specific ProGuard rules here.
-# You can control the set of applied configuration files using the
-# proguardFiles setting in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
diff --git a/Speedtest-Android/app/src/main/AndroidManifest.xml b/Speedtest-Android/app/src/main/AndroidManifest.xml
deleted file mode 100644
index 685d61d..0000000
--- a/Speedtest-Android/app/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Speedtest-Android/app/src/main/assets/ServerList.json b/Speedtest-Android/app/src/main/assets/ServerList.json
deleted file mode 100644
index 7719f0f..0000000
--- a/Speedtest-Android/app/src/main/assets/ServerList.json
+++ /dev/null
@@ -1,10 +0,0 @@
-[
- {
- "name":"Helsinki, Finland",
- "server":"//fi.openspeed.org",
- "dlURL":"garbage.php",
- "ulURL":"empty.php",
- "pingURL":"empty.php",
- "getIpURL":"getIP.php"
- }
-]
diff --git a/Speedtest-Android/app/src/main/assets/SpeedtestConfig.json b/Speedtest-Android/app/src/main/assets/SpeedtestConfig.json
deleted file mode 100644
index 0967ef4..0000000
--- a/Speedtest-Android/app/src/main/assets/SpeedtestConfig.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
diff --git a/Speedtest-Android/app/src/main/assets/TelemetryConfig.json b/Speedtest-Android/app/src/main/assets/TelemetryConfig.json
deleted file mode 100644
index 0967ef4..0000000
--- a/Speedtest-Android/app/src/main/assets/TelemetryConfig.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
diff --git a/Speedtest-Android/app/src/main/assets/privacy_en.html b/Speedtest-Android/app/src/main/assets/privacy_en.html
deleted file mode 100644
index 053c477..0000000
--- a/Speedtest-Android/app/src/main/assets/privacy_en.html
+++ /dev/null
@@ -1,54 +0,0 @@
-
-
-
-
-
-
-
-
Privacy Policy
-
This Speedtest app is configured with telemetry enabled.
-
What data we collect
-
- At the end of the test, the following data is collected and stored:
-
-
Test ID
-
Time of testing
-
Test results (download and upload speed, ping and jitter)
-
IP address
-
ISP information
-
Approximate location (inferred from IP address, not GPS)
-
Device manufacturer, model, Android version, and language
-
Test log (contains no personal information)
-
-
-
How we use the data
-
- Data collected through this service is used to:
-
-
Allow sharing of test results (sharable image for forums, etc.)
-
To improve the service offered to you (for instance, to detect problems on our side)
-
- No personal information is disclosed to third parties.
-
-
Your consent
-
- By starting the test, you consent to the terms of this privacy policy.
-
-
Data removal
-
- If you want to have your information deleted, you need to provide either the ID of the test or your IP address. This is the only way to identify your data, without this information we won't be able to comply with your request.
-
-
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/Speedtest.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/Speedtest.java
deleted file mode 100644
index dae6064..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/Speedtest.java
+++ /dev/null
@@ -1,203 +0,0 @@
-package com.fdossena.speedtest.core;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-
-import com.fdossena.speedtest.core.config.SpeedtestConfig;
-import com.fdossena.speedtest.core.config.TelemetryConfig;
-import com.fdossena.speedtest.core.serverSelector.ServerSelector;
-import com.fdossena.speedtest.core.serverSelector.TestPoint;
-import com.fdossena.speedtest.core.worker.SpeedtestWorker;
-
-public class Speedtest {
- private ArrayList servers=new ArrayList<>();
- private TestPoint selectedServer=null;
- private SpeedtestConfig config=new SpeedtestConfig();
- private TelemetryConfig telemetryConfig=new TelemetryConfig();
- private int state=0; //0=configs, 1=test points, 2=server selection, 3=ready, 4=testing, 5=finished
-
- private Object mutex=new Object();
-
- private String originalExtra="";
-
- public Speedtest(){
-
- }
-
- public void setSpeedtestConfig(SpeedtestConfig c){
- synchronized (mutex){
- if(state!=0) throw new IllegalStateException("Cannot change config at this moment");
- config=c.clone();
- String extra=config.getTelemetry_extra();
- if(extra!=null&&!extra.isEmpty()) originalExtra=extra;
- }
- }
-
- public void setTelemetryConfig(TelemetryConfig c){
- synchronized (mutex) {
- if (state != 0) throw new IllegalStateException("Cannot change config at this moment");
- telemetryConfig = c.clone();
- }
- }
-
- public void addTestPoint(TestPoint t){
- synchronized (mutex) {
- if (state == 0) state = 1;
- if (state > 1) throw new IllegalStateException("Cannot add test points at this moment");
- servers.add(t);
- }
- }
-
- public void addTestPoints(TestPoint[] s){
- synchronized (mutex) {
- for (TestPoint t : s) addTestPoint(t);
- }
- }
-
- public void addTestPoint(JSONObject json){
- synchronized (mutex) {
- addTestPoint(new TestPoint(json));
- }
- }
-
- public void addTestPoints(JSONArray json){
- synchronized (mutex) {
- for (int i = 0; i < json.length(); i++)
- try {
- addTestPoint(json.getJSONObject(i));
- } catch (JSONException t) {
- }
- }
- }
-
- public TestPoint[] getTestPoints(){
- synchronized (mutex) {
- return servers.toArray(new TestPoint[0]);
- }
- }
-
- private ServerSelector ss=null;
- public void selectServer(final ServerSelectedHandler callback){
- synchronized (mutex) {
- if (state == 0) throw new IllegalStateException("No test points added");
- if (state == 2) throw new IllegalStateException("Server selection is in progress");
- if (state > 2) throw new IllegalStateException("Server already selected");
- state = 2;
- ss = new ServerSelector(getTestPoints(), config.getPing_connectTimeout()) {
- @Override
- public void onServerSelected(TestPoint server) {
- selectedServer = server;
- synchronized (mutex) {
- if (server != null) state = 3; else state = 1;
- }
- callback.onServerSelected(server);
- }
- };
- ss.start();
- }
- }
-
- public void setSelectedServer(TestPoint t){
- synchronized (mutex) {
- if (state == 2) throw new IllegalStateException("Server selection is in progress");
- if (t == null) throw new IllegalArgumentException("t is null");
- selectedServer = t;
- state = 3;
- }
- }
-
- private SpeedtestWorker st=null;
- public void start(final SpeedtestHandler callback){
- synchronized (mutex) {
- if (state < 3) throw new IllegalStateException("Server hasn't been selected yet");
- if (state == 4) throw new IllegalStateException("Test already running");
- state = 4;
- try {
- JSONObject extra = new JSONObject();
- if (originalExtra != null && !originalExtra.isEmpty())
- extra.put("extra", originalExtra);
- extra.put("server", selectedServer.getName());
- config.setTelemetry_extra(extra.toString());
- } catch (Throwable t) {
- }
- st = new SpeedtestWorker(selectedServer, config, telemetryConfig) {
- @Override
- public void onDownloadUpdate(double dl, double progress) {
- callback.onDownloadUpdate(dl, progress);
- }
-
- @Override
- public void onUploadUpdate(double ul, double progress) {
- callback.onUploadUpdate(ul, progress);
- }
-
- @Override
- public void onPingJitterUpdate(double ping, double jitter, double progress) {
- callback.onPingJitterUpdate(ping, jitter, progress);
- }
-
- @Override
- public void onIPInfoUpdate(String ipInfo) {
- callback.onIPInfoUpdate(ipInfo);
- }
-
- @Override
- public void onTestIDReceived(String id) {
- String shareURL=prepareShareURL(telemetryConfig);
- if(shareURL!=null) shareURL=String.format(shareURL,id);
- callback.onTestIDReceived(id,shareURL);
- }
-
- @Override
- public void onEnd() {
- synchronized (mutex) {
- state = 5;
- }
- callback.onEnd();
- }
-
- @Override
- public void onCriticalFailure(String err) {
- synchronized (mutex) {
- state = 5;
- }
- callback.onCriticalFailure(err);
- }
- };
- }
- }
-
- private String prepareShareURL(TelemetryConfig c){
- if(c==null) return null;
- String server=c.getServer(), shareURL=c.getShareURL();
- if(server==null||server.isEmpty()||shareURL==null||shareURL.isEmpty()) return null;
- if(!server.endsWith("/")) server=server+"/";
- while(shareURL.startsWith("/")) shareURL=shareURL.substring(1);
- if(server.startsWith("//")) server="https:"+server;
- return server+shareURL;
- }
-
- public void abort(){
- synchronized (mutex) {
- if (state == 2) ss.stopASAP();
- if (state == 4) st.abort();
- state = 5;
- }
- }
-
- public static abstract class ServerSelectedHandler{
- public abstract void onServerSelected(TestPoint server);
- }
- public static abstract class SpeedtestHandler{
- public abstract void onDownloadUpdate(double dl, double progress);
- public abstract void onUploadUpdate(double ul, double progress);
- public abstract void onPingJitterUpdate(double ping, double jitter, double progress);
- public abstract void onIPInfoUpdate(String ipInfo);
- public abstract void onTestIDReceived(String id, String shareURL);
- public abstract void onEnd();
- public abstract void onCriticalFailure(String err);
- }
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/base/Connection.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/base/Connection.java
deleted file mode 100644
index 132be25..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/base/Connection.java
+++ /dev/null
@@ -1,237 +0,0 @@
-package com.fdossena.speedtest.core.base;
-
-import android.os.Build;
-
-import java.io.InputStream;
-import java.io.InputStreamReader;
-import java.io.OutputStream;
-import java.io.PrintStream;
-import java.net.InetSocketAddress;
-import java.net.Socket;
-import java.net.URL;
-import java.util.HashMap;
-import java.util.Locale;
-
-import javax.net.SocketFactory;
-import javax.net.ssl.SSLSocketFactory;
-
-public class Connection {
- private Socket socket;
- private String host; private int port;
- private int mode=MODE_NOT_SET;
- private static final int MODE_NOT_SET=0, MODE_HTTP=1, MODE_HTTPS=2;
-
- private static final String USER_AGENT="Speedtest-Android/1.1.3 (SDK "+Build.VERSION.SDK_INT+"; "+Build.PRODUCT+"; Android "+Build.VERSION.RELEASE+")",
- LOCALE= Build.VERSION.SDK_INT>=21?Locale.getDefault().toLanguageTag():null;
-
- public Connection(String url, int connectTimeout, int soTimeout, int recvBuffer, int sendBuffer){
- boolean tryHTTP=false, tryHTTPS=false;
- Locale.getDefault().toString();
- if(url.startsWith("http://")){
- tryHTTP=true;
- try{
- URL u=new URL(url);
- host=u.getHost();
- port=u.getPort();
- }catch(Throwable t){
- throw new IllegalArgumentException("Malformed URL (HTTP)");
- }
- }else if(url.startsWith("https://")){
- tryHTTPS=true;
- try{
- URL u=new URL(url);
- host=u.getHost();
- port=u.getPort();
- }catch(Throwable t){
- throw new IllegalArgumentException("Malformed URL (HTTPS)");
- }
- }else if(url.startsWith("//")){
- tryHTTP=true;
- tryHTTPS=true;
- try{
- URL u=new URL("http:"+url);
- host=u.getHost();
- port=u.getPort();
- }catch(Throwable t){
- throw new IllegalArgumentException("Malformed URL (HTTP/HTTPS)");
- }
- }else{
- throw new IllegalArgumentException("Malformed URL (Unknown or unspecified protocol)");
- }
- try{
- if(tryHTTPS){
- SocketFactory factory = SSLSocketFactory.getDefault();
- socket=factory.createSocket();
- if(connectTimeout>0){
- socket.connect(new InetSocketAddress(host, port==-1?443:port),connectTimeout);
- }else{
- socket.connect(new InetSocketAddress(host, port==-1?443:port));
- }
- mode=MODE_HTTPS;
- }
- }catch(Throwable t){}
- try{
- if(tryHTTP){
- SocketFactory factory = SocketFactory.getDefault();
- socket=factory.createSocket();
- if(connectTimeout>0) {
- socket.connect(new InetSocketAddress(host, port == -1 ? 80 : port), connectTimeout);
- }else{
- socket.connect(new InetSocketAddress(host, port == -1 ? 80 : port));
- }
- mode=MODE_HTTP;
- }
- }catch(Throwable t){}
- if(mode==MODE_NOT_SET) throw new IllegalStateException("Failed to connect");
- if(soTimeout>0) {
- try {
- socket.setSoTimeout(soTimeout);
- } catch(Throwable t){}
- }
- if(recvBuffer>0){
- try{
- socket.setReceiveBufferSize(recvBuffer);
- }catch(Throwable t){}
- }
- if(sendBuffer>0){
- try{
- socket.setSendBufferSize(sendBuffer);
- }catch(Throwable t){}
- }
- }
-
- private static final int DEFAULT_CONNECT_TIMEOUT=2000, DEFAULT_SO_TIMEOUT=5000;
- public Connection(String url){
- this(url,DEFAULT_CONNECT_TIMEOUT,DEFAULT_SO_TIMEOUT,-1,-1);
- }
-
- public InputStream getInputStream(){
- try{
- return socket.getInputStream();
- }catch (Throwable t){
- return null;
- }
- }
-
- public OutputStream getOutputStream(){
- try{
- return socket.getOutputStream();
- }catch (Throwable t){
- return null;
- }
- }
-
- private PrintStream ps=null;
- public PrintStream getPrintStream(){
- if(ps==null){
- try{
- ps=new PrintStream(getOutputStream(),false,"utf-8");
- }catch(Throwable t){
- ps=null;
- }
- }
- return ps;
- }
- private InputStreamReader isr=null;
- public InputStreamReader getInputStreamReader(){
- if(isr==null){
- try{
- isr=new InputStreamReader(getInputStream(),"utf-8");
- }catch(Throwable t){
- isr=null;
- }
- }
- return isr;
- }
-
- public void GET(String path, boolean keepAlive) throws Exception{
- try{
- if(!path.startsWith("/")) path="/"+path;
- PrintStream ps=getPrintStream();
- ps.print("GET "+path+" HTTP/1.1\r\n");
- ps.print("Host: "+host+"\r\n");
- ps.print("User-Agent: "+USER_AGENT);
- ps.print("Connection: "+(keepAlive?"keep-alive":"close")+"\r\n");
- ps.print("Accept-Encoding: identity\r\n");
- if(LOCALE!=null) ps.print("Accept-Language: "+LOCALE+"\r\n");
- ps.print("\r\n");
- ps.flush();
- }catch (Throwable t){
- throw new Exception("Failed to send GET request");
- }
- }
-
- public void POST(String path, boolean keepAlive, String contentType, long contentLength) throws Exception{
- try{
- if(!path.startsWith("/")) path="/"+path;
- PrintStream ps=getPrintStream();
- ps.print("POST "+path+" HTTP/1.1\r\n");
- ps.print("Host: "+host+"\r\n");
- ps.print("User-Agent: "+USER_AGENT+"\r\n");
- ps.print("Connection: "+(keepAlive?"keep-alive":"close")+"\r\n");
- ps.print("Accept-Encoding: identity\r\n");
- if(LOCALE!=null) ps.print("Accept-Language: "+LOCALE+"\r\n");
- if(contentType!=null) ps.print("Content-Type: "+contentType+"\r\n");
- ps.print("Content-Encoding: identity\r\n");
- if(contentLength>=0) ps.print("Content-Length: "+contentLength+"\r\n");
- ps.print("\r\n");
- ps.flush();
- }catch (Throwable t){
- throw new Exception("Failed to send POST request");
- }
- }
-
- public String readLineUnbuffered(){
- try {
- InputStreamReader in = getInputStreamReader();
- StringBuilder sb=new StringBuilder();
- while(true){
- int c=in.read();
- if(c==-1) break;
- sb.append((char)c);
- if(c=='\n') break;
- }
- return sb.toString();
- }catch(Throwable t){
- return null;
- }
- }
-
- public HashMap parseResponseHeaders() throws Exception{
- try{
- HashMap ret=new HashMap<>();
- String s=readLineUnbuffered();
- if(!s.contains("200 OK")) throw new Exception("Did not receive an HTTP 200 ("+s.trim()+")");
- while(true){
- s=readLineUnbuffered();
- if(s.trim().isEmpty()) break;
- if(s.contains(":")){
- ret.put(s.substring(0,s.indexOf(":")).trim().toLowerCase(),s.substring(s.indexOf(":")+1).trim());
- }
- }
- return ret;
- }catch(Throwable t){
- throw new Exception("Failed to get response headers ("+t+")");
- }
- }
-
- public void close(){
- try{
- socket.close();
- }catch(Throwable t){}
- socket=null;
- }
-
- public String getHost() {
- return host;
- }
-
- public int getPort() {
- return port;
- }
-
- public int getMode() {
- return mode;
- }
-
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/base/Utils.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/base/Utils.java
deleted file mode 100644
index 51d5cd2..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/base/Utils.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.fdossena.speedtest.core.base;
-
-import java.net.URLEncoder;
-
-public class Utils {
- public static String urlEncode(String s){
- try{return URLEncoder.encode(s, "utf-8");}catch(Throwable t){return null;}
- }
- public static void sleep(long ms){
- try{Thread.sleep(ms);}catch (Throwable t){}
- }
- public static void sleep(long ms, int ns){
- try{Thread.sleep(ms,ns);}catch (Throwable t){}
- }
- public static String url_sep(String url){
- if(url.contains("?")) return "&"; else return "?";
- }
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/config/SpeedtestConfig.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/config/SpeedtestConfig.java
deleted file mode 100644
index c7d7584..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/config/SpeedtestConfig.java
+++ /dev/null
@@ -1,415 +0,0 @@
-package com.fdossena.speedtest.core.config;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-public class SpeedtestConfig {
- private int dl_ckSize=100, ul_ckSize=20;
- private int dl_parallelStreams=3, ul_parallelStreams=3;
- private int dl_streamDelay=300, ul_streamDelay=300;
- private double dl_graceTime=1.5, ul_graceTime=1.5;
- private int dl_connectTimeout=5000, dl_soTimeout=10000, ul_connectTimeout=5000, ul_soTimeout=10000, ping_connectTimeout=2000, ping_soTimeout=5000;
- private int dl_recvBuffer=-1, dl_sendBuffer=-1, ul_recvBuffer=-1, ul_sendBuffer=16384, ping_recvBuffer=-1, ping_sendBuffer=-1;
- private String errorHandlingMode=ONERROR_ATTEMPT_RESTART;
- public static final String ONERROR_FAIL="fail", ONERROR_ATTEMPT_RESTART="attempt-restart", ONERROR_MUST_RESTART="must-restart";
- private int time_dl_max=15, time_ul_max=15;
- private boolean time_auto=true;
- private int count_ping=10;
- private String telemetry_extra="";
- private double overheadCompensationFactor=1.06;
- private boolean getIP_isp=true;
- private String getIP_distance=DISTANCE_KM;
- public static final String DISTANCE_NO="no", DISTANCE_MILES="mi", DISTANCE_KM="km";
- private boolean useMebibits=false;
- private String test_order="IP_D_U";
-
- private void check(){
- if(dl_ckSize<1) throw new IllegalArgumentException("dl_ckSize must be at least 1");
- if(ul_ckSize<1) throw new IllegalArgumentException("ul_ckSize must be at least 1");
- if(dl_parallelStreams<1) throw new IllegalArgumentException("dl_parallelStreams must be at least 1");
- if(ul_parallelStreams<1) throw new IllegalArgumentException("ul_parallelStreams must be at least 1");
- if(dl_streamDelay<0) throw new IllegalArgumentException("dl_streamDelay must be at least 0");
- if(ul_streamDelay<0) throw new IllegalArgumentException("ul_streamDelay must be at least 0");
- if(dl_graceTime<0) throw new IllegalArgumentException("dl_graceTime must be at least 0");
- if(ul_graceTime<0) throw new IllegalArgumentException("ul_graceTime must be at least 0");
- if(!(errorHandlingMode.equals(ONERROR_FAIL)||errorHandlingMode.equals(ONERROR_ATTEMPT_RESTART)||errorHandlingMode.equals(ONERROR_MUST_RESTART))) throw new IllegalArgumentException("errorHandlingMode must be fail, attempt-restart, or must-restart");
- if(time_dl_max<1) throw new IllegalArgumentException("time_dl_max must be at least 1");
- if(time_ul_max<1) throw new IllegalArgumentException("time_ul_max must be at least 1");
- if(count_ping<1) throw new IllegalArgumentException("count_ping must be at least 1");
- if(overheadCompensationFactor<1) throw new IllegalArgumentException("overheadCompensationFactor must be at least 1");
- if(!(getIP_distance.equals(DISTANCE_NO)||getIP_distance.equals(DISTANCE_KM)||getIP_distance.equals(DISTANCE_MILES))) throw new IllegalArgumentException("getIP_distance must be no, km or miles");
- for(char c:test_order.toCharArray()){
- if(!(c=='I'||c=='P'||c=='D'||c=='U'||c=='_')) throw new IllegalArgumentException("test_order can only contain characters I, P, D, U, _");
- }
- }
-
- public SpeedtestConfig(){
- check();
- }
-
- public SpeedtestConfig(int dl_ckSize, int ul_ckSize, int dl_parallelStreams, int ul_parallelStreams, int dl_streamDelay, int ul_streamDelay, double dl_graceTime, double ul_graceTime, int dl_connectTimeout, int dl_soTimeout, int ul_connectTimeout, int ul_soTimeout, int ping_connectTimeout, int ping_soTimeout, int dl_recvBuffer, int dl_sendBuffer, int ul_recvBuffer, int ul_sendBuffer, int ping_recvBuffer, int ping_sendBuffer, String errorHandlingMode, int time_dl_max, int time_ul_max, boolean time_auto, int count_ping, String telemetry_extra, double overheadCompensationFactor, boolean getIP_isp, String getIP_distance, boolean useMebibits, String test_order) {
- this.dl_ckSize = dl_ckSize;
- this.ul_ckSize = ul_ckSize;
- this.dl_parallelStreams = dl_parallelStreams;
- this.ul_parallelStreams = ul_parallelStreams;
- this.dl_streamDelay = dl_streamDelay;
- this.ul_streamDelay = ul_streamDelay;
- this.dl_graceTime = dl_graceTime;
- this.ul_graceTime = ul_graceTime;
- this.dl_connectTimeout = dl_connectTimeout;
- this.dl_soTimeout = dl_soTimeout;
- this.ul_connectTimeout = ul_connectTimeout;
- this.ul_soTimeout = ul_soTimeout;
- this.ping_connectTimeout = ping_connectTimeout;
- this.ping_soTimeout = ping_soTimeout;
- this.dl_recvBuffer = dl_recvBuffer;
- this.dl_sendBuffer = dl_sendBuffer;
- this.ul_recvBuffer = ul_recvBuffer;
- this.ul_sendBuffer = ul_sendBuffer;
- this.ping_recvBuffer = ping_recvBuffer;
- this.ping_sendBuffer = ping_sendBuffer;
- this.errorHandlingMode = errorHandlingMode;
- this.time_dl_max = time_dl_max;
- this.time_ul_max = time_ul_max;
- this.time_auto = time_auto;
- this.count_ping = count_ping;
- this.telemetry_extra = telemetry_extra;
- this.overheadCompensationFactor = overheadCompensationFactor;
- this.getIP_isp = getIP_isp;
- this.getIP_distance = getIP_distance;
- this.useMebibits = useMebibits;
- this.test_order = test_order;
- check();
- }
-
- public SpeedtestConfig(JSONObject json){
- try {
- if (json.has("dl_ckSize")) this.dl_ckSize = json.getInt("dl_ckSize");
- if (json.has("ul_ckSize")) this.ul_ckSize = json.getInt("ul_ckSize");
- if (json.has("dl_parallelStreams"))
- this.dl_parallelStreams = json.getInt("dl_parallelStreams");
- if (json.has("ul_parallelStreams"))
- this.ul_parallelStreams = json.getInt("ul_parallelStreams");
- if (json.has("dl_streamDelay")) this.dl_streamDelay = json.getInt("dl_streamDelay");
- if (json.has("ul_streamDelay")) this.ul_streamDelay = json.getInt("ul_streamDelay");
- if (json.has("dl_graceTime")) this.dl_graceTime = json.getDouble("dl_graceTime");
- if (json.has("ul_graceTime")) this.ul_graceTime = json.getDouble("ul_graceTime");
- if (json.has("dl_connectTimeout"))
- this.dl_connectTimeout = json.getInt("dl_connectTimeout");
- if (json.has("ul_connectTimeout"))
- this.ul_connectTimeout = json.getInt("ul_connectTimeout");
- if (json.has("ping_connectTimeout"))
- this.ping_connectTimeout = json.getInt("ping_connectTimeout");
- if (json.has("dl_soTimeout")) this.dl_soTimeout = json.getInt("dl_soTimeout");
- if (json.has("ul_soTimeout")) this.ul_soTimeout = json.getInt("ul_soTimeout");
- if (json.has("ping_soTimeout")) this.ping_soTimeout = json.getInt("ping_soTimeout");
- if (json.has("dl_recvBuffer")) this.dl_recvBuffer = json.getInt("dl_recvBuffer");
- if (json.has("ul_recvBuffer")) this.ul_recvBuffer = json.getInt("ul_recvBuffer");
- if (json.has("ping_recvBuffer")) this.ping_recvBuffer = json.getInt("ping_recvBuffer");
- if (json.has("dl_sendBuffer")) this.dl_sendBuffer = json.getInt("dl_sendBuffer");
- if (json.has("ul_sendBuffer")) this.ul_sendBuffer = json.getInt("ul_sendBuffer");
- if (json.has("ping_sendBuffer")) this.ping_sendBuffer = json.getInt("ping_sendBuffer");
- if (json.has("errorHandlingMode"))
- this.errorHandlingMode = json.getString("errorHandlingMode");
- if (json.has("time_dl_max")) this.time_dl_max = json.getInt("time_dl_max");
- if (json.has("time_ul_max")) this.time_ul_max = json.getInt("time_ul_max");
- if (json.has("count_ping")) this.count_ping = json.getInt("count_ping");
- if (json.has("telemetry_extra"))
- this.telemetry_extra = json.getString("telemetry_extra");
- if (json.has("overheadCompensationFactor"))
- this.overheadCompensationFactor = json.getDouble("overheadCompensationFactor");
- if (json.has("getIP_isp")) this.getIP_isp = json.getBoolean("getIP_isp");
- if (json.has("getIP_distance")) this.getIP_distance = json.getString("getIP_distance");
- if (json.has("test_order")) this.test_order = json.getString("test_order");
- if (json.has("useMebibits")) this.useMebibits = json.getBoolean("useMebibits");
- check();
- }catch(JSONException t){
- throw new IllegalArgumentException("Invalid JSON ("+t.toString()+")");
- }
- }
-
- public int getDl_ckSize() {
- return dl_ckSize;
- }
-
- public int getUl_ckSize() {
- return ul_ckSize;
- }
-
- public int getDl_parallelStreams() {
- return dl_parallelStreams;
- }
-
- public int getUl_parallelStreams() {
- return ul_parallelStreams;
- }
-
- public int getDl_streamDelay() {
- return dl_streamDelay;
- }
-
- public int getUl_streamDelay() {
- return ul_streamDelay;
- }
-
- public double getDl_graceTime() {
- return dl_graceTime;
- }
-
- public double getUl_graceTime() {
- return ul_graceTime;
- }
-
- public int getDl_connectTimeout() {
- return dl_connectTimeout;
- }
-
- public int getDl_soTimeout() {
- return dl_soTimeout;
- }
-
- public int getUl_connectTimeout() {
- return ul_connectTimeout;
- }
-
- public int getUl_soTimeout() {
- return ul_soTimeout;
- }
-
- public int getPing_connectTimeout() {
- return ping_connectTimeout;
- }
-
- public int getPing_soTimeout() {
- return ping_soTimeout;
- }
-
- public int getDl_recvBuffer() {
- return dl_recvBuffer;
- }
-
- public int getDl_sendBuffer() {
- return dl_sendBuffer;
- }
-
- public int getUl_recvBuffer() {
- return ul_recvBuffer;
- }
-
- public int getUl_sendBuffer() {
- return ul_sendBuffer;
- }
-
- public int getPing_recvBuffer() {
- return ping_recvBuffer;
- }
-
- public int getPing_sendBuffer() {
- return ping_sendBuffer;
- }
-
- public String getErrorHandlingMode() {
- return errorHandlingMode;
- }
-
- public int getTime_dl_max() {
- return time_dl_max;
- }
-
- public int getTime_ul_max() {
- return time_ul_max;
- }
-
- public boolean getTime_auto() {
- return time_auto;
- }
-
- public int getCount_ping() {
- return count_ping;
- }
-
- public String getTelemetry_extra() {
- return telemetry_extra;
- }
-
- public double getOverheadCompensationFactor() {
- return overheadCompensationFactor;
- }
-
- public boolean getGetIP_isp() {
- return getIP_isp;
- }
-
- public String getGetIP_distance() {
- return getIP_distance;
- }
-
- public boolean getUseMebibits() {
- return useMebibits;
- }
-
- public String getTest_order() {
- return test_order;
- }
-
- public void setDl_ckSize(int dl_ckSize) {
- if(dl_ckSize<1) throw new IllegalArgumentException("dl_ckSize must be at least 1");
- this.dl_ckSize = dl_ckSize;
- }
-
- public void setUl_ckSize(int ul_ckSize) {
- if(ul_ckSize<1) throw new IllegalArgumentException("ul_ckSize must be at least 1");
- this.ul_ckSize = ul_ckSize;
- }
-
- public void setDl_parallelStreams(int dl_parallelStreams) {
- if(dl_parallelStreams<1) throw new IllegalArgumentException("dl_parallelStreams must be at least 1");
- this.dl_parallelStreams = dl_parallelStreams;
- }
-
- public void setUl_parallelStreams(int ul_parallelStreams) {
- if(ul_parallelStreams<1) throw new IllegalArgumentException("ul_parallelStreams must be at least 1");
- this.ul_parallelStreams = ul_parallelStreams;
- }
-
- public void setDl_streamDelay(int dl_streamDelay) {
- if(dl_streamDelay<0) throw new IllegalArgumentException("dl_streamDelay must be at least 0");
- this.dl_streamDelay = dl_streamDelay;
- }
-
- public void setUl_streamDelay(int ul_streamDelay) {
- if(ul_streamDelay<0) throw new IllegalArgumentException("ul_streamDelay must be at least 0");
- this.ul_streamDelay = ul_streamDelay;
- }
-
- public void setDl_graceTime(double dl_graceTime) {
- if(dl_graceTime<0) throw new IllegalArgumentException("dl_graceTime must be at least 0");
- this.dl_graceTime = dl_graceTime;
- }
-
- public void setUl_graceTime(double ul_graceTime) {
- if(ul_graceTime<0) throw new IllegalArgumentException("ul_graceTime must be at least 0");
- this.ul_graceTime = ul_graceTime;
- }
-
- public void setDl_connectTimeout(int dl_connectTimeout) {
-
- this.dl_connectTimeout = dl_connectTimeout;
- }
-
- public void setDl_soTimeout(int dl_soTimeout) {
-
- this.dl_soTimeout = dl_soTimeout;
- }
-
- public void setUl_connectTimeout(int ul_connectTimeout) {
-
- this.ul_connectTimeout = ul_connectTimeout;
- }
-
- public void setUl_soTimeout(int ul_soTimeout) {
-
- this.ul_soTimeout = ul_soTimeout;
- }
-
- public void setPing_connectTimeout(int ping_connectTimeout) {
-
- this.ping_connectTimeout = ping_connectTimeout;
- }
-
- public void setPing_soTimeout(int ping_soTimeout) {
-
- this.ping_soTimeout = ping_soTimeout;
- }
-
- public void setDl_recvBuffer(int dl_recvBuffer) {
-
- this.dl_recvBuffer = dl_recvBuffer;
- }
-
- public void setDl_sendBuffer(int dl_sendBuffer) {
-
- this.dl_sendBuffer = dl_sendBuffer;
- }
-
- public void setUl_recvBuffer(int ul_recvBuffer) {
-
- this.ul_recvBuffer = ul_recvBuffer;
- }
-
- public void setUl_sendBuffer(int ul_sendBuffer) {
-
- this.ul_sendBuffer = ul_sendBuffer;
- }
-
- public void setPing_recvBuffer(int ping_recvBuffer) {
-
- this.ping_recvBuffer = ping_recvBuffer;
- }
-
- public void setPing_sendBuffer(int ping_sendBuffer) {
-
- this.ping_sendBuffer = ping_sendBuffer;
- }
-
- public void setErrorHandlingMode(String errorHandlingMode) {
- if(!(errorHandlingMode.equals(ONERROR_FAIL)||errorHandlingMode.equals(ONERROR_ATTEMPT_RESTART)||errorHandlingMode.equals(ONERROR_MUST_RESTART))) throw new IllegalArgumentException("errorHandlingMode must be fail, attempt-restart, or must-restart");
- this.errorHandlingMode = errorHandlingMode;
- }
-
- public void setTime_dl_max(int time_dl_max) {
- if(time_dl_max<1) throw new IllegalArgumentException("time_dl_max must be at least 1");
- this.time_dl_max = time_dl_max;
- }
-
- public void setTime_ul_max(int time_ul_max) {
- if(time_ul_max<1) throw new IllegalArgumentException("time_ul_max must be at least 1");
- this.time_ul_max = time_ul_max;
- }
-
- public void setTime_auto(boolean time_auto) {
-
- this.time_auto = time_auto;
- }
-
- public void setCount_ping(int count_ping) {
- if(count_ping<1) throw new IllegalArgumentException("count_ping must be at least 1");
- this.count_ping = count_ping;
- }
-
- public void setTelemetry_extra(String telemetry_extra) {
-
- this.telemetry_extra = telemetry_extra;
- }
-
- public void setOverheadCompensationFactor(double overheadCompensationFactor) {
- if(overheadCompensationFactor<1) throw new IllegalArgumentException("overheadCompensationFactor must be at least 1");
- this.overheadCompensationFactor = overheadCompensationFactor;
- }
-
- public void setGetIP_isp(boolean getIP_isp) {
-
- this.getIP_isp = getIP_isp;
- }
-
- public void setGetIP_distance(String getIP_distance) {
- if(!(getIP_distance.equals(DISTANCE_NO)||getIP_distance.equals(DISTANCE_KM)||getIP_distance.equals(DISTANCE_MILES))) throw new IllegalArgumentException("getIP_distance must be no, km or miles");
- this.getIP_distance = getIP_distance;
- }
-
- public void setUseMebibits(boolean useMebibits) {
-
- this.useMebibits = useMebibits;
- }
-
- public void setTest_order(String test_order) {
- for(char c:test_order.toCharArray()){
- if(!(c=='I'||c=='P'||c=='D'||c=='U'||c=='_')) throw new IllegalArgumentException("test_order can only contain characters I, P, D, U, _");
- }
- this.test_order = test_order;
- }
-
- public SpeedtestConfig clone(){
- return new SpeedtestConfig(dl_ckSize, ul_ckSize, dl_parallelStreams, ul_parallelStreams, dl_streamDelay, ul_streamDelay, dl_graceTime, ul_graceTime, dl_connectTimeout, dl_soTimeout, ul_connectTimeout, ul_soTimeout, ping_connectTimeout, ping_soTimeout, dl_recvBuffer, dl_sendBuffer, ul_recvBuffer, ul_sendBuffer, ping_recvBuffer, ping_sendBuffer, errorHandlingMode, time_dl_max, time_ul_max, time_auto, count_ping, telemetry_extra, overheadCompensationFactor, getIP_isp, getIP_distance, useMebibits, test_order);
- }
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/config/TelemetryConfig.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/config/TelemetryConfig.java
deleted file mode 100644
index ffb72fe..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/config/TelemetryConfig.java
+++ /dev/null
@@ -1,55 +0,0 @@
-package com.fdossena.speedtest.core.config;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-public class TelemetryConfig {
- private String telemetryLevel=LEVEL_DISABLED, server=null, path=null, shareURL=null;
- public static final String LEVEL_DISABLED="disabled", LEVEL_BASIC="basic", LEVEL_FULL="full";
-
- private void check(){
- if(!(telemetryLevel.equals(LEVEL_DISABLED)||telemetryLevel.equals(LEVEL_BASIC)||telemetryLevel.equals(LEVEL_FULL))) throw new IllegalArgumentException("Telemetry level must be disabled, basic or full");
- }
-
- public TelemetryConfig(){}
-
- public TelemetryConfig(String telemetryLevel, String server, String path, String shareURL){
- this.telemetryLevel=telemetryLevel;
- this.server=server;
- this.path=path;
- this.shareURL=shareURL;
- check();
- }
-
- public TelemetryConfig(JSONObject json){
- try{
- if(json.has("telemetryLevel")) telemetryLevel=json.getString("telemetryLevel");
- if(json.has("server")) server=json.getString("server");
- if(json.has("path")) path=json.getString("path");
- if(json.has("shareURL")) shareURL=json.getString("shareURL");
- check();
- }catch(JSONException t){
- throw new IllegalArgumentException("Invalid JSON ("+t.toString()+")");
- }
- }
-
- public String getTelemetryLevel() {
- return telemetryLevel;
- }
-
- public String getServer() {
- return server;
- }
-
- public String getPath() {
- return path;
- }
-
- public String getShareURL() {
- return shareURL;
- }
-
- public TelemetryConfig clone(){
- return new TelemetryConfig(telemetryLevel,server,path,shareURL);
- }
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/download/DownloadStream.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/download/DownloadStream.java
deleted file mode 100644
index b4acee5..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/download/DownloadStream.java
+++ /dev/null
@@ -1,106 +0,0 @@
-package com.fdossena.speedtest.core.download;
-
-import com.fdossena.speedtest.core.config.SpeedtestConfig;
-import com.fdossena.speedtest.core.base.Connection;
-import com.fdossena.speedtest.core.base.Utils;
-import com.fdossena.speedtest.core.log.Logger;
-
-public abstract class DownloadStream {
- private String server, path;
- private int ckSize;
- private int connectTimeout, soTimeout, recvBuffer, sendBuffer;
- private Connection c=null;
- private Downloader downloader;
- private String errorHandlingMode= SpeedtestConfig.ONERROR_ATTEMPT_RESTART;
- private long currentDownloaded=0, previouslyDownloaded=0;
- private boolean stopASAP=false;
- private Logger log;
-
- public DownloadStream(String server, String path, int ckSize, String errorHandlingMode, int connectTimeout, int soTimeout, int recvBuffer, int sendBuffer, Logger log){
- this.server=server;
- this.path=path;
- this.ckSize=ckSize;
- this.errorHandlingMode=errorHandlingMode;
- this.connectTimeout=connectTimeout;
- this.soTimeout=soTimeout;
- this.recvBuffer=recvBuffer;
- this.sendBuffer=sendBuffer;
- this.log=log;
- init();
- }
-
- private void init(){
- if(stopASAP) return;
- new Thread(){
- public void run(){
- if(c!=null){
- try{c.close();}catch (Throwable t){}
- }
- if(downloader !=null) downloader.stopASAP();
- currentDownloaded=0;
- try {
- c = new Connection(server, connectTimeout, soTimeout, recvBuffer, sendBuffer);
- if(stopASAP){
- try{c.close();}catch (Throwable t){}
- return;
- }
- downloader =new Downloader(c,path,ckSize) {
- @Override
- public void onProgress(long downloaded) {
- currentDownloaded=downloaded;
- }
-
- @Override
- public void onError(String err) {
- log("A downloader died");
- if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_FAIL)){
- DownloadStream.this.onError(err);
- return;
- }
- if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_ATTEMPT_RESTART)||errorHandlingMode.equals(SpeedtestConfig.ONERROR_MUST_RESTART)){
- previouslyDownloaded+=currentDownloaded;
- Utils.sleep(100);
- init();
- }
- }
- };
- }catch (Throwable t){
- log("A downloader failed hard");
- try{c.close();}catch (Throwable t1){}
- if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_MUST_RESTART)){
- Utils.sleep(100);
- init();
- }else onError(t.toString());
- }
- }
- }.start();
-
- }
-
- public abstract void onError(String err);
-
- public void stopASAP(){
- stopASAP=true;
- if(downloader !=null) downloader.stopASAP();
- }
-
- public long getTotalDownloaded(){
- return previouslyDownloaded+currentDownloaded;
- }
-
- public void resetDownloadCounter(){
- previouslyDownloaded=0;
- currentDownloaded=0;
- if(downloader !=null) downloader.resetDownloadCounter();
- }
-
- public void join(){
- while(downloader==null) Utils.sleep(0,100);
- try{downloader.join();}catch (Throwable t){}
- }
-
- private void log(String s){
- if(log!=null) log.l(s);
- }
-
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/download/Downloader.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/download/Downloader.java
deleted file mode 100644
index a876ac9..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/download/Downloader.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.fdossena.speedtest.core.download;
-
-import java.io.InputStream;
-
-import com.fdossena.speedtest.core.base.Connection;
-import com.fdossena.speedtest.core.base.Utils;
-
-public abstract class Downloader extends Thread{
- private Connection c;
- private String path;
- private int ckSize;
- private boolean stopASAP=false, resetASAP=false;
- private long totDownloaded=0;
-
- public Downloader(Connection c, String path, int ckSize){
- this.c=c;
- this.path=path;
- this.ckSize=ckSize<1?1:ckSize;
- start();
- }
-
- private static final int BUFFER_SIZE=16384;
- public void run(){
- try{
- String s=path;
- s+= Utils.url_sep(s)+"ckSize="+ckSize;
- long lastProgressEvent=System.currentTimeMillis();
- long ckBytes=ckSize*1048576, newRequestThreshold=ckBytes/4;
- long bytesLeft=0;
- InputStream in=c.getInputStream();
- byte[] buf=new byte[BUFFER_SIZE];
- for(;;){
- if(stopASAP) break;
- if(bytesLeft<=newRequestThreshold){
- c.GET(s, true);
- bytesLeft+=ckBytes;
- }
- if(stopASAP) break;
- int l=in.read(buf);
- if(stopASAP) break;
- bytesLeft-=l;
- if(resetASAP){
- totDownloaded=0;
- resetASAP=false;
- }
- totDownloaded+=l;
- if(System.currentTimeMillis()-lastProgressEvent>200){
- lastProgressEvent=System.currentTimeMillis();
- onProgress(totDownloaded);
- }
- }
- c.close();
- }catch(Throwable t){
- try{c.close();}catch(Throwable t1){}
- onError(t.toString());
- }
- }
-
- public void stopASAP(){
- this.stopASAP=true;
- }
-
- public abstract void onProgress(long downloaded);
- public abstract void onError(String err);
-
- public void resetDownloadCounter(){
- resetASAP=true;
- }
-
- public long getDownloaded() {
- return resetASAP?0:totDownloaded;
- }
-}
\ No newline at end of file
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/getIP/GetIP.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/getIP/GetIP.java
deleted file mode 100644
index 51dd07f..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/getIP/GetIP.java
+++ /dev/null
@@ -1,59 +0,0 @@
-package com.fdossena.speedtest.core.getIP;
-
-import java.io.BufferedReader;
-import java.util.HashMap;
-
-import com.fdossena.speedtest.core.config.SpeedtestConfig;
-import com.fdossena.speedtest.core.base.Connection;
-import com.fdossena.speedtest.core.base.Utils;
-
-public abstract class GetIP extends Thread{
- private Connection c;
- private String path;
- private boolean isp;
- private String distance;
- public GetIP(Connection c, String path, boolean isp, String distance){
- this.c=c;
- this.path=path;
- this.isp=isp;
- if(!(distance==null||distance.equals(SpeedtestConfig.DISTANCE_KM)||distance.equals(SpeedtestConfig.DISTANCE_MILES))) throw new IllegalArgumentException("Distance must be null, mi or km");
- this.distance=distance;
- start();
- }
-
- public void run(){
- try{
- String s=path;
- if(isp){
- s+= Utils.url_sep(s)+"isp=true";
- if(!distance.equals(SpeedtestConfig.DISTANCE_NO)){
- s+=Utils.url_sep(s)+"distance="+distance;
- }
- }
- c.GET(s,true);
- HashMap h=c.parseResponseHeaders();
- BufferedReader br=new BufferedReader(c.getInputStreamReader());
- if(h.get("content-length")!=null){
- //standard encoding
- char[] buf=new char[Integer.parseInt(h.get("content-length"))];
- br.read(buf);
- String data=new String(buf);
- onDataReceived(data);
- }else{
- //chunked encoding hack. TODO: improve this garbage with proper chunked support
- c.readLineUnbuffered(); //ignore first line
- String data=c.readLineUnbuffered(); //actual info we want
- c.readLineUnbuffered(); //ignore last line (0)
- onDataReceived(data);
- }
-
- c.close();
- }catch(Throwable t){
- try{c.close();}catch(Throwable t1){}
- onError(t.toString());
- }
- }
-
- public abstract void onDataReceived(String data);
- public abstract void onError(String err);
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/log/Logger.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/log/Logger.java
deleted file mode 100644
index 76d27a4..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/log/Logger.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.fdossena.speedtest.core.log;
-
-public class Logger {
- private String log="";
- public Logger(){}
-
- public String getLog(){
- synchronized (this){
- return log;
- }
- }
-
- public void l(String s){
- synchronized (this){
- log+=System.currentTimeMillis()+" "+s+"\n";
- }
- }
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/ping/PingStream.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/ping/PingStream.java
deleted file mode 100644
index e661d6f..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/ping/PingStream.java
+++ /dev/null
@@ -1,99 +0,0 @@
-package com.fdossena.speedtest.core.ping;
-
-import com.fdossena.speedtest.core.config.SpeedtestConfig;
-import com.fdossena.speedtest.core.base.Connection;
-import com.fdossena.speedtest.core.base.Utils;
-import com.fdossena.speedtest.core.log.Logger;
-
-public abstract class PingStream {
- private String server, path;
- private int remainingPings=10;
- private int connectTimeout, soTimeout, recvBuffer, sendBuffer;
- private Connection c=null;
- private Pinger pinger;
- private String errorHandlingMode= SpeedtestConfig.ONERROR_ATTEMPT_RESTART;
- private boolean stopASAP=false;
- private Logger log;
-
- public PingStream(String server, String path, int pings, String errorHandlingMode, int connectTimeout, int soTimeout, int recvBuffer, int sendBuffer, Logger log){
- this.server=server;
- this.path=path;
- remainingPings=pings<1?1:pings;
- this.errorHandlingMode=errorHandlingMode;
- this.connectTimeout=connectTimeout;
- this.soTimeout=soTimeout;
- this.recvBuffer=recvBuffer;
- this.sendBuffer=sendBuffer;
- this.log=log;
- init();
- }
-
- private void init(){
- if(stopASAP) return;
- if(c!=null){
- try{c.close();}catch (Throwable t){}
- }
- new Thread(){
- public void run(){
- if(pinger !=null) pinger.stopASAP();
- if(remainingPings<=0) return;
- try {
- c = new Connection(server, connectTimeout, soTimeout, recvBuffer, sendBuffer);
- if(stopASAP){
- try{c.close();}catch (Throwable t){}
- return;
- }
- pinger =new Pinger(c,path) {
- @Override
- public boolean onPong(long ns) {
- boolean r=PingStream.this.onPong(ns);
- if(--remainingPings<=0||!r){
- onDone();
- return false;
- } else return true;
- }
-
- @Override
- public void onError(String err) {
- log("A pinger died");
- if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_FAIL)){
- PingStream.this.onError(err);
- return;
- }
- if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_ATTEMPT_RESTART)||errorHandlingMode.equals(SpeedtestConfig.ONERROR_MUST_RESTART)){
- Utils.sleep(100);
- init();
- }
- }
- };
- }catch (Throwable t){
- log("A pinger failed hard");
- try{c.close();}catch (Throwable t1){}
- if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_MUST_RESTART)){
- Utils.sleep(100);
- init();
- }else onError(t.toString());
- }
- }
- }.start();
- }
-
- public abstract void onError(String err);
- public abstract boolean onPong(long ns);
- public abstract void onDone();
-
- public void stopASAP(){
- stopASAP=true;
- if(pinger !=null) pinger.stopASAP();
- }
-
- public void join(){
- while(pinger==null) Utils.sleep(0,100);
- try{pinger.join();}catch (Throwable t){}
- }
-
- private void log(String s){
- if(log!=null) log.l(s);
- }
-
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/ping/Pinger.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/ping/Pinger.java
deleted file mode 100644
index f896080..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/ping/Pinger.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package com.fdossena.speedtest.core.ping;
-
-import java.io.InputStream;
-
-import com.fdossena.speedtest.core.base.Connection;
-
-public abstract class Pinger extends Thread{
- private Connection c;
- private String path;
- private boolean stopASAP=false;
-
- public Pinger(Connection c, String path){
- this.c=c;
- this.path=path;
- start();
- }
-
- public void run(){
- try{
- String s=path;
- InputStream in=c.getInputStream();
- for(;;){
- if(stopASAP) break;
- c.GET(s,true);
- if(stopASAP) break;
- long t=System.nanoTime();
- if(c.readLineUnbuffered().trim().isEmpty()) throw new Exception("Persistent connection died");
- t=System.nanoTime()-t;
- if(stopASAP) break;
- while(!c.readLineUnbuffered().trim().isEmpty());
- if(stopASAP) break;
- if(!onPong(t/2)) break;
- }
- c.close();
- }catch(Throwable t){
- try{c.close();}catch(Throwable t1){}
- onError(t.toString());
- }
- }
-
- public abstract boolean onPong(long ns);
- public abstract void onError(String err);
-
- public void stopASAP(){
- this.stopASAP=true;
- }
-}
\ No newline at end of file
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/serverSelector/ServerSelector.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/serverSelector/ServerSelector.java
deleted file mode 100644
index ecdded0..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/serverSelector/ServerSelector.java
+++ /dev/null
@@ -1,115 +0,0 @@
-package com.fdossena.speedtest.core.serverSelector;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.ArrayList;
-
-import com.fdossena.speedtest.core.config.SpeedtestConfig;
-import com.fdossena.speedtest.core.ping.PingStream;
-
-public abstract class ServerSelector {
- private ArrayList servers=new ArrayList<>();
- private static final int PARALLELISM=6;
- private TestPoint selectedTestPoint=null;
- private int state=NOT_STARTED;
- private static final int NOT_STARTED=0, WORKING=1, DONE=2;
- private int timeout;
- private static final int PINGS=3, SLOW_THRESHOLD=500;
- private boolean stopASAP=false;
-
- public ServerSelector(TestPoint[] servers, int timeout){
- addTestPoints(servers);
- this.timeout=timeout;
- }
- public void addTestPoint(TestPoint t){
- if(state!=NOT_STARTED) throw new IllegalStateException("Cannot add test points at this time");
- if(t==null) return;
- servers.add(t);
- }
- public void addTestPoint(JSONObject t){
- if(state!=NOT_STARTED) throw new IllegalStateException("Cannot add test points at this time");
- servers.add(new TestPoint(t));
- }
- public void addTestPoints(JSONArray a){
- if(state!=NOT_STARTED) throw new IllegalStateException("Cannot add test points at this time");
- for(int i=0;i= servers.size()){
- if(activeStreams<=0){
- selectedTestPoint=null;
- for(TestPoint t:servers){
- if(t.ping==-1) continue;
- if(selectedTestPoint==null||t.ping h=c.parseResponseHeaders();
- String data="";
- String transferEncoding=h.get("transfer-encoding");
- if(transferEncoding!=null&&transferEncoding.equalsIgnoreCase("chunked")){
- c.readLineUnbuffered();
- }
- data=c.readLineUnbuffered();
- onDataReceived(data);
- c.close();
- }catch(Throwable t){
- try{c.close();}catch(Throwable t1){}
- onError(t.toString());
- }
- }
-
- public abstract void onDataReceived(String data);
- public abstract void onError(String err);
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/upload/UploadStream.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/upload/UploadStream.java
deleted file mode 100644
index 4ae3c02..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/upload/UploadStream.java
+++ /dev/null
@@ -1,105 +0,0 @@
-package com.fdossena.speedtest.core.upload;
-
-import com.fdossena.speedtest.core.base.Connection;
-import com.fdossena.speedtest.core.base.Utils;
-import com.fdossena.speedtest.core.config.SpeedtestConfig;
-import com.fdossena.speedtest.core.log.Logger;
-
-public abstract class UploadStream {
- private String server, path;
- private int ckSize;
- private int connectTimeout, soTimeout, recvBuffer, sendBuffer;
- private Connection c=null;
- private Uploader uploader;
- private String errorHandlingMode= SpeedtestConfig.ONERROR_ATTEMPT_RESTART;
- private long currentUploaded=0, previouslyUploaded=0;
- private boolean stopASAP=false;
- private Logger log;
-
- public UploadStream(String server, String path, int ckSize, String errorHandlingMode, int connectTimeout, int soTimeout, int recvBuffer, int sendBuffer, Logger log){
- this.server=server;
- this.path=path;
- this.ckSize=ckSize;
- this.errorHandlingMode=errorHandlingMode;
- this.connectTimeout=connectTimeout;
- this.soTimeout=soTimeout;
- this.recvBuffer=recvBuffer;
- this.sendBuffer=sendBuffer;
- this.log=log;
- init();
- }
-
- private void init(){
- if(stopASAP) return;
- new Thread(){
- public void run(){
- if(c!=null){
- try{c.close();}catch (Throwable t){}
- }
- if(uploader !=null) uploader.stopASAP();
- currentUploaded=0;
- try {
- c = new Connection(server, connectTimeout, soTimeout, recvBuffer, sendBuffer);
- if(stopASAP){
- try{c.close();}catch (Throwable t){}
- return;
- }
- uploader =new Uploader(c,path,ckSize) {
- @Override
- public void onProgress(long uploaded) {
- currentUploaded=uploaded;
- }
-
- @Override
- public void onError(String err) {
- log("An uploader died");
- if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_FAIL)){
- UploadStream.this.onError(err);
- return;
- }
- if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_ATTEMPT_RESTART)||errorHandlingMode.equals(SpeedtestConfig.ONERROR_MUST_RESTART)){
- previouslyUploaded+=currentUploaded;
- Utils.sleep(100);
- init();
- }
- }
- };
- }catch (Throwable t){
- log("An uploader failed hard");
- try{c.close();}catch (Throwable t1){}
- if(errorHandlingMode.equals(SpeedtestConfig.ONERROR_MUST_RESTART)){
- Utils.sleep(100);
- init();
- }else onError(t.toString());
- }
- }
- }.start();
- }
-
- public abstract void onError(String err);
-
- public void stopASAP(){
- stopASAP=true;
- if(uploader !=null) uploader.stopASAP();
- }
-
- public long getTotalUploaded(){
- return previouslyUploaded+currentUploaded;
- }
-
- public void resetUploadCounter(){
- previouslyUploaded=0;
- currentUploaded=0;
- if(uploader !=null) uploader.resetUploadCounter();
- }
-
- public void join(){
- while(uploader==null) Utils.sleep(0,100);
- try{uploader.join();}catch (Throwable t){}
- }
-
- private void log(String s){
- if(log!=null) log.l(s);
- }
-
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/upload/Uploader.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/upload/Uploader.java
deleted file mode 100644
index 34a0a9f..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/upload/Uploader.java
+++ /dev/null
@@ -1,73 +0,0 @@
-package com.fdossena.speedtest.core.upload;
-
-import java.io.OutputStream;
-import java.util.Random;
-
-import com.fdossena.speedtest.core.base.Connection;
-
-public abstract class Uploader extends Thread{
- private Connection c;
- private String path;
- private boolean stopASAP=false, resetASAP=false;
- private long totUploaded=0;
- private byte[] garbage;
-
- public Uploader(Connection c, String path, int ckSize){
- this.c=c;
- this.path=path;
- garbage=new byte[ckSize*1048576];
- Random r=new Random(System.nanoTime());
- r.nextBytes(garbage);
- start();
- }
-
- private static final int BUFFER_SIZE=16384;
- public void run(){
- try{
- String s=path;
- long lastProgressEvent=System.currentTimeMillis();
- OutputStream out=c.getOutputStream();
- byte[] buf=new byte[BUFFER_SIZE];
- for(;;){
- if(stopASAP) break;
- c.POST(s,true,"application/octet-stream",garbage.length);
- for(int offset=0;offset=garbage.length)?(garbage.length-offset):BUFFER_SIZE;
- out.write(garbage,offset,l);
- if(stopASAP) break;
- if(resetASAP){
- totUploaded=0;
- resetASAP=false;
- }
- totUploaded+=l;
- if(System.currentTimeMillis()-lastProgressEvent>200){
- lastProgressEvent=System.currentTimeMillis();
- onProgress(totUploaded);
- }
- }
- if(stopASAP) break;
- while(!c.readLineUnbuffered().trim().isEmpty());
- }
- c.close();
- }catch(Throwable t){
- try{c.close();}catch(Throwable t1){}
- onError(t.toString());
- }
- }
-
- public void stopASAP(){
- this.stopASAP=true;
- }
-
- public abstract void onProgress(long uploaded);
- public abstract void onError(String err);
-
- public void resetUploadCounter(){
- resetASAP=true;
- }
-
- public long getUploaded() {
- return resetASAP?0:totUploaded;
- }
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/worker/SpeedtestWorker.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/worker/SpeedtestWorker.java
deleted file mode 100644
index d5d3af9..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/worker/SpeedtestWorker.java
+++ /dev/null
@@ -1,277 +0,0 @@
-package com.fdossena.speedtest.core.worker;
-
-import org.json.JSONObject;
-
-import com.fdossena.speedtest.core.base.Connection;
-import com.fdossena.speedtest.core.base.Utils;
-import com.fdossena.speedtest.core.config.SpeedtestConfig;
-import com.fdossena.speedtest.core.config.TelemetryConfig;
-import com.fdossena.speedtest.core.download.DownloadStream;
-import com.fdossena.speedtest.core.getIP.GetIP;
-import com.fdossena.speedtest.core.log.Logger;
-import com.fdossena.speedtest.core.ping.PingStream;
-import com.fdossena.speedtest.core.serverSelector.TestPoint;
-import com.fdossena.speedtest.core.telemetry.Telemetry;
-import com.fdossena.speedtest.core.upload.UploadStream;
-
-import java.util.Locale;
-
-public abstract class SpeedtestWorker extends Thread{
- private TestPoint backend;
- private SpeedtestConfig config;
- private TelemetryConfig telemetryConfig;
- private boolean stopASAP=false;
- private double dl=-1, ul=-1, ping=-1, jitter=-1;
- private String ipIsp="";
- private Logger log=new Logger();
-
- public SpeedtestWorker(TestPoint backend, SpeedtestConfig config, TelemetryConfig telemetryConfig){
- this.backend=backend;
- this.config=config==null?new SpeedtestConfig():config;
- this.telemetryConfig=telemetryConfig==null?new TelemetryConfig():telemetryConfig;
- start();
- }
-
- public void run(){
- log.l("Test started");
- try {
- for (char t : config.getTest_order().toCharArray()) {
- if(stopASAP) break;
- if (t == '_') Utils.sleep(1000);
- if (t == 'I') getIP();
- if (t == 'D') dlTest();
- if (t == 'U') ulTest();
- if (t == 'P') pingTest();
- }
- }catch (Throwable t){
- onCriticalFailure(t.toString());
- }
- try{
- sendTelemetry();
- }catch (Throwable t){}
- onEnd();
- }
-
- private boolean getIPCalled=false;
- private void getIP(){
- if(getIPCalled) return; else getIPCalled=true;
- final long start=System.currentTimeMillis();
- Connection c = null;
- try {
- c = new Connection(backend.getServer(), config.getPing_connectTimeout(), config.getPing_soTimeout(), -1, -1);
- } catch (Throwable t) {
- if (config.getErrorHandlingMode().equals(SpeedtestConfig.ONERROR_FAIL)){
- abort();
- onCriticalFailure(t.toString());
- }
- return;
- }
- GetIP g = new GetIP(c, backend.getGetIpURL(), config.getGetIP_isp(), config.getGetIP_distance()) {
- @Override
- public void onDataReceived(String data) {
- ipIsp=data;
- try{
- data=new JSONObject(data).getString("processedString");
- }catch (Throwable t){}
- log.l("GetIP: "+ data+ " (took "+(System.currentTimeMillis()-start)+"ms)");
- onIPInfoUpdate(data);
- }
-
- @Override
- public void onError(String err) {
- log.l("GetIP: FAILED (took "+(System.currentTimeMillis()-start)+"ms)");
- abort();
- onCriticalFailure(err);
- }
- };
- while (g.isAlive()) Utils.sleep(0, 100);
- }
-
- private boolean dlCalled=false;
- private void dlTest(){
- if(dlCalled) return; else dlCalled=true;
- final long start=System.currentTimeMillis();
- onDownloadUpdate(0,0);
- DownloadStream[] streams=new DownloadStream[config.getDl_parallelStreams()];
- for(int i=0;i=config.getDl_graceTime()*1000){
- graceTimeDone=true;
- for(DownloadStream d:streams) d.resetDownloadCounter();
- startT=System.currentTimeMillis();
- continue;
- }
- if(stopASAP||t+bonusT>=config.getTime_dl_max()*1000){
- for(DownloadStream d:streams) d.stopASAP();
- for(DownloadStream d:streams) d.join();
- break;
- }
- if(graceTimeDone) {
- long totDownloaded = 0;
- for (DownloadStream d : streams) totDownloaded += d.getTotalDownloaded();
- double speed = totDownloaded / ((t<100?100:t) / 1000.0);
- if (config.getTime_auto()) {
- double b = (3.2 * speed) / 100000.0;
- bonusT += b > 400 ? 400 : b;
- }
- double progress = (t + bonusT) / (double) (config.getTime_dl_max() * 1000);
- speed = (speed * 8 * config.getOverheadCompensationFactor()) / (config.getUseMebibits() ? 1048576.0 : 1000000.0);
- dl = speed;
- onDownloadUpdate(dl, progress>1?1:progress);
- }
- Utils.sleep(100);
- }
- if(stopASAP) return;
- log.l("Download: "+ dl+ " (took "+(System.currentTimeMillis()-start)+"ms)");
- onDownloadUpdate(dl,1);
- }
-
- private boolean ulCalled=false;
- private void ulTest(){
- if(ulCalled) return; else ulCalled=true;
- final long start=System.currentTimeMillis();
- onUploadUpdate(0,0);
- UploadStream[] streams=new UploadStream[config.getUl_parallelStreams()];
- for(int i=0;i=config.getUl_graceTime()*1000){
- graceTimeDone=true;
- for(UploadStream u:streams) u.resetUploadCounter();
- startT=System.currentTimeMillis();
- continue;
- }
- if(stopASAP||t+bonusT>=config.getTime_ul_max()*1000){
- for(UploadStream u:streams) u.stopASAP();
- for(UploadStream u:streams) u.join();
- break;
- }
- if(graceTimeDone) {
- long totUploaded = 0;
- for (UploadStream u : streams) totUploaded += u.getTotalUploaded();
- double speed = totUploaded / ((t<100?100:t) / 1000.0);
- if (config.getTime_auto()) {
- double b = (3.2 * speed) / 100000.0;
- bonusT += b > 400 ? 400 : b;
- }
- double progress = (t + bonusT) / (double) (config.getTime_ul_max() * 1000);
- speed = (speed * 8 * config.getOverheadCompensationFactor()) / (config.getUseMebibits() ? 1048576.0 : 1000000.0);
- ul = speed;
- onUploadUpdate(ul, progress>1?1:progress);
- }
- Utils.sleep(100);
- }
- if(stopASAP) return;
- log.l("Upload: "+ ul+ " (took "+(System.currentTimeMillis()-start)+"ms)");
- onUploadUpdate(ul,1);
- }
-
- private boolean pingCalled=false;
- private void pingTest(){
- if(pingCalled) return; else pingCalled=true;
- final long start=System.currentTimeMillis();
- onPingJitterUpdate(0,0,0);
- PingStream ps=new PingStream(backend.getServer(),backend.getPingURL(),config.getCount_ping(),config.getErrorHandlingMode(),config.getPing_connectTimeout(),config.getPing_soTimeout(),config.getPing_recvBuffer(),config.getPing_sendBuffer(),log) {
- private double minPing=Double.MAX_VALUE, prevPing=-1;
- private int counter=0;
- @Override
- public void onError(String err) {
- log.l("Ping: FAILED (took "+(System.currentTimeMillis()-start)+"ms)");
- abort();
- onCriticalFailure(err);
- }
-
- @Override
- public boolean onPong(long ns) {
- counter++;
- double ms = ns / 1000000.0;
- if (ms < minPing) minPing = ms;
- ping = minPing;
- if (prevPing == -1) {
- jitter=0;
- }else {
- double j = Math.abs(ms - prevPing);
- jitter=j>jitter?(jitter*0.3+j*0.7):(jitter*0.8+j*0.2);
- }
- prevPing = ms;
- double progress = counter / (double) config.getCount_ping();
- onPingJitterUpdate(ping, jitter, progress>1?1:progress);
- return !stopASAP;
- }
-
- @Override
- public void onDone() {
- }
- };
- ps.join();
- if(stopASAP) return;
- log.l("Ping: "+ ping+" "+jitter+ " (took "+(System.currentTimeMillis()-start)+"ms)");
- onPingJitterUpdate(ping,jitter,1);
- }
-
- private void sendTelemetry(){
- if(telemetryConfig.getTelemetryLevel().equals(TelemetryConfig.LEVEL_DISABLED)) return;
- if(stopASAP&&telemetryConfig.getTelemetryLevel().equals(TelemetryConfig.LEVEL_BASIC)) return;
- try{
- Connection c=new Connection(telemetryConfig.getServer(),-1,-1,-1,-1);
- Telemetry t=new Telemetry(c,telemetryConfig.getPath(),telemetryConfig.getTelemetryLevel(),ipIsp,config.getTelemetry_extra(),dl==-1?"":String.format(Locale.ENGLISH,"%.2f",dl),ul==-1?"":String.format(Locale.ENGLISH,"%.2f",ul),ping==-1?"":String.format(Locale.ENGLISH,"%.2f",ping),jitter==-1?"":String.format(Locale.ENGLISH,"%.2f",jitter),log.getLog()) {
- @Override
- public void onDataReceived(String data) {
- if(data.startsWith("id")){
- onTestIDReceived(data.split(" ")[1]);
- }
- }
-
- @Override
- public void onError(String err) {
- System.err.println("Telemetry error: "+err);
- }
- };
- t.join();
- }catch (Throwable t){
- System.err.println("Failed to send telemetry: "+t.toString());
- t.printStackTrace(System.err);
- }
- }
-
- public void abort(){
- if(stopASAP) return;
- log.l("Manually aborted");
- stopASAP=true;
- }
-
- public abstract void onDownloadUpdate(double dl, double progress);
- public abstract void onUploadUpdate(double ul, double progress);
- public abstract void onPingJitterUpdate(double ping, double jitter, double progress);
- public abstract void onIPInfoUpdate(String ipInfo);
- public abstract void onTestIDReceived(String id);
- public abstract void onEnd();
-
- public abstract void onCriticalFailure(String err);
-
-}
diff --git a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/ui/GaugeView.java b/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/ui/GaugeView.java
deleted file mode 100644
index 11a0571..0000000
--- a/Speedtest-Android/app/src/main/java/com/fdossena/speedtest/ui/GaugeView.java
+++ /dev/null
@@ -1,122 +0,0 @@
-package com.fdossena.speedtest.ui;
-
-import android.content.Context;
-import android.content.res.TypedArray;
-import android.graphics.Canvas;
-import android.graphics.Paint;
-import android.graphics.RectF;
-import android.util.AttributeSet;
-import android.view.View;
-
-import your.name.here.speedtest.R;
-
-public class GaugeView extends View {
- private float strokeWidth;
- private int backgroundColor;
- private int fillColor;
- private int startAngle;
- private int angles;
- private int maxValue;
- private int value=0;
-
- public GaugeView(Context context, AttributeSet attrs) {
- super(context, attrs);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.GaugeView, 0, 0);
- setStrokeWidth(a.getDimension(R.styleable.GaugeView_gauge_strokeWidth, 10));
- setBackgroundColor(a.getColor(R.styleable.GaugeView_gauge_backgroundColor, 0xFFCCCCCC));
- setFillColor(a.getColor(R.styleable.GaugeView_gauge_fillColor, 0xFFFFFFFF));
- setStartAngle(a.getInt(R.styleable.GaugeView_gauge_startAngle, 135));
- setAngles(a.getInt(R.styleable.GaugeView_gauge_angles, 270));
- setMaxValue(a.getInt(R.styleable.GaugeView_gauge_maxValue, 1000));
- }
-
- public GaugeView(Context context) {
- super(context);
- }
-
- private Paint paint=null;
- private RectF rect=null;
- @Override
- protected void onDraw(Canvas canvas) {
- super.onDraw(canvas);
- float size = getWidth()16*1024*1024) throw new Exception("Too big");
- options.inJustDecodeBounds = false;
- DisplayMetrics displayMetrics = new DisplayMetrics();
- getWindowManager().getDefaultDisplay().getMetrics(displayMetrics);
- int vh = displayMetrics.heightPixels, vw = displayMetrics.widthPixels;
- double desired=Math.max(vw,vh) * 0.7;
- double scale=desired/Math.max(iw,ih);
- final Bitmap b = Bitmap.createScaledBitmap(BitmapFactory.decodeResource(getResources(), R.drawable.testbackground, options),(int)(iw*scale), (int)(ih*scale), true);
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- v.setImageBitmap(b);
- }
- });
- }catch (Throwable t){
- System.err.println("Failed to load testbackground ("+t.getMessage()+")");
- }
- page_init();
- }
- }.start();
- }
-
- private static Speedtest st=null;
-
- private void page_init(){
- transition(R.id.page_init,TRANSITION_LENGTH);
- final TextView t=((TextView)findViewById(R.id.init_text));
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- t.setText(R.string.init_init);
- }
- });
- SpeedtestConfig config=null;
- TelemetryConfig telemetryConfig=null;
- TestPoint[] servers=null;
- try{
- String c=readFileFromAssets("SpeedtestConfig.json");
- JSONObject o=new JSONObject(c);
- config=new SpeedtestConfig(o);
- c=readFileFromAssets("TelemetryConfig.json");
- o=new JSONObject(c);
- telemetryConfig=new TelemetryConfig(o);
- if(telemetryConfig.getTelemetryLevel().equals(TelemetryConfig.LEVEL_DISABLED)){
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- hideView(R.id.privacy_open);
- }
- });
- }
- c=readFileFromAssets("ServerList.json");
- JSONArray a=new JSONArray(c);
- if(a.length()==0) throw new Exception("No test points");
- ArrayList s=new ArrayList<>();
- for(int i=0;i availableServers=new ArrayList<>();
- for(TestPoint t:servers) {
- if (t.getPing() != -1) availableServers.add(t);
- }
- int selectedId=availableServers.indexOf(selected);
- final Spinner spinner=(Spinner)findViewById(R.id.serverList);
- ArrayList options=new ArrayList();
- for(TestPoint t:availableServers){
- options.add(t.getName());
- }
- ArrayAdapter adapter=new ArrayAdapter(this,android.R.layout.simple_spinner_dropdown_item,options.toArray(new String[0]));
- adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
- spinner.setAdapter(adapter);
- spinner.setSelection(selectedId);
- final Button b=(Button)findViewById(R.id.start);
- b.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- reinitOnResume=false;
- page_test(availableServers.get(spinner.getSelectedItemPosition()));
- b.setOnClickListener(null);
- }
- });
- TextView t=(TextView)findViewById(R.id.privacy_open);
- t.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- page_privacy();
- }
- });
- }
-
- private void page_privacy(){
- transition(R.id.page_privacy,TRANSITION_LENGTH);
- reinitOnResume=false;
- ((WebView)findViewById(R.id.privacy_policy)).loadUrl(getString(R.string.privacy_policy));
- TextView t=(TextView)findViewById(R.id.privacy_close);
- t.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- transition(R.id.page_serverSelect,TRANSITION_LENGTH);
- reinitOnResume=true;
- }
- });
- }
-
- private void page_test(final TestPoint selected){
- transition(R.id.page_test,TRANSITION_LENGTH);
- st.setSelectedServer(selected);
- ((TextView)findViewById(R.id.serverName)).setText(selected.getName());
- ((TextView)findViewById(R.id.dlText)).setText(format(0));
- ((TextView)findViewById(R.id.ulText)).setText(format(0));
- ((TextView)findViewById(R.id.pingText)).setText(format(0));
- ((TextView)findViewById(R.id.jitterText)).setText(format(0));
- ((ProgressBar)findViewById(R.id.dlProgress)).setProgress(0);
- ((ProgressBar)findViewById(R.id.ulProgress)).setProgress(0);
- ((GaugeView)findViewById(R.id.dlGauge)).setValue(0);
- ((GaugeView)findViewById(R.id.ulGauge)).setValue(0);
- ((TextView)findViewById(R.id.ipInfo)).setText("");
- ((ImageView)findViewById(R.id.logo_inapp)).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- String url=getString(R.string.logo_inapp_link);
- if(url.isEmpty()) return;
- Intent i=new Intent(Intent.ACTION_VIEW);
- i.setData(Uri.parse(url));
- startActivity(i);
- }
- });
- final View endTestArea=findViewById(R.id.endTestArea);
- final int endTestAreaHeight=endTestArea.getHeight();
- ViewGroup.LayoutParams p=endTestArea.getLayoutParams();
- p.height=0;
- endTestArea.setLayoutParams(p);
- findViewById(R.id.shareButton).setVisibility(View.GONE);
- st.start(new Speedtest.SpeedtestHandler() {
- @Override
- public void onDownloadUpdate(final double dl, final double progress) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ((TextView)findViewById(R.id.dlText)).setText(progress==0?"...": format(dl));
- ((GaugeView)findViewById(R.id.dlGauge)).setValue(progress==0?0:mbpsToGauge(dl));
- ((ProgressBar)findViewById(R.id.dlProgress)).setProgress((int)(100*progress));
- }
- });
- }
-
- @Override
- public void onUploadUpdate(final double ul, final double progress) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ((TextView)findViewById(R.id.ulText)).setText(progress==0?"...": format(ul));
- ((GaugeView)findViewById(R.id.ulGauge)).setValue(progress==0?0:mbpsToGauge(ul));
- ((ProgressBar)findViewById(R.id.ulProgress)).setProgress((int)(100*progress));
- }
- });
-
- }
-
- @Override
- public void onPingJitterUpdate(final double ping, final double jitter, final double progress) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ((TextView)findViewById(R.id.pingText)).setText(progress==0?"...": format(ping));
- ((TextView)findViewById(R.id.jitterText)).setText(progress==0?"...": format(jitter));
- }
- });
- }
-
- @Override
- public void onIPInfoUpdate(final String ipInfo) {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- ((TextView)findViewById(R.id.ipInfo)).setText(ipInfo);
- }
- });
- }
-
- @Override
- public void onTestIDReceived(final String id, final String shareURL) {
- if(shareURL==null||shareURL.isEmpty()||id==null||id.isEmpty()) return;
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- Button shareButton=(Button)findViewById(R.id.shareButton);
- shareButton.setVisibility(View.VISIBLE);
- shareButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent share = new Intent(android.content.Intent.ACTION_SEND);
- share.setType("text/plain");
- share.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
- share.putExtra(Intent.EXTRA_TEXT, shareURL);
- startActivity(Intent.createChooser(share, getString(R.string.test_share)));
- }
- });
- }
- });
- }
-
- @Override
- public void onEnd() {
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- final Button restartButton=(Button)findViewById(R.id.restartButton);
- restartButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- page_init();
- restartButton.setOnClickListener(null);
- }
- });
- }
- });
- final long startT=System.currentTimeMillis(), endT=startT+TRANSITION_LENGTH;
- new Thread(){
- public void run(){
- while(System.currentTimeMillis()=Build.VERSION_CODES.N) {
- l = getResources().getConfiguration().getLocales().get(0);
- }else{
- l=getResources().getConfiguration().locale;
- }
- if(d<10) return String.format(l,"%.2f",d);
- if(d<100) return String.format(l,"%.1f",d);
- return ""+Math.round(d);
- }
-
- private int mbpsToGauge(double s){
- return (int)(1000*(1-(1/(Math.pow(1.3,Math.sqrt(s))))));
- }
-
- private String readFileFromAssets(String name) throws Exception{
- BufferedReader b=new BufferedReader(new InputStreamReader(getAssets().open(name)));
- String ret="";
- try{
- for(;;){
- String s=b.readLine();
- if(s==null) break;
- ret+=s;
- }
- }catch(EOFException e){}
- return ret;
- }
-
- private void hideView(int id){
- View v=findViewById(id);
- if(v!=null) v.setVisibility(View.GONE);
- }
-
- private boolean reinitOnResume=false;
- @Override
- protected void onResume() {
- super.onResume();
- if(reinitOnResume){
- reinitOnResume=false;
- page_init();
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- try{st.abort();}catch (Throwable t){}
- }
-
- @Override
- public void onBackPressed() {
- if(currentPage==R.id.page_privacy)
- transition(R.id.page_serverSelect,TRANSITION_LENGTH);
- else super.onBackPressed();
- }
-
- //PAGE TRANSITION SYSTEM
-
- private int currentPage=-1;
- private boolean transitionBusy=false; //TODO: improve mutex
- private int TRANSITION_LENGTH=300;
-
- private void transition(final int page, final int duration){
- if(transitionBusy){
- new Thread(){
- public void run(){
- try{sleep(10);}catch (Throwable t){}
- transition(page,duration);
- }
- }.start();
- }else transitionBusy=true;
- if(page==currentPage) return;
- final ViewGroup oldPage=currentPage==-1?null:(ViewGroup)findViewById(currentPage),
- newPage=page==-1?null:(ViewGroup)findViewById(page);
- new Thread(){
- public void run(){
- long t=System.currentTimeMillis(), endT=t+duration;
- runOnUiThread(new Runnable() {
- @Override
- public void run() {
- if(newPage!=null){
- newPage.setAlpha(0);
- newPage.setVisibility(View.VISIBLE);
- }
- if(oldPage!=null){
- oldPage.setAlpha(1);
- }
- }
- });
- while(t
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Speedtest-Android/app/src/main/res/values-ru/strings.xml b/Speedtest-Android/app/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..8dfa0a8
--- /dev/null
+++ b/Speedtest-Android/app/src/main/res/values-ru/strings.xml
@@ -0,0 +1,26 @@
+
+ Шаблон LibreSpeed для Android
+ https://github.com/librespeed/speedtest-android
+ file:///android_asset/privacy_en.html
+
+
+ Загрузка
+ Ошибка конфигурации
+ Повторить
+ Выбор сервера
+ Нет доступных серверов
+ Выбор сервера
+ Начать
+ Загрузка
+ Выгрузка
+ Mbps
+ Пинг
+ Джиттер
+ ms
+ Поделиться результатом
+ Новый тест
+ Произошла ошибка
+ Попробуйте снова
+ Приватность
+ Назад
+
diff --git a/Speedtest-Android/app/src/main/res/values-v21/styles.xml b/Speedtest-Android/app/src/main/res/values-v21/styles.xml
deleted file mode 100644
index 371a081..0000000
--- a/Speedtest-Android/app/src/main/res/values-v21/styles.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/Speedtest-Android/app/src/main/res/values/attrs.xml b/Speedtest-Android/app/src/main/res/values/attrs.xml
deleted file mode 100644
index b2334b2..0000000
--- a/Speedtest-Android/app/src/main/res/values/attrs.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Speedtest-Android/app/src/main/res/values/colors.xml b/Speedtest-Android/app/src/main/res/values/colors.xml
deleted file mode 100644
index 031453f..0000000
--- a/Speedtest-Android/app/src/main/res/values/colors.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
-
- #6060AA
- #FFFFFF
- #404040
- #6060AA
- #AACCCCCC
- #AACCCCCC
- #AAAAAA
- #6060AA
- #616161
- #616161
- #616161
- #AA6060
- #AA6060
- #808080
- #6060AA
- #FFFFFF
- #6060AA
- #FFFFFF
- #BF360C
- #FFFFFF
- #6060AA
- #FFFFFF
- #808080
-
diff --git a/Speedtest-Android/app/src/main/res/values/dimens.xml b/Speedtest-Android/app/src/main/res/values/dimens.xml
deleted file mode 100644
index 5ac7ff8..0000000
--- a/Speedtest-Android/app/src/main/res/values/dimens.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-
-
- 16dp
- 20dp
- 18dp
- 14dp
- 18dp
- 32dp
- 18dp
- 36dp
- 150dp
- 10dp
- 80dp
- 16dp
- 16dp
-
- 300dp
- 500dp
-
- 400dp
- 60dp
- 24dp
-
- 16dp
- 6dp
- 6dp
- 16dp
- 6dp
- 6dp
- 16dp
- 6dp
- 6dp
- 16dp
- 6dp
- 6dp
-
- 16dp
- 18dp
- 8dp
- 48dp
-
-
diff --git a/Speedtest-Android/app/src/main/res/values/strings.xml b/Speedtest-Android/app/src/main/res/values/strings.xml
deleted file mode 100644
index 7ca3a52..0000000
--- a/Speedtest-Android/app/src/main/res/values/strings.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-
- LibreSpeed Android Template
- https://github.com/librespeed/speedtest-android
- file:///android_asset/privacy_en.html
-
-
- Loading
- Configuration error
- Retry
- Selecting server
- No servers available
- Select a server
- Start
- Download
- Upload
- Mbps
- Ping
- Jitter
- ms
- Share result
- New test
- An error occurred
- Try again
- Privacy
- Back
-
diff --git a/Speedtest-Android/app/src/main/res/values/styles.xml b/Speedtest-Android/app/src/main/res/values/styles.xml
deleted file mode 100644
index f4fd33f..0000000
--- a/Speedtest-Android/app/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-
-
-
-
-
-
-
-
diff --git a/Speedtest-Android/build.gradle b/Speedtest-Android/build.gradle
deleted file mode 100644
index a5bb815..0000000
--- a/Speedtest-Android/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- repositories {
- google()
- jcenter()
-
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.5.3'
-
- // NOTE: Do not place your application dependencies here; they belong
- // in the individual module build.gradle files
- }
-}
-
-allprojects {
- repositories {
- google()
- jcenter()
-
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/Speedtest-Android/gradle.properties b/Speedtest-Android/gradle.properties
deleted file mode 100644
index 82618ce..0000000
--- a/Speedtest-Android/gradle.properties
+++ /dev/null
@@ -1,15 +0,0 @@
-# Project-wide Gradle settings.
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-
-
diff --git a/Speedtest-Android/gradle/wrapper/gradle-wrapper.jar b/Speedtest-Android/gradle/wrapper/gradle-wrapper.jar
deleted file mode 100644
index f6b961f..0000000
Binary files a/Speedtest-Android/gradle/wrapper/gradle-wrapper.jar and /dev/null differ
diff --git a/Speedtest-Android/gradle/wrapper/gradle-wrapper.properties b/Speedtest-Android/gradle/wrapper/gradle-wrapper.properties
deleted file mode 100644
index beed97a..0000000
--- a/Speedtest-Android/gradle/wrapper/gradle-wrapper.properties
+++ /dev/null
@@ -1,6 +0,0 @@
-#Mon Sep 02 18:14:21 CEST 2019
-distributionBase=GRADLE_USER_HOME
-distributionPath=wrapper/dists
-zipStoreBase=GRADLE_USER_HOME
-zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/Speedtest-Android/gradlew b/Speedtest-Android/gradlew
deleted file mode 100755
index cccdd3d..0000000
--- a/Speedtest-Android/gradlew
+++ /dev/null
@@ -1,172 +0,0 @@
-#!/usr/bin/env sh
-
-##############################################################################
-##
-## Gradle start up script for UN*X
-##
-##############################################################################
-
-# 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
-
-APP_NAME="Gradle"
-APP_BASE_NAME=`basename "$0"`
-
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
-
-# 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
-nonstop=false
-case "`uname`" in
- CYGWIN* )
- cygwin=true
- ;;
- Darwin* )
- darwin=true
- ;;
- MINGW* )
- msys=true
- ;;
- NONSTOP* )
- nonstop=true
- ;;
-esac
-
-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" -a "$nonstop" = "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
-
-# Escape application args
-save () {
- for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
- echo " "
-}
-APP_ARGS=$(save "$@")
-
-# Collect all arguments for the java command, following the shell quoting and substitution rules
-eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
-exec "$JAVACMD" "$@"
diff --git a/Speedtest-Android/gradlew.bat b/Speedtest-Android/gradlew.bat
deleted file mode 100644
index e95643d..0000000
--- a/Speedtest-Android/gradlew.bat
+++ /dev/null
@@ -1,84 +0,0 @@
-@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
-
-set DIRNAME=%~dp0
-if "%DIRNAME%" == "" set DIRNAME=.
-set APP_BASE_NAME=%~n0
-set APP_HOME=%DIRNAME%
-
-@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=
-
-@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 Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_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=%*
-
-: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/Speedtest-Android/settings.gradle b/Speedtest-Android/settings.gradle
deleted file mode 100644
index 33b9b25..0000000
--- a/Speedtest-Android/settings.gradle
+++ /dev/null
@@ -1,2 +0,0 @@
-include ':app'
-rootProject.name='Speedtest-Android'
diff --git a/doc.md b/doc.md
deleted file mode 100644
index c9636b3..0000000
--- a/doc.md
+++ /dev/null
@@ -1,907 +0,0 @@
-# LibreSpeed Android template
-
-The LibreSpeed Android template allows you to configure and distribute an Android app that performs a speedtest using your existing [LibreSpeed](https://github.com/librespeed/speedtest) server(s).
-
-## Configuring the template
-Here's an overview of the structure of the template, configured with the default settings. Click to expand.
-
-[](https://github.com/librespeed/speedtest-android/blob/master/.github/template_fsm.png?raw=true)
-
-### Download the template
-The first thing to do is download latest version of the template:
-```
-git clone https://github.com/librespeed/speedtest-android
-```
-
-Now you will have a directory containing the template project and some documentation. Open the Speedtest-Android project in Android Studio.
-
-### Setting the package name
-The template's default package name is `your.name.here.speedtest`; this must be changed to allow distribution. Typically it's going to be something like `com.yourdomain.speedtest`.
-
-* Right click app and click Open Module settings
-* Go to Modules > app > Default config, change Application ID to your new package name and press Ok
-* Expand app and double click manifests
-* Update `package="your.name.here.speedtest"` with your new package name
-* Expand app > java > com > fdossena > speedtest > ui
-* Open MainActivity.java and update `import your.name.here.speedtest.R` with your new package name
-* Do the same thing for GaugeView.java
-* Build the application
-
-### Adding your test points
-Expand app > assets and open ServerList.json. Here you will find a list of test servers expressed as a JSON array.
-
-* Select all and delete
-* Add your test points following this syntax:
- ```json
- [
- {
- "name":"My Test Server 1",
- "server":"//example.yourdomain.com",
- "dlURL":"garbage.php",
- "ulURL":"empty.php",
- "pingURL":"empty.php",
- "getIpURL":"getIP.php"
- },
- {
- ...
- }
- ...
- ]
- ```
-* Save the file
-
-Here's a more in-depth explanation of what the individual fields for each servers are:
-* __`"name"`__: User friendly name (eg. `"Milan, Italy"`)
-* __`"server"`__: URL to the server where LibreSpeed is installed. If it only supports HTTP or HTTPS, specify it; if it supports both, simply use // followed by the address
-* __`"dlURL"`__: Path on your server where the download test can be performed (typically `"garbage.php"` or `"backend/garbage.php"`)
-* __`"urURL"`__: Path on your server where the upload test can be performed (typically `"empty.php"` or `"backend/empty.php"`)
-* __`"pingURL"`__: Path on your server where the ping/jitter test can be performed (typically `"empty.php"` or `"backend/empty.php"`)
-* __`"getIpURL"`__: Path on your server where the IP address and ISP info can be fetched (typically `"getIP.php"` or `"backend/getIP.php"`)
-
-__Important__: This app DOES NOT handle HTTP redirects (3xx codes)!
-
-### Telemetry and results sharing
-If you want to enable telemetry and results sharing, edit TelemetryConfig.json. Here you need to add the information about your telemetry server (LibreSpeed frontend server). This feature is disabled by default.
-
-* Follow this syntax:
-```json
-{
- "telemetryLevel":"full",
- "server":"//speedtest.yourdomain.com",
- "path":"results/telemetry.php",
- "shareURL":"results/?id=%s"
-}
-```
-* Save the file
-
-Here's a more in-depth explanation of what the individual fields are:
-
-* __`"telemetryLevel"`__:
- * `"disabled"`: No telemetry sent at all. If you choose this, you don't need to specify anything else
- * `"basic"`: Stores results for completed tests without log
- * `"full"`: Stores results and log for all tests, even if they are aborted or if they fail
- * Default: `"disabled"`
-* __`"server"`__: URL to the server where the LibreSpeed telemetry is installed (the frontend server). If it only supports HTTP or HTTPS, specify it; if it supports both, simply use // followed by the address
-* __`"path"`__: Path on your server where telemetry can be stored (typically `results/telemetry.php`)
-* __`"shareURL"`__: Path on your server where the results sharing links point to (typically `results/?id=%s` or an empty string if you don't want to enable this feature). %s will be replaced by the test ID. Omit this if you don't want to use results sharing
-
-#### Privacy policy
-Telemetry contains personal information (according to GDPR defintion), therefore it is important to treat this data respectfully of national and international laws, especially if you plan to offer the service in the European Union.
-
-A template for a privacy policy is included in `privacy_en.html`. You MUST read it, change it if necessary, and att your email address for data deletion requests. __Failure to comply with GDPR regulations can get you in serious trouble__.
-
-If you plan to support other languages, see the chapter on localization on how to localize the privacy policy.
-
-### Optional: Advanced configuration
-The default speedtest configuration is fine for most use cases, but you can customize it if you want.
-
-* Edit `SpeedtestConfig.json`. Inside you will find an empty JSON object. This indicates that the test is uses the default configuration. Delete it.
-* Follow this syntax:
-```json
-{
- "param1":"value",
- "param2":"other value",
- ...
-}
-```
-
-Here's a list of all the test parameters. All parameters are optional. Do not change them unless you have a good reason to!
-
-* __`"dl_ckSize"`__: Size in megabytes of the garbage data generated by garbage.php and downloaded by the test.
- * Default: `100`,
- * Recommended: `1`-`400`
- * Important: if you're not using garbage.php, make sure that your replaement file or whatever sends at least ckSize megabytes of data or the download test might fail!
-* __`"ul_ckSize"`__: Size in megabytes of the garbage data sent by the upload test.
- * Default: `20`,
- * Recommended: `1`-`100`
-* __`"dl_parallelStreams"`__: Number of concurrent streams used by the download test.
- * Default: `3`
- * Recommended: `1`-`6`.
-* __`"ul_parallelStreams"`__: Number of concurrent streams used by the upload test.
- * Default: `3`
- * Recommended: `1`-`6`.
-* __`"dl_streamDelay"`__: Delay in milliseconds between starts of concurrent streams in the download test
- * Default: `300`
- * Recommended: `0`-`1000`
-* __`"ul_streamDelay"`__: Delay in milliseconds between starts of concurrent streams in the upload test
- * Default: `300`
- * Recommended: `0`-`1000`
-* __`"time_dl_max"`__: Maximum duration in settings of the download test (if time_auto is set to false, this will be the fixed duration)
- * Default: `15`
- * Recommended: `5`-`30`
-* __`"time_ul_max"`__: Maximum duration in settings of the upload test (if time_auto is set to false, this will be the fixed duration)
- * Default: `15`
- * Recommended: `5`-`30`
-* __`"time_auto"`__: If set to true, the duration of the upload and download tests will be automatically decided according to the measured speed.
- * Default: `true`
-* __`"count_ping"`__: Number of pings to be done during the ping+jitter test
- * Default: `10`
- * Recommended: `3`-`50`
-* __`"dl_graceTime"`__: Seconds at the beginning of the download test where data is discarded (allows TCP window adjustment)
- * Default: `1.5`
- * Recommended: >`0`
-* __`"ul_graceTime"`__: Seconds at the beginning of the upload test where data is discarded (prevents initial spike due to buffering)
- * Default: `1.5`
- * Recommended: >`0`
-* __`"errorHandlingMode"`__: What the test should do in case an error is encountered
- * `"fail"`: Fail the test immediately
- * `"attempt-restart"`: If a stream dies, it tries to restart it. If reopening fails, the test fails
- * `"must-restart"`: If a stream dies, keep trying to restart it indefinitely
- * Default: `"attempt-restart"`
-* __`"getIP_isp"`__: If set to true, the test will request ISP information along with the IP address
- * Default: `true`
-* __`"getIP_distance"`__: If `getIP_isp` is set to true, this tells whether to calculate approximate distance or not, and the unit
- * `"no"`: Do not calculate distance
- * `"km"`: Calculate distance in kilometers
- * `"mi"`: Calculate distance in miles
- * Default: `"km"`
-* __`"overheadCompensationFactor"`__: How much the download and upload values should be multipled by to compensate for transport overhead
- * Default: `1.06`
- * Recommended: `1` to ignore overhead, or `1.06` for a decent estimate
-* __`"useMebibits"`__: If set to true, the download and upload speeds will be measured in mebibits instead of megabits
- * Default: `false`
-* __`"test_order"`__: The order in which tests are performed. Each character in this string represents a test. Tests can only be run once
- * Default: `"IP_D_U"`
- * `I`: IP address and ISP information
- * `P`: Ping+jitter test
- * `D`: Download test
- * `U`: Upload test
- * `_`: 1 second delay
-* __`"dl_connectTimeout"`__, __`"dl_soTimeout"`__: Timeouts in milliseconds for the sockets used in the download test.
- * Default: `5000`, `10000`
- * `-1` means System default
-* __`"ul_connectTimeout"`__, __`"ul_soTimeout"`__: Timeouts in milliseconds for the sockets used in the upload test.
- * Default: `5000`, `10000`
- * `-1` means System default
-* __`"ping_connectTimeout"`__, __`"ping_soTimeout"`__: Timeouts in milliseconds for the sockets used in the ping+jitter test, the server selector, IP/ISP fetching, and telemetry.
- * Default: `2000`, `5000`
- * `-1` means System default
-* __`"dl_recvBuffer"`__, __`"dl_sendBuffer"`__: Sizes of socket buffers used in the download test
- * Default: `-1`
- * `-1` means System default
-* __`"ul_recvBuffer"`__, __`"ul_sendBuffer"`__: Sizes of socket buffers used in the upload test
- * Default: `-1` and `16384` respectively
- * `-1` means System default
-* __`"ping_recvBuffer"`__, __`"ping_sendBuffer"`__: Sizes of socket buffers used in the ping+jitter test
- * Default: `-1`
- * `-1` means System default
-
-### Branding
-
-#### Logos and background
-Expand app > res > drawable. Replace the following files (make sure that they actually go in the drawable folder!):
-
-* __`ic_launcher.png`__: App icon (256x256 recommended)
-* __`logo.png`__: Large logo file used in the splash screen (1000px width recommended)
-* __`logo_inapp.png`__: Small logo file shown above the test (800-1000px width recommended)
-* __`testbackground.png`__: Background image shown under the test (Large but not too large or it won't be loaded to prevent crashing low end devices)
-
-#### App name and link
-Expand app > res > values and edit strings.xml.
-
-* Replace the value of `app_name` with the name of your app
-* Replace the value of `logo_inapp_link` with a link to your website that users can open by clicking on the logo. Leave empty if you don't want the logo to be clickable.
-
-#### Colors
-Expand app > res > values and edit colors.xml.
-
-Here's a list of where each color is used:
-
-* __`splashBackground`__: Displayed on the splash screen, under the logo
-* __`appBackground`__: Background color in the rest of the app
-* __`textColor`__: Color used by most of the text in the application
-* __`loadingColor`__: Color of the spinning progress bar displayed during server selection
-* __`gaugesBackground`__: Background color of the gauges
-* __`progressBackground`__: Background color of the progress bars below the download and upload indicators (ignored in older versions of Android)
-* __`progressColor`__: Color of the progress bars below the download and upload indicators (ignored in older versions of Android)
-* __`dlGauge`__: Color of the download gauge
-* __`dlText`__: Color of the text indicating the download speed
-* __`ulGauge`__: Color of the upload gauge
-* __`ulText`__: Color of the text indicating the upload speed
-* __`pingText`__: Color of the text indicating the ping time
-* __`jitterText`__: Color of the text indicating the jitter time
-* __`serverText`__: Color of the text indicating the server name
-* __`startButton_background`__: Color of the start button
-* __`startButton_test`__: Color of the text in the start button
-* __`failButton_background`__: Color of the fail button (the one that says Retry)
-* __`failButton_test`__: Color of the text in the fail button (the one that says Retry)
-* __`shareButton_background`__: Color of the share button
-* __`shareButton_test`__: Color of the text in the share button
-* __`restartButton_background`__: Color of the button to start a new test
-* __`restartButton_test`__: Color of the text in the button to start a new test
-* __`privacyLinks`__: Color of the link to open/close the privacy policy
-
-__Important__: Make sure your text is always legible!
-
-### Optional: localization
-The template only contains an English localization. To replace the English text or add other languages, expand app > res > values and double click strings.xml. From here you can change all the strings in the application. To add other languages, use Android Studio's localization editor.
-
-Here's a description of each string:
-
-* __`app_name`__: Name of the app as it appears in the app drawer
-* __`logo_inapp_link`__: Link to open if the user clicks on the logo. Leave this empty if you don't want this
-* __`privacy_policy`__: URL to the privacy policy for this specific locale. You can have multiple privacy policies for different languages in the assets folder
-* __`init_init`__: Displayed while the app is loading
-* __`initFail_configError`__: Error that appears if the configuration couldn't be loaded
-* __`initFail_retry`__: Text of the Retry button when initialization or server selection fails
-* __`init_selecting`__: Displayed while the app is selecting a server
-* __`initFail_noServers`__: Displayed if no servers are available
-* __`serverSelect_message`__: Displayed above the server selection spinner
-* __`start`__: Text of the Start button
-* __`test_dl`__: Label above the download gauge
-* __`test_ul`__: Label above the upload gauge
-* __`test_speedMeasure`__: Label below the download and upload gauges
-* __`test_ping`__: Label above the ping time indicator
-* __`test_jitter`__: Label above the jitter time indicator
-* __`test_timeMeasure`__: Label next to the ping and jitter indicators
-* __`test_share`__: Text of the share button
-* __`test_restart`__: Text of the new test button
-* __`testFail_err`__: Displayed if an error occurs during the test
-* __`testFail_retry`__: Text of the Retry button when the test fails
-* __`privacy_open`__: Text of the link that opens the privacy policy
-* __`privacy_close`__: Text of the link that closes the privacy policy
-
-### The end
-Your template is now fully configured for your LibreSpeed server(s). Enjoy!
-
-This template is licensed under the [GNU LGPLv3 License](https://www.gnu.org/licenses/lgpl-3.0.en.html). In short, you are free to use, study, modify, and distribute modified copies of this application, but they must remain under the same GNU LGPLv3 license. This means that if you make any modification to the application, the source code must be made publicly available. Merely changing the configuration of this template as we did in this chapter does NOT count as a modified version, so you are free to distribute it as an APK, but any other change made to the app does count as a modification and you MUST provide the source code.
-
-## Making a custom UI
-If you don't like the UI provided by the template or the customization is too limited, you might want to make your own UI. This is possible and APIs are provided to develop a custom UI, however it requires pretty good knowledge of Android and Java.
-
-To make a custom UI:
-
-* Make a new project in Android Studio. The minimum SDK supported by the speedtest is API 15.
-* Copy the `com.fdossena.speedtest.core` package from the template into the new project
-* Add the `android.permission.INTERNET` permission to the manifest
-* Start developing
-
-Before we continue, we need to discuss the license: the `com.fdossena.speeedtest.core` package is licensed under a GNU LGPLv3 license; you are now using it as a library to develop your project so your project can use whatever license you want (even a proprietary one if you want), but any modification made to the files inside the `com.fdossena.speedtest.core` package MUST be made publicly available in source code form. You must also credit the original author in your app (Federico Dossena, https://fdossena.com).
-
-### The `Speedtest` class
-The `com.fdossena.speedtest.core` provides a handy `Speedtest` class that you can use to implement your test.
-
-##### Constructor
-The first thing to do is create an instance of the `Speedtest` class:
-
-```java
-Speedtest st = new Speedtest();
-```
-
-You might want to store this object in a variable in your activity.
-
-##### Configuration
-If you don't want to use the default settings, you need to load your configuration:
-
-```java
-SpeedtestConfig config=new SpeedtestConfig();
-... change some of the settings ...
-st.setSpeedtestConfig(config);
-```
-
-The `config` class provides getters and setters for all the test settings mentioned in the Advanced configuration section. Anything that you don't explicitly change will be set to the default value.
-
-The `SpeedtestConfig` class can also be instantiated from a JSON object, which is very convenient if you want to load the settings from a file.
-
-##### Telemetry
-If you want to use telemetry, you need to load your configuration:
-
-```java
-TelemetryConfig telemetryConfig=new TelemetryConfig(
- telemetryLevel,
- server,
- path,
- shareURL
-);
-st.setTelemetryConfig(telemetryConfig);
-```
-
-These are the same settings mentioned in the Telemetry and results sharing section.
-
-The `TelemetryConfig` class can also be instantiated from a JSON object, which is very convenient if you want to load the settings from a file.
-
-##### Adding your test points
-To run the test you need at least 1 test point. To add a test point, use
-```java
-TestPoint p=new TestPoint(name,server,dlURL,ulURL,pingURL,getIpURL);
-st.addTestPoint(p);
-```
-
-You can also add test points from an array, or from a JSON array, which is very convenient if you want to load the settings from a file:
-
-```java
-TestPoint[] servers=new TestPoint[]{
- new TestPoint(name,server,dlURL,ulURL,pingURL,getIpURL),
- ...
-};
-st.addTestPoints(servers);
-```
-
-These are the same settings mentioned in the Adding your test points section.
-
-##### Server selection
-To run the server selection process, use
-
-```java
-st.selectServer(new Speedtest.ServerSelectedHandler(){
- @Override
- public void onServerSelected(TestPoint server){
- //do something
- }
-});
-```
-
-The selection process is asynchronous. The `onServerSelected` method will be called at the end of process: `server` will either be the server with the lowest ping, or `null` if none of them were reachable.
-
-To manually choose one of the servers, use
-```java
-TestPoint p=new TestPoint(...);
-st.setSelectedServer(p);
-```
-
-Note: `selectServer` can only be called once, and you cannot call it after using `setSelectedServer`!
-
-Note: You cannot change the configuration, or add other test points after selecting a server!
-
-##### Running the test
-Finally, we can run the test:
-
-```java
- st.start(new Speedtest.SpeedtestHandler(){
- @Override
- public void onDownloadUpdate(double dl, double progress){
- //update your UI
- }
- public void onUploadUpdate(double ul, double progress){
- //update your UI
- }
- public void onPingJitterUpdate(double ping, double jitter, double progress){
- //update your UI
- }
- public void onIPInfoUpdate(String ipInfo){
- //update your UI
- }
- public void onTestIDReceived(String id){
- //update your UI
- }
- public void onEnd(){
- //test finished
- }
- public void onCriticalFailure(String err){
- //do something
- }
- });
-```
-
-The test is done asynchronously. During the test, the following callbacks will be called:
-
-* `onDownloadUpdate` is called periodically during the download test to report the download speed. `dl` is the current speed in Mbps, `progress` is a number between 0 and 1 representing how close we are to the time limit
-* `onUploadUpdate` is called periodically during the upload test to report the upload speed. `ul` is the current speed in Mbps, `progress` is a number between 0 and 1 representing how close we are to the time limit
-* `onPingJitterUpdate` is called periodically during the ping+jitter test to report ping and jitter times. `ping` is the current ping, `jitter` is the current jitter, `progress` is a number between 0 and 1 representing how close we are to the time limit
-* `onIPInfoUpdate` is called when the IP address and ISP information is received. `ipInfo` is a string combining this information
-* `onTestIDReceived` is called at the end of the test when we receive an ID from the telemetry. `id` is a string containing the test ID that can be used to generate a share link
-* `onEnd` is called at the end of the test
-* `onCriticalFailure` is called if the test fails. `err` is a string containing details about the error
-
-Note: if some functions are disabled (for instance, telemetry), the corresponding event will not be called.
-
-After the test is over, the test can be ran again, with a different server if you want.
-
-##### Aborting the test
-The test can be aborted at any time using
-
-```java
-st.abort();
-```
-
-Note: aborting the test is not instantaneous. This is done asynchronously. If the test is running, the `onEnd` event will be called.
-
-## Implementation details
-This chapter is dedicated to the inner workings of the `com.fdossena.speedtest.core` package. You don't need to read this section if you're just customizing the template or making a custom UI, it's only here if you need to make changes to how the speedtest client works.
-
-If you also need to know how the speedtest server works, see [here](https://github.com/librespeed/speedtest/wiki/Implementation-details).
-
-__Remember that both the server and this client are under a GNU LGPLv3 license, so any modification you make to it MUST be publicly available in source form! No exceptions.__
-
-The core is basically an HTTP client implemented using only Sockets, Threads and Java streams, so very good knowledge of these topics is mandatory if you want to make changes here.
-
-This chapter will be divided into the following sections:
-
-* __`base` package__: handles HTTP/HTTPS connection creation and provides functions to interact with an HTTP server
-* __`getIP` package__: implements IP and ISP info fetching
-* __`download` package__: implementation of the streams used for the download test
-* __`upload` package__: implementation of the streams used for the upload test
-* __`ping` package__: implementation of a "ping stream" used for the ping+jitter test and the server selector
-* __`telemetry` package__: implements telemetry sender
-* __`serverSelector` package__: implements the automatic server selection process
-* __`log` package__: a simple logger used to collect telemetry
-* __`config` package__: configuration for the speedtest and the telemetry
-* __`worker` package__: implements the speedtest
-* __`Speedtest` class__: wraps everything up
-
-### `base` package
-##### `Utils` class
-This class provides some static functions used throughout the test. These functions are:
-
-* `urlEncode(String s)`: URL-encodes a string (UTF-8)
-* `sleep(long ms)` and `sleep(long ms, int ns)`: simple sleep functions
-* `url_sep(String url)`: Returns the proper separator to use for GET parameters for a given string (eg. for "https://example.com/index.php" it will return "?", for "https://example.com/?id=whatever" it will return "&")
-
-##### `Connection` class
-This is the foundation on which relies most of the test.
-
-It uses a Socket to connect to the HTTP server, using either HTTP or HTTPS, and provides several methods to interact with it.
-
-__Constructors:__
-
-```java
-public Connection(String url, int connectTimeout, int soTimeout, int recvBuffer, int sendBuffer)
-```
-
-* `url` is the URL of the host that you want to connect to (eg. "https://speedtest.fdossena.com")
-* `connectTimeout` and `soTimeout` are the timeouts for the socket in milliseconds. Set them to `-1` to let the system determine them
-* `recvBuffer` and `sendBuffer` are sizes of the buffers used by the socket in bytes. Set them to `-1` to let the system determine them
-
-```java
-public Connection(String url)
-```
-
-* `url` is the URL of the host that you want to connect to (eg. "https://speedtest.fdossena.com")
-
-If you use this constructor, it will use a `connectTimeout` of `2000`, a `soTimeout` of `5000` and default buffer sizes.
-
-Note: URLs can be in one of these formats:
-* `http://address`: if the server only supports HTTP
-* `https://address`: if the server only supports HTTPS
-* `//address`: if the server supports both HTTP and HTTPS (HTTPS preferred)
-
-__Important__: This class DOES NOT handle HTTP redirects (3xx codes)!
-
-__Methods:__
-
-* `getInputStream()`: returns the socket's InputStream or null if the socket is closed/dead
-* `getOutputStream()`: returns the socket's OutputStream or null if the socket is closed/dead
-* `getPrintStream()`: similar to `getOutputStream` but it returns a `PrintStream` instead, which is more convenient for writing UTF-8 strings
-* `getInputStreamReader()`: similar to `getInputStream` but it returns an `InputStreamReader` instead, which is more convenient for reading UTF-8 strings
-* `GET(String path, boolean keepAlive)`: writes a GET request on the socket to fetch the resource at the requested `path`. If `keepAlive` is set to true, it also sends a `Connection: keep-alive` header so that the Connection can be reused. An Exception is thrown if something goes wrong.
-* `POST(String path, boolean keepAlive, String contentType, long contentLength):`: writes a POST request on the socket to fetch the requested `path` after sending some data. An Exception is thrown if something goes wrong.
- * `keepAlive`: if set to true, it will send the `Connection: keep-alive` header and the Connection can be reused
- * `contentType`: if not null, it will send the `Content-Type` header with the specified content type
- * `contentLength`: if >=`0`, it will send the `Content-Length` header, and more data can be written to the socket's OutputStream
-* `readLineUnbuffered()`: reads a line from the socket's InputStream until `\n` is encountered
-* `parseResponseHeaders()`: reads an entire HTTP response from the socket's InputStream. The headers are returned in a `Hashmap`. Throws an exception if something goes wrong or if the response was not a `200 OK`
-* `close()`: closes the socket
-
-### `getIP` package
-##### `GetIP` class
-Implements IP and ISP info fetching over an existing Connection. Creates a new Thread to do it. Starts immediately (no need to call `start()`).
-
-__Constructor:__
-
-```java
-public GetIP(Connection c, String path, boolean isp, String distance)
-```
-
-* `c`: the instance of Connection to be used
-* `path`: path on the server where the IP and ISP info can be fetched
-* `isp`: if set to true, it will fetch ISP info, otherwise it will only fetch the IP address
-* `distance`: if `isp` is set to true, this specifies how the distance should be measured
- * `SpeedtestConfig.DISTANCE_NO`: do not calculate distance
- * `SpeedtestConfig.DISTANCE_KM`: kilometers
- * `SpeedtestConfig.DISTANCE_MILES`: miles
-
-__Callbacks:__
-
-* `onDataReceived(String data)`: if the operation succedes, this is called with a JSON string representing the result (see `getIP.php` implementation detalis for more information)
-* `onError(String err)`: if the operation fails, this is called with an error message
-
-### `download` package
-##### `Downloader` class
-A Thread that uses a Connection to download an endless stream of garbage data from the server. The download starts immediately (no need to call `start()`).
-
-__Constructor:__
-
-```java
-public Downloader(Connection c, String path, int ckSize)
-```
-
-* `c`: the instance of Connection to be used
-* `path`: path on the server where the garbage data can be fetched
-* `ckSize`: size in megabytes of the garbage data (also see documentation for garbage.php)
-
-__Important:__ If you're not using `garbage.php`, your replacement must accept the `ckSize` parameter; if you're using a large file of garbage data, it needs to be at least `ckSize` megabytes for this downloader to work properly.
-
-This thread repeatedly sends a GET request to the speified `path` and download all the data coming to it. When the amount of downloaded data is nearing ckSize (75%), a new request is made to ensure a steady flow of data.
-
-__Callbacks:__
-
-* `onProgress(long downloaded)`: periodically called to inform of how much data was downloaded. (Max once every 200ms)
-* `onError(String err)`: called if something goes wrong. `err` contains the error message
-
-__Methods:__
-
-* `stopASAP()`: asks to stop the download as soon as possible. The Connection will also be closed. Use `join()` to wait for the thread to die
-* `resetDownloadCounter()`: resets the counter of the amount of downloaded data
-* `getDownloaded()`: returns the amount of downloaded data since the beginning or last reset
-
-##### `DownloadStream` class
-A stream for the download test. Manages a Connection and a Downloader, and handles errors according to the specified policy. Starts immediately.
-
-__Constructor:__
-
-```java
-public DownloadStream(String server, String path, int ckSize, String errorHandlingMode, int connectTimeout, int soTimeout, int recvBuffer, int sendBuffer, Logger log)
-```
-
-* `server`: URL to the server where we need to connect
-* `path`: path on the server where the garbage data can be fetched
-* `ckSize`: size in megabytes of the garbage data
-* `errorHandlingMode`: what we should do in case of errors:
- * SpeedtestConfig.ONERROR_FAIL: Fail immediately
- * SpeedtestConfig.ONERROR_ATTEMPT_RESTART: Try to reconnect. If that fails, the stream fails
- * SpeedtestConfig.ONERROR_MUST_RESTART: Keep trying to reconnect until we succeed (or stopped)
-* `connectTimeout`, `soTimeout`, `recvBuffer`, `sendBuffer`: settings for the socket created by Connection
-* `log`: instance of Logger that is used to report errors such as dead connections
-
-__Callbacks:__
-
-* `onError(String err)`: called if the stream fails. `err` contains the error message
-
-__Methods:__
-
-* `stopASAP()`: asks to stop the download as soon as possible. The Connection will also be closed. Use `join()` to wait for the connection to be actually closed
-* `resetDownloadCounter()`: resets the counter of the amount of downloaded data
-* `getTotalDownloaded()`: returns the amount of downloaded data since the beginning or last reset
-* `join()`: waits for the instance of Downloader to die
-
-### `upload` package
-##### `Uploader` class
-A Thread that uses a Connection to upload an endless stream of POST request containing garbage data to the server. The upload starts immediately (no need to call `start()`).
-
-This is the upload equivalent of the `Downloader` class.
-
-__Constructor:__
-
-```java
-public Uploader(Connection c, String path, int ckSize)
-```
-
-* `c`: the instance of Connection to be used
-* `path`: path on the server where the garbage data will be sent
-* `ckSize`: size in megabytes of the garbage data sent with each POST request
-
-__Important__: when an instance of Uploader is created, a blob of `ckSize` megabytes is generated and stays in RAM. Don't instantiate too many!
-
-This thread repeatedly sends a POST request containing garbage data to the server. Responses coming from the server are ignored and discarded.
-
-__Callbacks:__
-
-* `onProgress(long uploaded)`: periodically called to inform of how much data was uploaded. (Max once every 200ms)
-* `onError(String err)`: called if something goes wrong. `err` contains the error message
-
-__Methods:__
-
-* `stopASAP()`: asks to stop the upload as soon as possible. The Connection will also be closed. Use `join()` to wait for the thread to die
-* `resetUploadCounter()`: resets the counter of the amount of upload data
-* `getUploaded()`: returns the amount of uploaded data since the beginning or last reset
-
-##### `UploadStream` class
-A stream for the upload test. Manages a Connection and an Uploader, and handles errors according to the specified policy.
-
-This is the upload equivalent of the `DownloadStream` class.
-
-__Constructor:__
-
-```java
-public DownloadStream(String server, String path, int ckSize, String errorHandlingMode, int connectTimeout, int soTimeout, int recvBuffer, int sendBuffer, Logger log)
-```
-
-* `server`: URL to the server where we need to connect
-* `path`: path on the server where the garbage data can be fetched
-* `ckSize`: size in megabytes of the garbage data
-* `errorHandlingMode`: what we should do in case of errors:
- * `SpeedtestConfig.ONERROR_FAIL`: Fail immediately
- * `SpeedtestConfig.ONERROR_ATTEMPT_RESTART`: Try to reconnect. If that fails, the stream fails
- * `SpeedtestConfig.ONERROR_MUST_RESTART`: Keep trying to reconnect until we succeed (or stopped)
-* `connectTimeout`, `soTimeout`, `recvBuffer`, `sendBuffer`: settings for the socket created by Connection
-* `log`: instance of Logger that is used to report errors such as dead connections
-
-__Callbacks:__
-
-* `onError(String err)`: called if the stream fails. `err` contains the error message
-
-__Methods:__
-
-* `stopASAP()`: asks to stop the upload as soon as possible. The Connection will also be closed. Use `join()` to wait for the connection to be actually closed
-* `resetUploadCounter()`: resets the counter of the amount of uploaded data
-* `getTotalUploaded()`: returns the amount of uploaded data since the beginning or last reset
-* `join()`: waits for the instance of Uploader to die
-
-### `ping` package
-##### `Pinger` class
-A Thread that uses a Connection to repeatedly ping a specified path.
-
-A ping is defined as the time difference between the moment in which we finish sending the HTTP request, and the moment in which we start receiving the response, over a persistent HTTP connection.
-
-__Constructor:__
-
-```java
-public Pinger(Connection c, String path)
-```
-
-* `c`: the instance of Connection to be used
-* `path`: path on the server where the ping should be done
-
-__Callbacks:__
-
-* `boolean onPong(long ns)`: called after a ping. `ns` is the ping time in nanoseconds. Return true to perform another ping, false to stop.
-* `onError(String err)`: called if something goes wrong. `err` contains the error message
-
-__Methods:__
-
-* `stopASAP()`: stops the pinging as soon as possible. Use `join()` to wait for the thread to die
-
-##### `PingStream` class
-A stream for the ping+jitter test. Manages a Connection and a Pinger, and handles errors according to the specified policy.
-
-__Constructor:__
-
-```java
-public DownloadStream(String server, String path, int pings, String errorHandlingMode, int connectTimeout, int soTimeout, int recvBuffer, int sendBuffer, Logger log)
-```
-
-* `server`: URL to the server where we need to connect
-* `path`: path on the server where the ping can be done
-* `pings`: number of pings to be performed
-* `errorHandlingMode`: what we should do in case of errors:
- * `SpeedtestConfig.ONERROR_FAIL`: Fail immediately
- * `SpeedtestConfig.ONERROR_ATTEMPT_RESTART`: Try to reconnect. If that fails, the stream fails
- * `SpeedtestConfig.ONERROR_MUST_RESTART`: Keep trying to reconnect until we succeed (or stopped)
-* `connectTimeout`, `soTimeout`, `recvBuffer`, `sendBuffer`: settings for the socket created by Connection
-* `log`: instance of Logger that is used to report errors such as dead connections
-
-__Callbacks:__
-
-* `boolean onPong(long ns)`: called after a ping. `ns` is the ping time in nanoseconds. Return true to perform another ping, false to stop.
-* `onError(String err)`: called if the stream fails. `err` contains the error message
-* `onDone()`: called after all the pings are done
-
-__Methods:__
-
-* `stopASAP()`: asks to stop the pinging as soon as possible. The Connection will also be closed. Use `join()` to wait for the connection to be actually closed
-* `join()`: waits for the instance of Pinger to die
-
-### `telemetry` package
-##### `Telemetry` class
-Sends telemetry to the server over an existing Connection. Creates a new Thread to do it. Starts immediately (no need to call `start()`).
-
-__Constructor:__
-
-```java
-public Telemetry(Connection c, String path, String level, String ispinfo, String extra, String dl, String ul, String ping, String jitter, String log)
-```
-
-* `c`: the instance of Connection to be used
-* `path`: path on the server where the IP and ISP info can be fetched
-* `level`: telemetry level
- * `TelemetryConfig.DISABLED`: sends nothing
- * `TelemetryConfig.BASIC`: sends only the results
- * `TelemetryConfig.FULL`: sends results and log
-* `ispinfo`, `extra`, `dl`, `ul`, `ping`, `jitter`, `log`: data to be sent. No null values.
-
-__Callbacks:__
-
-* `onDataReceived(String data)`: if the operation succedes, this is called with the response from the server (1 line)
-* `onError(String err)`: if the operation fails, this is called with an error message
-
-### `serverSelector` package
-##### `TestPoint` class
-Defines a test point where the speedtest can be performed.
-
-__Constructors:__
-
-```java
-public TestPoint(String name, String server, String dlURL, String ulURL, String pingURL, String getIpURL)
-```
-
-* `name`: User friendly name (eg. `"Milan, Italy"`)
-* `server`: URL to the server where LibreSpeed is installed. If it only supports HTTP or HTTPS, specify it; if it supports both, simply use // followed by the address
-* `dlURL`: Path on your server where the download test can be performed (typically `"garbage.php"` or `"backend/garbage.php"`)
-* `urURL`: Path on your server where the upload test can be performed (typically `"empty.php"` or `"backend/empty.php"`)
-* `pingURL`: Path on your server where the ping/jitter test can be performed (typically `"empty.php"` or `"backend/empty.php"`)
-* `getIpURL`: Path on your server where the IP address and ISP info can be fetched (typically `"getIP.php"` or `"backend/getIP.php"`)
-
-```java
-public TestPoint(JSONObject json)
-```
-
-* `json`: JSON object with the same fields as above
-
-The class also has a protected `ping` field, which is used by `ServerSelector`. It is initialized at `-1`, and after running the server selector, it will contain the ping in milliseconds to this test point.
-
-The class provides getters for all fields.
-
-##### `ServerSelector` class
-Uses several parallel PingStream instances to ping a list of test points and find out which ones are online, and which one has the lowest ping.
-
-This class uses 6 parallel streams (see `PARALLELISM`), and pings each server up to 3 times (see `PINGS`); if a ping takes more than 500ms (see `SLOW_THRESHOLD`), no more pings are done. If a server is offline, its `ping` field is set to `-1`, otherwise it will be set to the lowest measured ping for that server. Once all test points have been pinged, the server with the lowest ping is determined and a callback is called.
-
-__Constructor:__
-
-```java
-public ServerSelector(TestPoint[] servers, int timeout)
-```
-
-* `servers`: list of servers. More can be added later. If you want to start with an empty list, use `new TestPoint[0]`, not null.
-* `timeout`: timeout for a ping
-
-__Callbacks:__
-
-* `onServerSelected(TestPoint server)`: called at the end of server selection. `server` is the server with the lowest ping, or null if all test points are offline.
-
-__Methods:__
-
-* `addTestPoint(TestPoint t)` and `addTestPoint(JSONObject o)`: adds a TestPoint to the list of servers
-* `addTestPoints(TestPoint[] servers)` and `addTestPoints(JSONArray a)`: adds a list of TestPoints to the list of servers
-* `getTestPoints()`: returns the list of servers as a TestPoint[]
-* `start()`: starts the server selection process
-* `stopASAP()`: stops the server selection process as soon as possible
-
-### `log` package
-##### `Logger` class
-Implements a very simple logger. Used to store the telemetry generated during the test.
-
-__Constructor:__
-
-```java
-public Logger()
-```
-
-__Methods:__
-
-* `getLog()`: returns the current log
-* `l(String s)`: appends a timestamp in milliseconds and the string `s` to the log`
-
-### `config` package
-##### `SpeedtestConfig` class
-Stores the configuration for the speedtest, and provides some constants used throughout the application.
-
-__Constructors:__
-
-To instantiate with the default configuration (recommended):
-```java
-public SpeedtestConfig()
-```
-
-To instantiate with custom settings:
-```java
-public SpeedtestConfig(int dl_ckSize, int ul_ckSize, int dl_parallelStreams, int ul_parallelStreams, int dl_streamDelay, int ul_streamDelay, double dl_graceTime, double ul_graceTime, int dl_connectTimeout, int dl_soTimeout, int ul_connectTimeout, int ul_soTimeout, int ping_connectTimeout, int ping_soTimeout, int dl_recvBuffer, int dl_sendBuffer, int ul_recvBuffer, int ul_sendBuffer, int ping_recvBuffer, int ping_sendBuffer, String errorHandlingMode, int time_dl_max, int time_ul_max, boolean time_auto, int count_ping, String telemetry_extra, double overheadCompensationFactor, boolean getIP_isp, String getIP_distance, boolean useMebibits, String test_order)
-```
-
-A description for each of these arguments is provided in the Advanced configuration section.
-
-SpeedtestConfig can also be instantiated from a JSON object with the same fields.
-
-Getters and setters for all of the settings are provided. Validity checks are performed every time a setting is changed.
-
-__Public constants:__
-
-* Erorr handling:
- * `ONERROR_FAIL`: `"fail"`
- * `ONERROR_ATTEMPT_RESTART`: `"attempt-restart"`
- * `ONERROR_MUST_RESTART`: `"must-restart"`
-* Distance measurement
- * `DISTANCE_NO`: `"no"`
- * `DISTANCE_KM`: `"km"`
- * `DISTANCE_MILES`: `"mi"`
-
-__Methods:__
-
-* `clone()`: generates a clone of this object
-
-##### `TelemetryConfig` class
-Stores the configuration for the telemetry, and provides some constants used throughout the application.
-
-__Constructors:__
-
-To instantiate with the default settings (telemetry disabled):
-```java
-public TelemetryConfig()
-```
-
-To instantiate with custom settings:
-```java
-public TelemetryConfig(String telemetryLevel, String server, String path, String shareURL)
-```
-
-A description for each of these arguments is provided in the Telemetry and results sharing section.
-
-TelemetryConfig can also be instantiated from a JSON object with the same fields.
-
-Getters are provided for all of the settings.
-
-__Methods:__
-
-* `clone()`: generates a clone of this object
-
-### `worker` package
-##### `SpeedtestWorker` class
-Performs the speedtest using a given TestPoint, SpeedtestConfig and TelemetryConfig. This is the Java equivalent of `speedtest_worker.js`. Creates a Thread. Starts immediately (no need to call `start()`).
-
-__Constructor:__
-
-```java
-public SpeedtestWorker(TestPoint backend, SpeedtestConfig config, TelemetryConfig telemetryConfig)
-```
-
-* `backend`: the TestPoint to use to perform the speedtest
-* `config`: speedtest settings. Use null to use the default settings
-* `telemetryConfig`: telemetry settings. Use null to use the default settings (telemetry disabled)
-
-The GetIP class is used to fetch IP and ISP info.
-
-The download test uses multiple instances of DownloadStream to perform the test. After an initial grace time in which speed data is discarded, speed is measured as the amount of downloaded data over the amount of time it took to download it.
-
-The upload test uses multiple instances of UploadStream to perform the test. After an initial grace time in which speed data is discarded, speed is measured as the amount of uploaded data over the amount of time it took to send it.
-
-The ping test is performed by a single PingStream. The ping reported by the test is the lowest ping measured. Jitter is also calculated as the variance between consecutive pings.
-
-At the end of the test, the Telemetry class is used to send telemetry (if enabled).
-
-Each test can only be ran once.
-
-__Callbacks:__
-
-* `onDownloadUpdate` is called periodically during the download test to report the download speed. `dl` is the current speed in Mbps, `progress` is a number between 0 and 1 representing how close we are to the time limit
-* `onUploadUpdate` is called periodically during the upload test to report the upload speed. `ul` is the current speed in Mbps, `progress` is a number between 0 and 1 representing how close we are to the time limit
-* `onPingJitterUpdate` is called periodically during the ping+jitter test to report ping and jitter times. `ping` is the current ping, `jitter` is the current jitter, `progress` is a number between 0 and 1 representing how close we are to the time limit
-* `onIPInfoUpdate` is called when the IP address and ISP information is received. `ipInfo` is a string combining this information
-* `onTestIDReceived` is called at the end of the test when we receive an ID from the telemetry. `id` is a string containing the test ID that can be used to generate a share link
-* `onEnd` is called at the end of the test
-* `onCriticalFailure` is called if the test fails. `err` is a string containing details about the error
-
-__Methods:__
-
-* `abort()`: aborts the test as soon as possible. Use `join()` to wait for the test to die.
-
-### `Speedtest` class
-This class wraps up everything into a convenient interface that can be used to develop your custom UI.
-
-It is described extensively in the The `Speedtest` class section in the chapter on making a custom UI.
-
-## Contributing
-
-Since this is an open source project, you can modify it.
-
-If you made some changes that you think should make it into the main project, send a Pull Request on GitHub, or contact me at [info@fdossena.com](mailto:info@fdossena.com).
-We don't require you to use a specific coding convention, write the code however you want and we'll change the formatting if necessary.
-
-Donations are also appreciated: you can donate with [PayPal](https://www.paypal.me/sineisochronic) or [Liberapay](https://liberapay.com/fdossena/donate).
-
-## License
-This software is under the GNU LGPL license, Version 3 or newer.
-
-To put it short: you are free to use, study, modify, and redistribute this software and modified versions of it, for free or for money.
-You can also use it in proprietary software but all changes to this software must remain under the same GNU LGPL license.
-
-Contact me at [info@fdossena.com](mailto:info@fdossena.com) for other licensing models.