From e6be739b770d07f442bd22347aba230b26c820b7 Mon Sep 17 00:00:00 2001
From: dosse91
Date: Fri, 31 Jan 2020 07:32:50 +0100
Subject: [PATCH 1/2] Empty branch
---
LICENSE | 165 ----
README.md | 52 -
Speedtest-Android/.gitignore | 14 -
.../.idea/codeStyles/Project.xml | 116 ---
Speedtest-Android/.idea/gradle.xml | 16 -
Speedtest-Android/.idea/misc.xml | 9 -
Speedtest-Android/.idea/runConfigurations.xml | 12 -
Speedtest-Android/.idea/vcs.xml | 6 -
Speedtest-Android/app/.gitignore | 1 -
Speedtest-Android/app/build.gradle | 24 -
Speedtest-Android/app/proguard-rules.pro | 21 -
.../app/src/main/AndroidManifest.xml | 26 -
.../app/src/main/assets/ServerList.json | 10 -
.../app/src/main/assets/SpeedtestConfig.json | 1 -
.../app/src/main/assets/TelemetryConfig.json | 1 -
.../app/src/main/assets/privacy_en.html | 54 --
.../fdossena/speedtest/core/Speedtest.java | 203 ----
.../speedtest/core/base/Connection.java | 237 -----
.../fdossena/speedtest/core/base/Utils.java | 18 -
.../core/config/SpeedtestConfig.java | 415 --------
.../core/config/TelemetryConfig.java | 55 --
.../core/download/DownloadStream.java | 106 --
.../speedtest/core/download/Downloader.java | 73 --
.../fdossena/speedtest/core/getIP/GetIP.java | 59 --
.../fdossena/speedtest/core/log/Logger.java | 18 -
.../speedtest/core/ping/PingStream.java | 99 --
.../fdossena/speedtest/core/ping/Pinger.java | 47 -
.../core/serverSelector/ServerSelector.java | 115 ---
.../core/serverSelector/TestPoint.java | 65 --
.../speedtest/core/telemetry/Telemetry.java | 74 --
.../speedtest/core/upload/UploadStream.java | 105 --
.../speedtest/core/upload/Uploader.java | 73 --
.../core/worker/SpeedtestWorker.java | 277 ------
.../com/fdossena/speedtest/ui/GaugeView.java | 122 ---
.../fdossena/speedtest/ui/MainActivity.java | 511 ----------
.../app/src/main/res/drawable/ic_launcher.png | Bin 20326 -> 0 bytes
.../app/src/main/res/drawable/logo.png | Bin 37296 -> 0 bytes
.../app/src/main/res/drawable/logo_inapp.png | Bin 37343 -> 0 bytes
.../src/main/res/drawable/testbackground.png | Bin 278762 -> 0 bytes
.../app/src/main/res/layout/activity_main.xml | 536 -----------
.../app/src/main/res/values-v21/styles.xml | 15 -
.../app/src/main/res/values/attrs.xml | 11 -
.../app/src/main/res/values/colors.xml | 26 -
.../app/src/main/res/values/dimens.xml | 42 -
.../app/src/main/res/values/strings.xml | 26 -
.../app/src/main/res/values/styles.xml | 15 -
Speedtest-Android/build.gradle | 27 -
Speedtest-Android/gradle.properties | 15 -
.../gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 0 bytes
.../gradle/wrapper/gradle-wrapper.properties | 6 -
Speedtest-Android/gradlew | 172 ----
Speedtest-Android/gradlew.bat | 84 --
Speedtest-Android/settings.gradle | 2 -
doc.md | 907 ------------------
54 files changed, 5084 deletions(-)
delete mode 100644 LICENSE
delete mode 100644 README.md
delete mode 100644 Speedtest-Android/.gitignore
delete mode 100644 Speedtest-Android/.idea/codeStyles/Project.xml
delete mode 100644 Speedtest-Android/.idea/gradle.xml
delete mode 100644 Speedtest-Android/.idea/misc.xml
delete mode 100644 Speedtest-Android/.idea/runConfigurations.xml
delete mode 100644 Speedtest-Android/.idea/vcs.xml
delete mode 100644 Speedtest-Android/app/.gitignore
delete mode 100644 Speedtest-Android/app/build.gradle
delete mode 100644 Speedtest-Android/app/proguard-rules.pro
delete mode 100644 Speedtest-Android/app/src/main/AndroidManifest.xml
delete mode 100644 Speedtest-Android/app/src/main/assets/ServerList.json
delete mode 100644 Speedtest-Android/app/src/main/assets/SpeedtestConfig.json
delete mode 100644 Speedtest-Android/app/src/main/assets/TelemetryConfig.json
delete mode 100644 Speedtest-Android/app/src/main/assets/privacy_en.html
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/Speedtest.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/base/Connection.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/base/Utils.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/config/SpeedtestConfig.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/config/TelemetryConfig.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/download/DownloadStream.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/download/Downloader.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/getIP/GetIP.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/log/Logger.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/ping/PingStream.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/ping/Pinger.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/serverSelector/ServerSelector.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/serverSelector/TestPoint.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/telemetry/Telemetry.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/upload/UploadStream.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/upload/Uploader.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/core/worker/SpeedtestWorker.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/ui/GaugeView.java
delete mode 100644 Speedtest-Android/app/src/main/java/com/fdossena/speedtest/ui/MainActivity.java
delete mode 100644 Speedtest-Android/app/src/main/res/drawable/ic_launcher.png
delete mode 100644 Speedtest-Android/app/src/main/res/drawable/logo.png
delete mode 100644 Speedtest-Android/app/src/main/res/drawable/logo_inapp.png
delete mode 100644 Speedtest-Android/app/src/main/res/drawable/testbackground.png
delete mode 100644 Speedtest-Android/app/src/main/res/layout/activity_main.xml
delete mode 100644 Speedtest-Android/app/src/main/res/values-v21/styles.xml
delete mode 100644 Speedtest-Android/app/src/main/res/values/attrs.xml
delete mode 100644 Speedtest-Android/app/src/main/res/values/colors.xml
delete mode 100644 Speedtest-Android/app/src/main/res/values/dimens.xml
delete mode 100644 Speedtest-Android/app/src/main/res/values/strings.xml
delete mode 100644 Speedtest-Android/app/src/main/res/values/styles.xml
delete mode 100644 Speedtest-Android/build.gradle
delete mode 100644 Speedtest-Android/gradle.properties
delete mode 100644 Speedtest-Android/gradle/wrapper/gradle-wrapper.jar
delete mode 100644 Speedtest-Android/gradle/wrapper/gradle-wrapper.properties
delete mode 100755 Speedtest-Android/gradlew
delete mode 100644 Speedtest-Android/gradlew.bat
delete mode 100644 Speedtest-Android/settings.gradle
delete mode 100644 doc.md
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.
- Contact this email address for all deletion requests: TO BE FILLED BY DEVELOPER .
-
-
-
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=Y-Q9u$(%s$C-6h>fcL_)dNK2Q1bazXGfOL0vcir>9cP=wK%P{P>
zC%(6y=XWBM6{S&;h>!pPK>Z{mp$Y&H@K*?c00+M5JO8!-03<$ZadBm3D@RvH7c0jv
z6raS!DZV&6T3Fke1At6KX0nI6`W#-^c)N#p?kEe+NAa_EREl=);z^0mKM9R#q0!hm
zs^8^7VM<2|ouQ85_iWhs0rt8j5drHb0xuu_NcKe;6
z<(c1X3-F`urMb;j8!3<@E0BD1M>h6*WTEL31mzrn{tFn_u(3ml3HAia2$4Bl%g15g
zQz8U%d-T!++|g~p`>yA2pVE@L@xv!!BIdu24w*$XDfbpHd_}4Dlj@zm$6@WEWxbMw
z$Ej}niH*N-UOP_7AC;QfZyKfTyNlvZr1w{lay9M4b~wQWzLN@GN&@4i9_6e&ZlROp
zvnEM;|7Q!RK+1>{C^Y&{y@iz27d>Lj5^XpD$;S)&n72sS43eOGRfhw@W?u)B_`r&h
z$^7(%MYjFbjAQi}5o4TQvjUa}yB0lfUATl4pIx6<=zSVtausNpez~x=%JO?@y#m7Uvr!K9#
z+=#sopC|^a*jb0h#<+H!wfi@}zUICioO55#+
z@%{HPtjk)wyWVlE90)`PicT4z6M^1`qB2Ia3l#hUN7jW8^OXeyRuh6J17#hEULz_r
zj}R$>I1h0TwoyhV62Z2D(1^hEpfUxHtn)QN9D?DEVZI0N8N;+IY8t8z-YqvxpTOp}z@df_uSpf#j5UAM;*(lBPM%L`H1Vee(09
z*QDSiJzHv-f;+Wz0_^t5Hua_CrRk*}Vq{2lNGyz=NxMX}L~HMlUgS@0zmk7NZPN`2
z49N~9#6!nVsd|32|H%EZaGI}}s8~(4N40hm$C~{>az-$3K4Az?Up)6(am?2DOR%9)RA0};}!R+
zyp-Ir#!ZW?m93Et9o9+us1aF`Pp4Hcc9U_)-omgFnk8GAclXb7I&r#fCd9_g=Edgf
zkneD026c`<&DZtPHO-CeFy1ZGRqJT>Xmzq@pY>Yx`u1q$U)W9gb;7mnH9U$jv@LW+w|}sx
zcwKJ!yYZ^kFZ1DM+a`(T({>y-^6aJn)?GoWj)*5_%%YTH*IHauF{n~pX*RA<#p
z)J4=TShD}kVf#u@@SVQFN>6L)rlH7wZ9!)V@mO*G#lq1n+azGx-??~9b!_rmfhpP{
zntT{v7^ZZKX~vf5045D5O`%%*6t*R*WrIupRY+8pT+Ik;3Vualh3_2K94|M2W4*)P
z3Z@71oyna)I$`V%*$i2%;EW))V7j1XTUpzzXXc%h&y&~LHQBAo?dJ!MhrjoW_Z2rM
z7gP_Sj~Y*c7jt)ePad#ckPb*I6dBW$Zol3vCIXZnln1O3(j=lT>9r{fH4kkQ^}Tht
zj*P#IBkczcu4}9Pbe6fQnkx7aw-n?Qk1)A!10md@|@-{=`-B)UHACgbF9g|U(o3C~VO1WZ`SWyp|3Sg%2Z+9$Z_Ih!|Gd|By{v<-{TSJTx$O}RT<-YRPQeiu|E&FVUnkl>RA
z-1;rwc_`jz?h6g^EL;Z9qCYLt+V`C(`#3zwOREmZSgP->e_T^rF}O9b7bbf1ysuk4
zNMo+!uBBvPs(;kv;Md+hUQ&5NQ<}Z_SpKvGZwJTcIKR42q|fK+K4`uFN8v6##qnXe
z#ouS)q?>=_Y?(BMkgR#6gWrq!W_9FA=WdVO&NJL!`&s5tWyiE&;70i9Wtq=J(Ai?z
zukP&AegeE{S@mA0+3V%!w)2k<);l4G5>vk(whumz7ZZ}wqu-L?Po
zyXbKo>%1A282jG2-Kl@yex3BEw5O8M$YUICRLDQ!*22HyzWmP;)fvH=_nLHD_RsOx
zz!&8kl{3>hnocEj{7F2#S6JwuSI-|j@Z7*=63#?US^~fXoPlEfwme|~pa4Eeh^l)o
zo@9D?V$VDdbS4NYO#M3OpssVx4^8$%X;P1
zjsPg|EDbxI!GMI%W;=!e5ndpf2w$KY;1Byn){#Ry0%%%2z=kZNtm6T507k;A^cQgw
zAOVEDLg%E8z6Y5mVrYKF|uWni2lm)l%*#J36S#QAyeyH5aNrZJ_VD&hn7%~Gr7C;j?1GsK4U?T*W09E>q9KI9u
zuoSwb$k69xfgiVZuo3KQ(7VLiCzO{-ji~pj_p+fW(s_h~{xPWmX7%^qEn|VadOjn7
z_=`1Twg`pmcLRWIf*93}j0Tv{U7-Mspu#D~<3#p3V1dHJ=x?sEsZbKOZg&8WXhyck
zx&lrhHlxsvbBMwE^Ei|?dVHl4MH3QhAU~%Kr`nuyKrdqvxJ)KSmj!_SV^5O8m5?0j
zXDK(4RU;t;Tm}o^2mVrzhl%W~ssg>X(}#X;s5fnrfzEMP0ch)7mv#st4qLW>$3Zll
zPi!=x`|X7vSQgEK5BgW$3kje4J!3S>IbEA+A96<5W$}a4A1b$~iMR`1kPXTz;+FZt
zc%aDs!p}gd@QTf305y32rfM@+hFHhHZ`i3>+S=PkaU@v-o?92}Zu6{ljk!|WtkVkrTClU~p5;J9-Wt%zF%5R`
zQ?U2$P@bP(%5^g+Aczu_+k=zBJ+{2=vv|s4R7o1%NxLnavR;Co)J~+Y2hi
zfLk-JSR6uib#*~-7#d@&5`jpVv;?g>od%Djijbg_Aj61mT3Q-X6cRFVEgO5t-gqYR
z;OJ=dm-&HMyTbypx
zkY8P`lARPcaldr2RZ?rID9z2y9W^+0?36V{jLKY34=~0BPz`=;UqO>p36F@T&vxy(
z>FfCxtNh!~&vY@h&>#lg-U$m$7kmw_KU_5j*M`Jz?nA+TBdb=-Tm0x>>b$NH3*iICJEqidfy_tk^?
z5iB<&g~()lKN$B7eot^22M34ahj#u}VoFkGK)@E9hOUjx`|58agEPda7g`7+b($#%
z6v6Xh!dn&Xr+<241t^tiaejG5B3s$m46i+QTil*23HM>yHif)zpZMX`3^2f-!&NKn
z5eiz^bw7t9X9@`k{mOTTL`O$^5wH45Uq3})OK3k-i#c+Xb?5{BP%K&VglbWS?_-rTrKAX|rHbx#9699g)d1c203uPv>k|zB*Vj=cp{oF|
z7gLjO7`Cp$Ud_$LZ(ch)O^b`V@inWgB!KU=RPYTtgaCdWp&FT6f)bO=e>-kEVs4Ek
zP~yf1FWFoH9{qTH<*^%HoCFP?6~W^*mzyZ&RR!Icp&FhW?gI_JU)>e<)-PfQ60wZV
zy>xDr8L!#9u4}O6;<6ut#;6kRZb^4w8tT(is7j!LFSh#
zy`;*IAv1U8(66&r9CcFpxlqu-?>FM&hGojRcqk}D-eEL#8u60E)XW)qEhKwC*2P?}
zay9306Pf(BDp!n@8$eR&rPdm|A=hhQa69JQf@{t9v$3@;Kd`pOA1}(brTcdziMz8y
zR60WxQ?Vqv|3l{e4kRc@quR-7EGRfW=@T@()FV7hlPN|Fulu&l;$q`0N^Yj&WU3xw
zHt<6Y%>Q^PkpUR5VxAHbqos~z67)~yGU@xI}n9GhBU
z*q~{V)Oz>&
zlwZe}X{4ktZf-8Q8#zFJwl+Qe{=;kRf_j}MH+ffohh6Bus;U<5xsEA@Bw4DKDO&N&jcX3Ly^2Ky4vM3wvtim{Z#-^ar
zZZ)If@n){g#l`uN6=f@02(5=@wV9XqosZDkioTjb0xt_Mukw2=H?JCqq{`(Hwfv7+
zb*&?2Je=#9KU4!D;L1>eM~{n{N~wIDE~{T*FZREoDxmIyses6wBJsbOzoIXJ!fI+9f1~eA-NM7EP9#y?=P-kan7#_CW5p}
z#iw^%v^-5-J-g^_yh!QAxgD!NQ&N_E5AW8JXky4?SySZ=YC85xC(MLjj$Jyy%-bTM
z>Ab$=ILYO>{Btr4#@H>JuX?B33*DpnYU#xNHnVZ|7n!g-18krizrBg7>ctKBsydhF
zzH+^4@i96@HT%inj&szIS^N?uc6t}fx#AN2rd=W?||*MndML6=3(H9>aSSaN=@>q>D4t?K&-n!{J86}O%wS#uQ6V}&2XW|}}%
zXg^!SKW#YRm+HrC=oc89dOrU@zdrTgGkKNOAmLV2SEv0lSjkreYL50m&<^$tGs+4vF)m?LF;R~W-0sI
zm-ALn{l&gMWODK(SimZDG5dD*9ulZd=@!~FAHe(UQpy@jzRT{lWXWc^38exyH;N>}
zLfb(Lwbzo8p_~Rh2H!g!{)Duxo|3oo!C~N&8@0Ih4OdlQwVZW6FSKEV~2+$^=pS*`($j55$L`qhNG%N8U6jNtR8z+Ns{LqWB98s^Mxs426u4|#*j|Rg(hvK0nzSmROa^EmPA{#HmZM$G=>H@
zCWrkuN1HmyH-~oo(C{Dd9Tyq|EqA8ICF*(JvTFmM$UM)*Y&%_!2jeWVQpWM{@nPqo
zpET;fYs18d*eihjZtonEy@E=z(ZF-Lx>g7gH1~3!O~B7_GwQIsvMKIAHJ78r+|+F?
zhkfd4g$Ao(w2|@U-ZvW7%F4=Am;kyMQwsl@w}(NyGOg>ww}jnYi>K#ibn|RJubL@-
zxF<%_uGJ8MOc>=G}&sTd+nrI+k!&U&wKD_NSvFQ3K1)`{|(s@m=9-
zV|P>2{Lz_lJzAW{axV{q@)WTR3aE96GEV5+ntsBEgrT|$ht{&PGXK>Vmtk958$QnM
z#38e5wQ(Vz-O>6pE3uwGaP=8bZ(_{ZxV!AS9hYA3ChXP1
z-|r|XTJUIyh=lipn|+o(>7Cal6zlt}rZ?Ox&QT+oZ`Z{M_(lfxbU)1(&<3#8>FRI#
zv(6$ka(Q9mG@DDhcX-q4ndh-&Ado2p~&4I
z`ud29rU((%9h*F@uVYslqWUcYTmPZiz#m*P(ug{Ev(gE;;D3-223+`uL%1#gPS`Too6
zl4C^-%)bpNtgTlk`&ZMEWNR2G(IQh!-US`YES+ZO{hfeL+wl}!s2~wDT3VLp=PRYj
zwSXtamco{_S`pkfua;z71WrJN)m3A<=5vM?8#sH)=l6gA*mT=+aqF+5@=~U0yLEiv
zVYFfALvA*i_ww&xo1P{(@`I6TG8L@4CCS3Yea+6d0DTXG@AOa5BQ-a%e>3TSmzA9#
zz`l02XdwWSD)V*bw#l7Qr4P@SSK|-1b8W@q@E#~gl9UWa4`zph6Zv6HIXyaif7_(z
zmzNnj2%Ey|XBa-WzQ=f#e1B>32PVANSu@fns2dxmhSRGrSp&+lbzby`&Rr@wyJ
zV3#me{KKVy+o=(Jaku<{;0n$3*UFv{^W2S*AsSSu)yuhnHB=(z@i(twF^&3cyttfy6;k=
zgjR#smkZ$pjHD}PoC4Gdr2@4+$j
z+fRAw6gX1(CyIR>3I7JNB9cf383J1)VtKh9J>kmen
zVvcV+S?4Y+DY7rrQ8t|6a}&Cv3?trxch_o@kF#<~KuTHG|Ul%!(hN{y|&0wH!vw
z8J*SSRxg-23shUFFe*%urnNGAnH(&jo;i-;)xU!(HN2n0NEmVjovBNuFtmcP7N4}Y
zQ-f@@!^pbX&e1!+;L5=#kH>Fxt=Ih4#-q&Y!}}gELTh`fmVmy-N{RfS;REW74Tc6a
z;PU0CK!Bw1r?A}(4uvPdYOBwg;MeMCuGMeRpiJK!X-UH55a-e&z4;&P=;oN(_G)?Y
z8~0(y?rAgN*-KY^?o(6BZU+l^_bUW~Sh{BDQ&AtxPKpMvDRAKljMS8Oc$Jiv)+P%)
zhFh;JHipfg+&X;XG~FJiA5NGs9CqNf_-hvSq}4On{TnQ
zxJMQ|NKdv^-A97zj=b{
zE=h=5b~Sl`FNcgflf2Xm?`1j(1<|vw>wpwgQW9>&SzCR-xwuD?EUCxb%G5YEWFKzZ
znOD$fj3gx~gYUnL2i`Z-)4?+a@8gR));WEbzO(nJBs}-k1tYq+L15vC0B7Ano%P9}
zq$s?<9=`Q^QN5>zat&d9Ne!gL;q)hCwx6}vS4^jK_=RyexR#tS0U=(wg2s9UPy4Ue
zc5Pxmd>_`{*kuwYCl0#6zz!pM>2n(ip0XT6x#~p~7mL>QgAQcPa_&}QFmmEjEj>Al
zYyfXndbZ!;CuSgn>a}+7FR#Z)d1HO*O5xEQvjk;QmYkAYtJp7p;R`*49u}R-_GeXJ
zQ}N(3nqy1ZEVHWK?+9=so0sIDnqTw&eyLbOrz>U^uYN%(XyA=&zeSiilsRs;`m`AR
zn#UFDoui6zY#is*5VxVf`gFJEpPfM=<@<`>EMunX`Q5EZci8bErHi(S!gl+wXBGFiW6v
z6%Wr6ULn7pT+b?_e4t_k@$dzEI&ijM99=q`y$~*9QQ@N@&PTE@?b+v0b7y8cnh)2*
zxH}b*@ZU)Uy^UpLWZG#S5~UHlCUNPJ*Ymp8cB6^M+|BeHeg6bbhQkbSzh;n>xv?4Zk|Euj*ZyqHX)@7cn}
zTBg(cu4y%G9}Q%z`qO!|%*N4uo^Y9n-;cRgLsGU=h-bB7?epTedhvjV+uhx6djE=v
zNtjB4O{^yh)78fia9NCX&d~BO!15ewnQad>C?Tos#xMD2rZ5?{T0>9|uviks}
zF?wW{h#-NVw-q&h^chq%&UOzwDAj-Cn6eX<4&auNMfDU)^N9%@GK+jY?|gF`Qm0^G
zST>b{ki7$tQv`P7KfC^yqPd@Lo!xgqCFM{$ns91^vHd?W5Dc2w#sxbEb0a-vS8F}T
zH(~!{$jh?LuaM5;;^hlc74cfO@pVh@iD6FpAScAdadyyvJB
zQZ04+l$1SZ&mml6<2eH+Zj*LTXa
zwj9SZWT0Kw&)fwI(J(Fo4C(Mc7rx9LIXoevZN;9~PDByo0BIj8Ku`Jm3Tb6~fA_FO
zMyP*ffC2`r1mL-zuetz7O><>k;JQ}A^$jmXiXzNm*=escG@b7vqIEw1J0hM3{|*|K~oVSo~;
zL&^r-#*bW~xqr&W~5JWr27aOaw(%Tteqk_5b+
zaGzw2dt{sH%=X@R%$EDLZkvdT=I}2~ebjntOoBcih@BeI+8Vq2F*_Ji&dJ9!08Nl%
zEuOpOYeUuZu#R0jgmZJV+Me1L2H<@7Pp$edfo5E=`2T@f<)5}n>u)I6!G
zaI&|T?f(}XdoU}Sz|f~Y0DB!Y|A)Q+V({Qt0pB_|xz8Hr1z2Q(qm@S3VU2GE3Zf7d
zGADvvSoW>X{OLGY0FS62Hnm|Be7&Pi!)?x+S~|I{%0?p{-u20h)3wiwjg8Gj0Hmpv
zQsWWvGjzdXt2vgva=pgLOP>V_#j3?^HX#pxi`80u&QdWOz*2ndXXKxXfZkm=!FB=*
zyxmtnNU>!(8`+}9Bzi>0{JkjpyO@A_tqV#xh8ZI
zgF*?Aq`LM*w^6+j?v1B#aB=iHJskh|-18mOlrQoJVn}Z{2KY~+$O4|f%TwFB9Pd>v
z6<6N+#sTPmHc0pT++fy~;YI{^ItTzgIg*ImQki5`5ne-N^~_hM$!yZ24}^u-$k<_n
zy&3V5M8rfU>|@!?8$Ov^G>X@hP+gGd>};LKa!&XH|J0TGX
z-%jUmbf%zP_Og=doFy7*{zSIFz;Zw{375?;RdO%arUd4_jT@yt=5uxNeR@YKwGm+6
z|2n=FcEyXJCww31Vc5Ai$E~=vXGsn9Z}t4G;Un*Q^U=5&8}H@SB@gO2c0#-?Xv0#UZc~{oua;7D#a%@U;RvT6i
zQBX^|dgkAd>9m3_Io0qalmwj}$m4+D^P2sVUno74-3>fs4-OQBj0hrvueHDezJ%{^s>G
z7A_iW(=BhXapO}gMKy_d@7iCcW1}R15x;kSKKlPGfTpy11R);jL#|&vZkBsClyZ~N
z4lYAp*GDRsAMyes?{au%ET2xh{8x)0rJzrTixirGAVLh90BQQ?0=uPP12|^J=CpVg
za3{#l&VT6##-LL>4N{=m)cEwq+Qm$&z=;qkLsTXz-dK_!t%!g2aFm+ssvVmoJ}%zn
z$_fn$r-i%_W+2y3&zGAv{uM2sj`c(uMHL)s#k5}DnfZ+`>b_4m9q&CU6x{~Y-eq*e
zWqFp)>pRMR?s^fKJPSUdLVT<+n|cYF?spr!=uF|e^m_^tOq@h#(9fcQ65cGvea
z_uv$g#FwcU>xv|WxxR9-cA@3D*}kZqyf|mZ`sJmqF)&g#K9X0#0~FfEo(AuJHZ`$O
z0_7MuB+Cpq5nu1?R5Uebt?G4oNzdGtPTsbCvxrA(k_B5m&_lxh$h1TAzw!@@D7CLe
zyA?vL_Kv$d+2nTmifw)@*v;bN<6h7`G2%sugIWzyIH2Eu9!KSsz2?9F6r2(E2I|~=
z6(_s9W|pa=7P0#HrhU$Ei8)(GkBME`xI+SL@Ofz#+puUfdw*M<46(+Tv&aMPgYGzt
z=fg3jD1j7Kn`tZhvyHcj{0u1X;CQwFoa4QXQbAN=p+8bE>uh-iQ6dXP+||+>n6+@vjWarQ<@WmWv-{M}(ji~^ZMeU@JZm!}
z7Uj^4)YM$N?0G6^wYRm^fll)~sDEN_KYyB>%fAWw9~00>YZOnrH|vjiy>_0JR#FP;
z09G1JIai&;)aTniZ*>!YboO6Dz7h~hIz@VAJioxK#l=xDz03W9Ac72?kWhNx;B;z7
zRD`n`moM$2*b9nx=Ah6cO2!C8j*;I<(m(qOJgmm1+VjD!6V>C#{*dEoUznPj^eif+
z0nJc3n4C~Z#HY5A3Uv()XYbP3)?3{%{+jDY;-6H9uM7(0kl>8yy$a|
zm&uwgN46Te9fY|Th=>kx>YI3SdYG7)Z6$~5>gqqicKo{Gah#sH2u1eO5HYWftKXkM
zh#`@1r$3HlJ>MI@^F4m5G=HG_0U+tN+JLjuBiojl*uG%e2={8&klQISoh-tk0fOQs
zGoJUNwl0K{N?CdDKd-ra5?CwDZ7Bdj9ElJj}=lJ;VUojxV27!ZN%r)yYx3z9ZsZe2$De~
zprTi9FZK6*g-SuvQBzB}2R9qp$ulEVS1U$=T7Iop3n^N<{Yx6bY(W_5_TJ8=12Yd#
zQ%RBi)D~3EjExt5*6)gvOSlc$J&n+ahek{~>g2Zgk`lVro!uTPN}H!PKte+5vLeBP
zE1kCwqj$4oCWWwBx33e`$jzvRaE{D`HQmedO46oYu!FN{z!V4y5^YNc6sWO#Y8Z-Q
zI?SKY+Vz?4*wf`TI5{~L3JN~Nhy|Ze~^XLmeNfR4n0BBYqaBUH>jEEjKv-CmxZt
zJVD58ge=mW14sJ7vW){Ts9E#0xB&y0my4?LgVe(yK&?+*y1>c?UTLme*3{U5u39D;
z;_n%oApZG6OIVTeT~4OTl9El=$Y>FYKxJCHvu(W~Z3eUnt7m}@h6XiKox_?2^$l|}
z#&0RB7@x5yezglRL!S94fVSOp*#gz+Jy?;AlhLD5RW*aYkKb}MOHcIA=4`4tJlg=P
zI@AhVxxI_qDrh$nQn7B%@y3phm^q|~6tQPDq9!R@imw_$;{>mIA6%mmcMb?Y)sA(GoLUoUf5vN5|v<(1F2#>{El(01vU=
zdmOL(m6~#g7(Sg@e}Z2p<5gts8?y5dh!qr%*hysKI=k1WlB}N`nw6RiD
zLnP+dM&v%8%E8rIRwZ!x|{jB^B%y82yw)y8aP?pq5@h+`{_?K
zw~992o7~*oU{#1SHS3=q9grFpzI`~gAuXHeU%yi&_Ghj)n0bIyG1_}^=yN}?h+xmb
z1PBKN7&0q|^?W|Pf-_ZMY)J&|SRLJ9}q)eYIyx
z5da7G#iBAM7SV9?uY2gbu^5};`+QMy5mk3Xdb^6@oObAVzMEUeis2uLnM#KWT)7aO
z;2WmGbw8M6+^AraKo>$zVc`J7ok=31!-K2_=~yKQ-SCJPtSX1UzbcT?&~Xb+L(3Nr
zJEJmHb^~`;wRXXW=Qj36wOa7E%Iu<|qHQ)znp!u{8zX{DKxk-a+vK~Ckzchbi=lGM
zdD~*gE01jXW*axoAKd(+yY%zlW<(wSRK
zDrtjb#!L;>G`s?Vj$8!erR=pDyV~)KJERW<)++5guVJG4>!`y8SN4tt@gBTSQTWK-
z0v(fEtbsL?lR$^PgiZbKV%nG>daiQ#&-wY9&s0*0Xk=_5Na(Q$cyjM^fk#1(Jt12I
z`|>hwo}jvW`NaJtOCqATi+NsM!i?n-#DJ>nW
zK`MkMNs6}zSrp!#3zzvl$Mm1O#YiON;Kw##T3yR$L)ETu$}0&G5xUp9xLAMfw4qx(
z%#eiDS>06Mw@tM`^Ddv34kiUj>NUE0tA8h;*5j;ebrVHQEou0!t9GbNW>Lfk{&{$C
zx&WXJsuxn9*XR7^ycqAZr0mB-&jp;r0*Dy7!7O6%?;Okvj3F-2^HKQg9GL|fCGtE^
zoWVPFmhV4z0Ucglpf4zU21YQ5XIRwY$`wp1!e?N?p{(l<-CUTDOu-lYLfN}qU^N|B
z>{w`RoEtUzSfucwgt-Hj_VmW(rdx~mFc_+BItMFE^!w*Bg5ob3zOgmm!HnJ&6zr)H
z<34|a%b{?0?_8^A=UrS@w$7Oy854mBKfusw6id33!HuyZObrDSEBVD5>0j*8UO+xc
zMkXIMQ;gQy@AC3;cYNX-9$0*)bKUHOksTf6kjZ@Nd?l+79v
z2}gknwEeLVhnS|eu0|P$GqmGu*x|Av))5W%q^W!Co?7ewBqg0xr*{jfLGxmhWTW#X
zrZ}`WNg&}!=X9he+W5x3D#KYdFE8J#6NhbbsvR6Ce`IFjjx|PaP)U6mqZ#}=xGu0m
zz-Q-4>+}Kry9*E=Dl>3%wb$jR_jry;AOq}FkaZBM=S2{7=D6#aB?9?SSvgsC3Eg
zF?ad1F#wP6$Ch(=jkNR2F^CFoXLk5J&$9|s0$L?`c%(~J&aj}O5nVtVsTLP{?PC2O
z3v#vmI=keEjvFD-+qz8hk=C1`H$yFT)c6Kp)Ww@n8ONNQ*V`3kT<+I+QPCY0)E7d{
zQVnH2z4U=1)CdVNNeQXlFPU)9_dRzRd$%UlJXV+XYJ;?0qp5#nr}(RGW6wVF@9hUz
zS!p-OQ*Z6CgIeo@W@!>7)W1WHb?#M{{B(!r&@1QlQzc=ATbwQ$z))h%XqdC2aZK
zT?ge3jZ1Pui!}wQO{q_RfcyBGBz}mZXuIz}Ud^fut{d=_IvoD=!gS2tgUUB$1W*_B
zA8(sh=yie&f{R~YSy@x{h2OdhqqP`i_P_AZ39WW8`pvFP{pnbGv;W%4u!fQs>%$kHLfd{v}pl2rK#s$;lo{_U}9BD6Qx*7l%=uIO%|c?F-&MV&5R
z_^#K_8{ML6lQGeJ-$C2{uKJQa2t$VX>n_Q2>uWzTJ#FM>y-}$7!nZZ0jD01ToOUmg
z9twVXx$K{by|#m#rn6MRS_N)-G%vI2wY_U5f!hG?&kEd4X
z@bKTi`I%!Irxw_a0mCUb00n2p!Y_*pg%mLLB*xP97+`NYBO@
zVb_mS*>%HK42K&to3HS%!-z?`WX`qsA7X`wdRj4yYAL}JBEp}rh$
zpS}FGs15R9px{s!^z99IovcLECt$PF_O{X{IQvCfI%6Z?f2z&3=DmbPaThwC$UK`PgvY&VbE&<9$lUwgY1=}((;t$14nv)
zoBrJ2-%sGtHphRO=I-$LH`Sh66jXsU7<}wL$?Cmj{Y+H9e~v0$WY9UOTC%UTl%F#=
zA#(StE+WC1*|oLZnWT}B3eFw5bB&PVExjx5;&v9wm|I9;FigA%4B|wN=}9<{8Dv$mn9`v|GBsc@7#e^Z)qM
zbzxcyfpK3a7(>7finx85x6?!RAJ{z&Jn9>~~1Iz*=3+MEIyzKKp0f<3#rM*V7#>B*&WQ
ztm6f5tK@c9;M`bNMh4;I&5AT4653eXSrEMbZqEH{GgmmmPmPfrUfv}Sa8f?kv+6By4;XjNPI&z|zI1gMYcxZnkT;y?pVduvw27+>VPpy&4cvpvmj99C2>VljsFM(iZ%xk09JF0ptDOn{^ah63)|Q71so)!Z-B
zT<6I}Nx0yPr*gkhB`yFoX)9yg&)^%Gwbm#whl)M_PZ4Jx4`tWJ@!J?n;g!fP6D2$u
z_1KppPb6DeLW9RrmJ-rfvJYinOO~>ZHQU&Vu|OEL!U>HWO#=l%cO
z=W}N6`&{Q-zw7tCCRq#H$bQPX>h4cI#tLHPuYW`h=vSt8)=mN+Y_xR%d@E
zxQSi>5_eG(3a;ojI+kO=qRttwlRS8)+t$P+^9byR=l-!-{_#-NrPyMNOSi!nkF21H
zr|23FtE=rtX%C&oH$teaav?Z4UgA3b2C@
zcodxC=h5Ca_xh7_bU=d=(3Bspf2_C9{bP|XBg?=2dzhg_GG1v^|DAD}2Xwipe*8|5
zO;#TETf!;iBH}h!ii($Qik=5q+0V&+OWweUWP{lxT
z_O*&tM+r3w6%Ii=d`P$po!*~!u;<&5|1SYmdFAu+^2dAJt+yC-qsY7JJlbzWZJI4L
z+&bb4a4yGyNy$0{MtH^m>{0@oziaUbKG;Uj-N-2I!S3N~Bh_ID7t$k<+!;qW^ye)x
zL*W;)d`Zdf_-g%x$#102TLF8k^~`(b2fHW+yL-GnR}anffLYPU;=SluuIar=H*z~z
z$;FcPt2YYF1Kf!}MX)&Kf65ddm6p|J2hd7hQ+Rf$axe%e0^FcPJU03G&!wikpayHC
z|7=CQDX_`PCg(|Vdv>k~ZS^#I7jK}G@rri;L~
z*uzY7u#O^q6?(KQ}UB7h59DSpIo~#Uf
zaTh`k$3FZC?9SK6@w4Du=-*FNACtH5FTmrDkYK^hUY)1ISyFdrfF29!+b)~^UPkPK
zM)^}m1#BJvUZU%LpB37rnIS3DZWEqCUF)~ox~2QTs8+Q0%ge&WSGjfYkpeRFxT-Nx
zMFX+~xXT(SS}wb085EjW4ZDq*<=v6YYnnYUkVu|wUH8yCo?p!2woKg>bElNfW&pQB
zvcmDpn=G_^q)FTRv@2QUWL}z{iV`32~>Ob%;o@LbZzI%6JHkR-6&v2IzA@$pUHz7jhYh_ut
z5vQhX%gnPxlqDOiYFmKLdbspOts8_+^5w{OuJ0EYe>r&W$16=*tKMHI55-X$hR?nc
z5OB7Awl@07OV)PLBXa(KXvh)o+k*`{x_;b;tm(D=jn!~KnMRatXz~euT=woAKPzT=
zzQ~CY#wZ7F9Y|pfPZKQ*on5lS@vv>q!PKE)C*o3W-iO9F&^3uVsKXjf3U~Y?haI?n
z2Dv9~jg9->9}L`n8VpqZuTHhvKTGRdZkRkJ;XNJYoVsH;S=m=!Ix40*W(?u{YUy96
z+piSVGeS-P0-AULO`fu{76WqqQGEUIke{F5F13>?*-I^J{k2jX>ulNszgJCCS~Dt5!(u@v9iD&4$$pvV#lSGzyUO#rOPa~d$|e|_+0{i+<;s|`KnmRx4$=0y@M
zc<&%Mq8I`kFp;G+EIHYJ*&KpT`
zLZiBziYJ_$ABJx5
zMJelw1N3s2C~SiXqZVa|GXfr?iCEcIleMzGB-0lYNx)V$&X*l9)Y|K^BR=GkcGW+B
zx?3Ym>^$q&&{s1KlMQ=4w?;moomGi7=j$hf7HFg!oCXntF$)1!W1E%S!gOZEdW>Q=VWUwH)oeO9WE;`v`xut(Azz&_lVO@d<9eSlcd
znArG?Plk)dSmsl!P!%1Vif-?Zynfsf20Y^aZypiCv_?GJ<7Sw;lCA15PXk3@khpQE
zaCT)~OzV7rayT=rdIT=YT`Zi!x`@AmC@aUaoFO6%=V1uPa%&J2@E}=3J54Ix{83@{
zsu_JY-LNQ3gnX@r>x-|{E&u#;+a9o9F_;SUwf9>%yMSVsvd2+a2Z%DE1i|qVN!i{Zqf(6|)~
za`cJh8b7p|vHiJw-BdyIrTBG7O0NP%3Jmv7R#YD
z`RD#WKdSFL0>-oYN05p@LgUyN9SCK$r;69Ftbu39WNt_R631vdRAygCrpW
zep!ZB-y4Mg*>HNGs+yeXOCYQn1EcGC>W+Kt35=14yczm5hL`IZM_V$%Cp%mVYX_o=
zG>Fqehjh9OFvs|4glKdOTl^J$Lrf2u#>NC=0MG`3N81>(t0jxD8`icJKAk6#484Ma
zu7V2G3*bo4D=pQloXHbwjoEstkwMalTk&hgDG6pj+I4(-hGB!{2?Hi(BvEt2QV`nl
zSY73W+<2dqxxQ0!{r-pn6HSPKhke<4MR8Fc8PcfZOR-@zx%^sl!;BrGG>@_1q#+r7
zW@5C+33%dz{dr<==6xvbGviNKy4K1q#54AT9i4WNKSsGabmT*CqYS~Y+!JkDNYT0T
zkbgcF-sa$70sACwS*|1)(-4@neFX{N|3n-Z0F02V2-Dx6wkd(plPc!h(|qEEhA9
zuK7g&Br1@{M(p?V|7tU{R@IBsMBq7jAyccs_pWg8Qu`UyZ>$(1@Mkzb{Bnx?@!c2)
zCKV9;@Rs><=t!34kN*JT>)s1|YC)-J0O4}Qqbis#r;p@l{hzs3Dk0QUIbR=;jr0&0=9*sm^!%-ry8lG
z5Kh}g-)M-g4gUx~b=?65*Bb8LXXpZb6^tz8CAqqk5&1U@GWj
z&{Y2OZSSDPNR4U|%M@?YY{v<>h229xYNfAtyN!>uAwWM_+tQ$~o}+NBMX9zz_SPMo
zOEYo#eZBR@D~A3w}j#ttR>D{!~h0BQ3UZ)$W^1
z1(Llj0Fe^T`l75FG>5<)>|fP^j>O=;FiQ^2o7oDX8yA_aOQ-j=eS=G?n$~M@*lSk#
z<{mIRrpr8-IQ0N6(IT$2gz1cl6m$^i0e!
zLUM;O;mv%LGW868kj(0ok0P?Y0_#s>T3JKc+vFimMtsEY9vCzHm_3FS_e%4SoHH0A
z0m0k$81iL|5JIEz&guD
z78vnlkF7|Z^pDKIi)pB%j_!L3<8r&1FYIPJFz)NJJP~ZGgp5EhiA3pi@*kNJ;DCU!
zb`(Mm2uJ~1PZgE5My>HcNy)+bQy9=N1jB5{+&jKyny7!hSr7Ve%MjYJdR`_oAz9}&
QXn6wN(l)w*)3OWuH$8?M<{fN=k?*0RRvu000;O78-PA4F7-x0QfxTrJ~`YWav)p
z;AC%RX=6(4;^|;YZ0ccY1^{?0SEXD2_{#1S{IP@11-`i$7j(zFCNK;%ANiWOrenq;
zuWO>Jre-42yS9oM%>8k*0er*yP>LEX($PLD@^~-GX-Cv!`FMJG!twFi*yv&QeeHgk
zeK~(f9g-)w<{VGGdBTCdI3OY_J$iY6eezWY9)vVqM0EQv)YYST3**rM#ZXmi~=?=5-
z*XWQ^WZ~WpZea%_{noOmwpTx9arqTbti7&HAf+w;%s>S0EjfUCrIrtifliRzz314bI5W;Fl^(zdK+3$&b~{4P3k<
z13yzc8i&Ux&cX35nC9&h=iS!Rq?_DnSXY1fJiKkKRhe*?-c%IlZ@<(VaKT+|Zp|GI
zFq34tP@I|xR*TpfF*EvE9BlcN`?(kDeb+;^EJR|-)U^6r|5PS3J(iVy&rdLxqdU-X
znxK8)k)kQvRg$9JE>$#^Wd$l(+cp8GjihN?W~{8MTBfVSztKF*RGd^j@Ihk{WO-d4
z{X#XC$#g7EH&1tMUOLd|H7`n0*K}=~2QD`6n7z6$&7U>D5K`dhxy+1~WO*$gnd{WL
z?Z~okIGxzGY+QK*yLI2%thZ7p-<*McE!M{xUc>W5x;tlD{@kJ{*(P_dLQ=((W#-*@!U-|eA(i{5d9l;KTmtv-{4UnwCf|%VbmTrtx|cRwC(B$vh_aon
z)>@qX&sX$aY~xD4U9n0imu5VBkTl~hOg5`650lm?#W@Z&c>lEAsaE!yWUzzd)a-aI
zWqFZlbEB~(XTJ;jl
zWlsGv*IRD<6D$SZ#lOlu(giQJSFT8W1S0SDtep!Qp3-u1V6AV;hi$Ju)}i*{s7|l2
zN$ofKfkcWaUY%t@Zm<#_co<5mccjOM!-)p`?gmeEm
zq7ng(
z5*8G0V3ix3^qWN17u(ny^WsQn#c(V|lws-H4c6mG0*Hiychc8}PYzyMJ{}9dg9({i
z^94~JT3x>>89F5WSv-iAeT~<^L?NQBgqO5(^3IFzRZoHr{tl!xhE&^-4QN!dgCX^_
zE?`PZAwg+%SOarB|I}amZUHBv6SP8JsP7Zq5sLK4J=bJ7^1F0ucFT>1lni=`LVk92
zYPt=2kI-9*74nveHpm$Jus1+g|4~_TJQCizlx}>e9o9^?gkFwkcTf!;5B(x!F9z4w
zte~d8&72Op(-I47&9?gJcde9XXDNnP8v}W(KFxI=z#A56lsF+jLO^pyE|e@sL04#l
z0$)fruETs_48>K!z(-4kA-$V7&T}&Ta)G6URK&S9bb;n;y4gAJeOCb8yydWCI%MN?
za7O=6v2e(ZBQSGy?auFg+lByiVk}i<8rVKOnf>^7xnWCZ>H}P*AfII3FhKH4;^5ihhQwQ$
z5>_P7{3&o_)E2z1ySALJuiOQ<51I(esmmEbpSo`vbq~ivAm=e6fV`l&&vo2*-!>ZB
zUg9zm5h(Qo-VBZ2@hgd;B@bK7O)x9TF|=qdcbPw!RueD1Itmi?JoYIGTCueP*%m_U
zWz$77@~>zi#Uv5)kjPD)0X6=F)L*o>5TC^CEC*Z@4ES||p?kggOCntlCX?!ky1(ZN
zP+y4sn2G%gt)L2>>_q+!9);F~I|NCFw;4VHRaVjCObbi+gVA4Wz~{=b*Az?ndjh>S
z7#J)*_eR`FO29Kgue=Cpsio_Jl-74U^7S2skhLV-@A|
zIfsJO*G%RGc*#jVx%|#s31wLN8KAfdb|Gbmz!9|y4tMEqYa=xVIULDQ0{om$!-8p}
zud()rYZvgl?3F}8$ChU*w=COAo7j(b2*(A!fI7ei55OFJ5luk)vFEU$i)<6fFwd3<3BgBf~O3H`ei`Ry
zalP*9AU@?_N1FlAj&b
zk4uGL;F)@0l7UI8v2$Hf1sc0GBPn9A$hKU^z_zuHk%OGT0c09(bb7xEZ`cIJb=hL%YbhX{)ZU9fYRigBpU?Kf+ClF13P3@{e_7Q
z1m8w}avsNA4b^YqOPz5U88|zN>*Pd)30o)#vX78yLo(P>Ae7faNYo2E36Bm0MA0CI
zqrvqv(R|$}y%rzL$yh2=b^4OxrHVj|nejyA0d2Fzz`l*iX_hzE$aI5u{Og6m^;Id6
z^qCNQjRmnpxOO*U6KI^#iw}kT2Z_Phz77*ZeXVJ0SFv{XjMR=5$k5H3CFl4YXdqTes)$DPd0R7;k
z=C1JOF%TJho`H2F%p2wkZOary1FO!l7;C7$;ft9HlB1`N`L}p6*#b(nx
zgiR-1Vrg)q^2I#t<*1M`z+o+AhP!yWVkmj|5Q)e2-lu!nxQUZ=Qnm1pe7HhDe}^NQ
z5OuVFxYc8*o0uyj1ovZFVxA_%Ro9n4Qoiq)O4$V#`Ad{yizz(t`(fs)$xFuCd$tdh
zk6(5*8#6!YRC6w3e2&c3@%WH=8k@wY_66W%SFW^)M&k#hxlXgikyynRIdy2>$J~__
z5~#)pJEO=imb)4`uBea40dM21&<23hqCI^`+hO9tF14D@wd`xc2U>mXK#&t&6j)6DkJgh-JqNi|-
z4~?05bs5bvibbbj99!@c0o(z#2$(a+2Bf0~*Jkt@VDynwF*d`XvcUKX?(e9P
zqlI)~kPq=p3;q!h8Wu{DupvI3i%&{so1zJZ)_}1GQz-G;cGHb|6fMS{(%L!7V+hSy
zjw(x2qVYn_&Vd$pP!@qWt6-|r=nf|#V-ENdW7^OBEN_~3?wz#No^t+8)XNC=J7Ms;
z09|PO6tylhUbr29LWe6Pltz*IYUJUk7GS>7@1af4b0qz^_;63^aUq>PrQjg5>+IX_
ziTk2Fi?PD+l_3?HPinD?W2Gnw(XgK9c#?(2B7P<}1@@?WVlJ?;cTVhI4q|(JzJRan
zOe+z_C%REN!Xk1HPW+Kq**1XsagX8RfVei_x!f65OZZL?+FyOPT%#mjqd6n>WZX$jedFgqSv!bVBaFIW
z=x<`yL12PcysV3qwkq{Dw;03gs1hTPxsPH>W~g_0-AOz`fvqW=h>I>ib+!yBLrH0L
zB&Eq_n4$v6358^3Wi!`I-iK)IRQ0|~EOi9xlnvWj;mgsPKjn@}elY{LkE=RV@y+PB
z*;!-7%W`-5!+@zvG!7;t_j6UFQ&Aj)gyEAdE=lSxf=%o0z}7Xdr~0J=K`5~wZx#FJ
z`@rU^%`e|vEgoXPcGproRP`(pMlTl}lR}^vhULs4c={1w0esO$s$}Q7slLDHj`v9?wO<
zs%qCXucOwF~M0945vo-l_#_de-Gg`5CzL3;x
z6F+B87`oz+B;5U;f7EDYug_fU
zc09>1-tNXNp8WBau2~e6O0N7Q8m37xY%Fjv3vnk&iVz42$#3&^U=gF^xdZwKKegJr
zN2)qe)+ZIWZQi_(Lm3fW@V#X=#SkyR9B&&IkQ)`{B=HU3ramRTNi5%&G3r
zq-2JY6CJs-5XT{W!m#=mm3ey8z8vd@oi@7->EVZ9{-HH<7eS@q!6FJ
z-R6Vemo+|VcnQU}-ieg7LIKI|*M=ixpOZP7;tFR&O9FZ>`g917=!SPU
zWF9XyEp~2Q1wC?yPx70(<>W#lRopO9o{%rEc6HnY(o*>5{|fNa|R`l=vZSmZ1_>>WG#!dFB*0#$T(9
zUjf$d3=mWU0Vr6;yB)bslg!*A&m%EPo;4Km9{R}1<^!vg@vhO3(u13MoRS^V3==6=
zA_-5MMM}$@&P~Bxcb!oYnlwC;m|I+{Oon)05K5PS_@oR^{Pm=Z8
znvoz4rB|@-!^$hB-Sc8+IU(g3@?(OBQ%I+Mxv6_`B4{q8Sab+rGXb>%bY2)8!j(?P
zOs*I`VOl|garRVE3}k&|&9;bm7e}@ns>-q?6eu2YH;qGhc>T
z$b^bfAZ2_>G$e(QHVO3Uoy};Ly#lA+NjIc6w?){fW%1LT83In##=R!#@ad1iMacB}mYi-kP@ijel(8ko7Ax9nZXQu^~g09&D+rMbY%DiqkjfgOy`
zwu>A^;u9ZXr<93fBIAAK(HEy26Lu_@Xg!|V=7AS^W*M=6X-lbw)eQ7QgVQLv-Qs^c
z^79ntt{cw5HdOjHN-;>w>m!4zyA=1ueVL1goN2+E2|G(8^uu84b^2C~&U0nmv&x6w
zz~7_2Z1*O|-fEj~iv3p=Hy4qRDA9Tu=e+;z?AsBtmq;Xu!v}n9!rsN+*}~p|SW;N{uhRzrNJM1+a92?QVunq1xqIbLFrZTkUvwc7cd?hvh#02f
z7<_|3VrHo$DF8_
zYn|Kmz-xZtyU-4Z?|f_PbkX<}z>*U{w7n-47adt_`~rq>34j;}7}PT{gA4KY2T1S{
z+TF?|e10T_4PbX4pa!tVbOs%|Tz*Jqr2N7PpMi{644fD>iD*?AC|e3dsPh#Yn0rKL
z?El7iBMO6F*O~SiYw5CKij+GlJ$uMFO2cO#!3|e?oR@SxgKRe(`wGiZ5i>Q3c3Yct
zK?bAPQS?;}KXb^?3_O4|;tU*#T5_P6l>Dk+XjP;W9e_`HMIHMQ36+Ka{ZYy82&c`*
z&L}aUdTgdJb7`6Ba6Ri(C04)yeZZuM;mM|5+e-^NDHXnVJDXoxOh>!DbY#J-d0|0p
zf5=Q9*iB8cmb7X%nZ1G=vibz1}COG
z)_&<#f5?wGAd@5>sbyvyot$LbchVTz4t&r5IJ#uNoqvcPJbM3zyq2BRi(dTcaJ#*3
zMtL^G$IA>lX)qt}%`^PNaZ^9HpCLG@cgxwMAlzX(+0iqi!0l#H2aG*i{NVas`SXY2
zVs0Y`kW>+u56r5PF&;M$d>62LEe{M#0-Q<#KqUZi2>!(Y$tHl;0h+KE3o?)a1xgJJQv%#F
z0J&a}ZxJ?90B#Y?Eyzj%9#`PA1sH_@3J&U06?ncLi8
zQ3Fs=aSKARNe~7Q;Y11&AcNy7g(DNBeiB*=w#V!3k~0O$e7R7-Mu!p=$H_lcTrb`G
zhH1rWMNp2e8p9Q6C4QG@S0JXyTqNwBoY)M3Icm@dqZU+_+YtYVz%Yoa^{yMPJ*X94
zbUf9@M=gnD9`@WWaOBYet44E2cgJK8&g`AoDZ141;PxTTiJ2Nq-NL)neFS^u@XH~V
z{X@cm(g!;l91~0>1iyv0MY4sFAfZCBj20B3vkh&8am8`<$uapc_N(v=MO%WAgwTxJ
zjNy#u4DSpzQ+lPW8@YHA)b80X`L*b^@wGNwWN=+@9Hh2Umq?6A$3Xl5ykzIkA3vjZ
zsYZE5rACtyArfbmJSc4`*(r{EJZKlHv=Z|27r
z)GFr-ZzYgQDe5z7Flt`~Y?BQhkBFhbsDDz_Ds+)3lebjU6kU?$
z6!xsS7TvSN$cSu^s+S7+t(iip8d+XQrQRfTmvzkCPO}!0BUMxI@W*^Ed9HIl*vi%F
z&Fbcu>v(J)eolLy$P$GG8y5~219uIVIioxSEaNF-BIBXyQVXH#YZb3nmzMJ~*D~s|
zb|ZHqh3l@XkIS`7hAZK5qHDH``pLq{`b_^J%t
zhgrV?yn(J^W8!W?bDwp;g=PdZ2lIrMi&jqySHp=0S!2DaKm$s{w(hw3*}~R%qQ$N*
zr+&IIqG8FLc|4CP5W6Uvy4gZoedVsX#CBsza|P~HZt=~`-Xzz^f6mXTY*J}*CbY;H
z=@>~Sj4KRPyxll!M{pRGf|a6Jxoh^bIih*9bKy;JRE~807-K3{b#b*1kPXPm&fU^v
zcd&-)PXA!^;D?M8w?{Zn7{@!$`RG3-1;1;o!v`su!#qtOJ~oZdPkZdjS;|+!x#(itp15oEE{YF#|crw^s5;
z%WzEzKMDJ9WGrmA7KfP(z}os+m@(H>_*C~W>CoX|_K+#jR`F!X=T#BJ9DK
zX2xUn*$rQ<19d#)*O1JRLd{rBR!#ec{s#GdiGxe0XQw;omOZ&$&wbwx=%&!7Vs3p}f8+N<
zt<)yhlRVd}lXjqWCA|)R_b==IgqYwMN<4om2HJ1*9$l11%If(}TcLe*6MC7MQC}k;
zMf=&fZdEQ#mAW;XK*VoW0hk_r@g0}
zXRQshmInz=N7tR#b-G|3%hZP$gBUgL!{^;vmYSL!VYT9|=0AV;lsnh#uM>Flt5B$J
zR6AB#u9Y;}uXMM)oxTafWFa@sQ2~FsB?%ROcv@{MvVT?Z{7zbt>V9XwO;wtJBgR~nWuS2@_E+)!TAz1Own$NlhlY+OFd
zV6Hd+J&87nnWM{*>TY-MGxT%>AAvR0Zrnkn6W!@iLD5?Bk+@MroytqEf9iU~>pr;k
z`=|osJLnqgoV42cc6Cja*PKyQv+aCx|mSRVGtK4hlaKdrX=
zc`u#);vTzLC5Xi#Y#Zz5_N2dCAA8Y!I3Tj|2=~)?l{i-1GcFpw<3D*@l
zypTLhf-$bFJLoY1wTqpXluwp>!N(%AKc9AwD5uKsrqp=w{By2+{MrxIkMtk9{`g+?
z+fVk~O^8fJ_w4rQJa*lt^i}lN(CWKSK~M1cCEc6(RXx-xXZsXdbf4+?}cbx&kV?kEp016t$@DuXT&ay#^y;(YkFNK{2ZSV{q6nkB^G_;|gR
zsRx)mSh%b;XSSszo}QCs>D4S|?t}Dn^ZlIk8-$AV%n-By22DX$2n;o?AmyP5&;^_U{2*$j?O8NNSRf-@1BqcluL9-pyMfk6
z7JxAznu~IVRmCS5W(Hvs3vdFcoX11-VuBMi%Ko1_0}mC9cp2%s*#jZ~)ewK8BBKzX+hy36#x%F1ASSwZc&>c`M>uK
zNBuK01ArN@3PGF|8G6`@4&g!2+u>E1pEE8PuR-z8`~r!2}FJL@kq}hkND&T3zn*_^*^8Y?$S@s`b^kFtd
z0gft=kXP*9VB>Rmh93`ukK2ao=KdwZwo*^j0rXS|L)Kc
z>Ou~v0k}{oe-0ko1+G
z=D{SRFLiSNa11Hv!tfO)jl1pcO&K)9>3tc9OWgGD|DB#hWiI6$j$CJfp052
zBi%bC6U2Y00ujrf&6#K51UbDvE*Txw6EiOGpGTq)z@%<_1oT(1UfHC0Qge&k;;u*pt^xxrs50%+ofYA?2
z8UIt4Z+rQl(EhrRfQ>~2VL%@MV8Rph;1LJ>JdO$FVuJdMnUE((1L8X^2$FnkBLI)M
z*?$p%H6^t&1#KQbo`4h+x;v*fmE+92c!TH21EfIOjv^7
zVhI4yM={V!uKz9$io^`>_3i5%oKPJCz=8Q+z>^0#)&B|IegHmrSH(KX9}5;(SlC*N
zMlEK*)6lzKhP92&tt$t6Gz?zw`vokYJm9Yc10CLx~0g18;jfLrdFfQ0B
z;G5bY`kG&j(D#KjV|!;u+PK}d;sO)T{0Mg~B_uS|^X2*J$(j|HU$E$tNwg=#pPbiC
zAJZ|;7B2i)tA$XpCcDZKr)?+?Yycg%=@FS_H1ab
z-?5&{p~ANmmyUR3;vmxgq`#V$Oub+aT^l_<%LW@5(Eav|6@%lgDyror)+tr=>0)Hc
zx=AZ;Z*L#}-D~f6wfDnm1o^M&Vx0nZT(%?}iT{QR%5U_O9&l`-eMkDyF7MQJhNI%d
z#@ky^C<$>DB35FKP4~PnE7o6+(RW}GaG{Ge^_}@tv*VH{{_hh$ef~#8c)?C7W?NxC
z0sv$4K%EBjxkE<5?(S|llLY_LimJ7b33dH+V3lr?9a|=j<^XLPn8;B~C`j`D4SG!@
zU$B5B6I{=uoOqAd>Z92xZ!(IKo!r9N@E=eTzH)AEzY^WCC`s&gg(8P#}pcIPjaAntIanJ=pA$bnyjNZlOtozKPBR@fV}M{y{Gzolf=u
zE&wi5lm}^tOPTj$-SFMoXSDheT-#V)b_(+cYI>erHFfm^(S3)=La#!SjB$a#x%bki
zjt{*TlPQ9OMT<7qaf){dOV>w|$~ZdM=;hOoWmW1Qc-^n34Kf{Ta?q&qf$J9ENSXgl
zemuxqjsD2w&f#Aw^!az{r}+4pP7}9$B}-fQqwDglYjWeCwkUE5PZN?|qG?Do;71|<
zBX^Kn`CJE$DR-)XYLcYJ*xcOQy{opHYwwbuw`A5HG|l?{OIbJj)-`txT2&@7&NmtB
z7HOhp3UrP2|49P;HjZ*Onyx7Sx25|aGb_&+F^
zs5oUYF){F`n0PB>G(4*hDgr|CLpE$!{G8^^?FJisT)ADYY@k5h`akrAMcxr!t#tud
zAPh_r{R^ipE;kF~&hQx=4EW{8^;Jzxo$sr4MV~x95yOxORZ>$^Loz1zu>n*qD%eAn
zT2=QJCaH8OyxwFhv&=IP%Cj@Z!a~BnJb!qaU9Ew;^qQTm$PpQsWuUsk@c#^Y5P8P|
zfMy!*iHM4;@z{UDI`;MT?eLb$LBhgPw_k3(Qn;_qKDCgPY@D}>xefNQY+RmMTwE-K
zfJQ`vMkZM1<>C@Mpk%8kEnR$B6hsYxc370EOE_cS
z8z2%>>?;rH!hz2^#ed^+7kjqSp?vm^r3k7C0!7!vA8rl#|~orL4-*EU{1eg8{?rxGH1=6w_M
zhutg~R}s7lmGW}1AZUX2qaUNZ?PB6BV``qlii(pv$o$D>OzA4S;D6-pSx*1&Hc+mV
zhq{zAXA=SW2PyR3&*hR8^I|P~7t1zmua|ER-aq`W+M4sb`O27=cvxs`rVu|`!c?q}lS-FjqFB
z_(2RSbeCd0j7z@mBc0ofc9wM$dVop8@eYiY!}B<&>Y5g~qWU+15K=81K%@be2>tqE
zK08y$WTip(o}o^Sn>Floxvx*IH#!=3MBD}h00@81&Od|d2&~f;toc4Nq$)Mh(f;|_IaSIhxQGbT(Jtj!S9uAB;h{meEmTx6KR>^%
zK;lC=St*Cf%;C7M-!OnyE$vC09|i#2A&s~43pE%(K>wD2uWxo$dwKhif{Kd{C}x84
z4^gC~rG2f+D+&pa#A`cjOXjVRH@HvuZZ`9Uy&DBn9B4e<9e+JksF%1f5jn(wSk3tV
zS&ae^p3)%uPK|3W^^3=jJ3U;WpRTAs`^(c?lvY$Yk409=l`!jbm`E7*yhCK5dEde!
zz#^PZV&9+@{i$w80~o-cCze=M!Wm&IS4vgUX
z4jL^x)@bKs$&-AyqUcZ8>wLX21HN(2P9DydJkE^K^Dj_T*q7$!>VpGPgNtf%i865U
z3}eFW_c6pzGk$-mqY|tQ@9L3ON&YA;Ep1dR6o&xd;^J2JeB5jQDvrU+_=Jof-n4R7
ze@6O^+l|I{KN{ypLaJ^Kuj}(IP0CC25Innl8*2;TAdek~z#^*zTgdGj66+c+EVqOcZ7k^E<
zR?LkZA^@aOtr7LO(=
zi^ULj#jXBrG(i2-+ITP?#s0hdt4B*$p4V}m?z)5(*0j$Ex)RigT=Hn+Ra$q!mt
zr(NL6$_gZ4f3cC+xn(^g34EPKe7VQ|MfQM>o1K-FjOYDQ7}dV(O02nm%R#%N)%rP_
zP&iE2CqU=FsZ#Kt(FQJ2zdYndKc8bpA{~vDtg?(=0%Om7i;7cLJKT?`YkCsiw)@nb
zwVz+wo2*UXn)fd*HUNPALp@!YI!i792)S$c+TpP=JX}0H?eU#i^~-_^r6+xG;h2G3
zMcc=t;Y#je8M<%O)D_OkbyjA)mjX7q-M%rq%#*rP?awRr
z_k0}IY6i~E4Pyr`%tH*f1%@FP^t#O#OIUi*kr(T{)Fh0IWM5etug0yV;|LM0tgNUK
zGcMYf+XVn>))FoOWK@)&02vt>uRn`l`X8*dbz<&k`#fB6FWy%z*8PWR#Q)5}Kibi=
zO8tCQlXZ2yj^@7TtkzN$1pk
zdhB5b%#BV;z$dsVwARZ(EN>dGwsAfK{CW+}JByJc);-TNyUfw%YQi)X{ZR2)14!s*fst1x~0H@PCE?7O!I
zYqepi(VkHgs;#VkOmszGrG&n%J?-C|z~s~v*47L=U*E2x@^Y#tnqTlN>uljAxldhE
zfMWd-=H@AvV7=T?cQ}N*YOCXz6QKE<{qrqP(x^#lM`x3k?QMlU9g_tM?ltz`z>C*#
z#~8xa(|EM{o|>x8-ZsnU)7pAny<%5YSygqk_xlZ0%Qv^l(=I;sQNw=${mo9fR!LPM
z^>{p91~T{1^EeRCfMgpVwX3|o9vV=IdgU+f-*QB14b}@%(eZf~_SaiwL1|Yj0h*12
znvn9Qm(Do@XZjsqc3F3enReop`3-tX&P2b1T#&k<&$@rhf8ks
zN!>r}&D>N}RN7aM&exm9I8M$x<}stlmQ!Bn|EXAx%yZQrMt>i}Bn(%tF*qtkz0BM=
zImL2Z&AN3=sI4z9dgOI>(wMI;vuXhtT&3T<%NA$IrojQ!ss5TRc4rx@+1zgUOt=Ub
z(_|>DgR4ZxFc}+i0aveMO(4dl`ZFMgf
zv_BU>No<6o<-axoN^pmwdm{`k*XefO))#TJ(d_f)DsANM*S;IZ>dst3G_;w<&5s;l
z2h(>HT+AOcv~11C4vvl^M;kSoZ@MNM?{8D>>sC!aQV4mq>fNUZU%tOVy+8K_DT+(S
z9iOskLk5J;*08#t5Zq4nD0jZP*9?<%bzhKlz3W?+&mCX)_V!*Mrll2ER(9+8zC3`M
zZ!9dQ=PM`jq*2Gp#@kBTOGCeJ5)mTx%u_y&q;VT=^asv<2HZUx!}XiHKNBa+POf{$
zKUirz?%NWR8yhV+Pks$#+MlQArn)y8Y^+!B^8IDT0V~=$7E~4MLtf_15Rvnc7QAPZ
zT{sVEGb%06ae+50h_ndW1#f6>i_UzCdM?R!{^Q8eK1J?zdwcN@a!H)Z14o!
zP;n0H_q*H_><1+c-}mKrb0byNl*3WTolaNs%<3q1z$5;m-s;_1S;0n#c{Hnth}bbb
zKGOi7(QZ+M_rp2o^TJ8Z1K&+Mh1hi$yV(`Gdam!Vp_|FF)X+E^2W>l!F=E_Qx_9SB
z0NC1IC9+z@-6BDfJzRB7&BHN(Z<7mT)+FlWGdYTbemMFcYhj?h64YKl@V#1W;GoWv
z-Mk&od#84GaNx*QpIfou^00YNR`dD3Nbz}RJL~ZuJ)gYFkk9s9gZMNnuPk+-MdLM|
zcvg3N%6isUHI8BRyC^}`Qx(l87Hg>h`qv9eX%SqUU{tg~-
z#|(0%(s=E=5d);ESsu>Tw6AAA-t<{?JjCBz?y=Ba-d<n6AL8;2|N;~RauJe4k
zG>*@Mf6RobY_~C!jB3Tc%PpfJbO*TtmMEe^oThFaTz$)pwl{34#OwQsK;1W3B
z^j5Kv5e)So^H(Jjmzk2j>O`%%s4mCfSVDRY81>0D{adhpm))BC%geY)I>^PdM~U`n
zER?A_aDSEN>w^3YFG-!xB3Zxb%DbMvzJ2${y7w_Yl6W}t1MlVKm!+DXJ2yY;K;QMx
zapr97?0AzY&RG$s{qyIyq#UpB3p
zXKng9iLPU+Xx!~_o*S>t$8NehBaJ%Kl@D^XX@68VoOcgfG-;SHUxEZs0u~Y8n%Mm>
zZ6YODC((@K{?A8TBQ9#4{ad|34Q7NanG=)v`k|iim?p@CTr3sRDk{s&Q$4jEMYPi(
z@%6a<4g=0{5JOw!T-nI-kyGZLj@i>xS~G8CnZ!H)qclg+JmM~GJ>OQvM~@%9)(@+j
z1b@vT;7GgO0En7Q^Pb)0#@Au&hip4tlwW>cQSURe?HkNv?$_DeTnyZa=7(|LVhYpxKbIQ1%+Vgl}_ze6-aV6Uzz;
z?}_#gjxA%7j$hy_Wf#^NhTX<(dEkGHARoPrSiOhEO1;4XLc#R!ujJg-YEO^}$6rm3
zl$Id9Wm@cT`sr}|0(3t8r8`@#66Bw1{u2aR)wgV~x5mHhwf$X>d2V3{{dV<*-q0bS
zk;XIJ9(X&n+s)tN_{lF;>x{7gb1Lc=xsC?}uVEKJ@|LEz({8XLTHlMyYhxV+)n-?}
zdwk)AT2Y~=dn30rh}Nsm`G1VWqlVXYfudb6&DgTbM;qB_r_*wF5g%52k;$uBS^ZAD
z!?)h&SGH%We_z^`f5dGB~2~wsX2UXd07AVAACssx|-g*qc+08m2MMV
z@h<99UY(%EAJsUR1;71h1Xs4XPjR6u4|kH#Dh($^Hq`0U*n!&B0nS=gZ2QHd%JrjF
zfAwSfXu#{Aw*4P$lE2V)ti_5G`CMWjtm>cgrpB}v7>uhLPrbZ+w(sV29Vkl8A;Y(2
z!&9UCU>e+VXv{>UO^85RJhM15Mm$H7G}O4)<#!$@+P^X-A*e{dNPc&!iMi3`O~g5~tgoQZZn+~i
zO?{hhXo%>Xgl=4L+=e(|8Y6HQB_Kt|lL9r!C-%wZD&=3z{VPCa^{#lejk)ue{gQq{R&go9Q0^-~IjsErQ+iXgzuDzmfM`6>nBg*aXD8%&iguSUuj
z`ALK!UNCo_xRMA2^ISoxciuX^jxo6$C2{UZ4*rdoktNFCTd0fI=5yj-)8GYC0jlV;`O%xqml3O+JnkSg4{o8=b6YKWzAWi$LH}o
zs2aIp$0G!-XhM@N?EI61@;5-7nG5x4j6vY-O2ASmLljM)r*
zYe1;%>p$+h;%LH`f}r2LcjFB(IL2~N_zhY#WFMJ)U6~Z0TmcN?_q}au3x@8!xM;Jd
z5uWYaXvnSU
zyXa$1!AAR+Lbk=aF*T_tuTBjh6b$==oUCkW2GRk3P1kk3#V>wP100s6J8_T&pqc(qdo@+Z#XccIB)NsNbM3gmK7JsaR>R3#m@IR$f-0(4R&YC8Bg;3RdM0rGQ7Y${2;n+3qy?gHVPk|0U4r@S`cf(_qXb#=n
zFVzesr2Lbof%XY{8kB&E*^}WJH0xu7Y=@uz@l^ZcJ!j?4$>Xc$&1}n_`s%Ya6%{8>
zFU=cBx(-{Lkd#li4-Xd!i-qEC@0-71oj8M6gB&R#Ajl9#Bsmnai@$fAu<^LYHS~y{
z#ezDAfoIRQtzt<;rP*4Lq%|3dru`3ly1#3BY#4bN?e*N^e*UcNfnqpdjpK9he^7=2
zdNaA@_DU@h=uS}||LUFB6HlMOKRLR4Sobx1<>>`(6>?oki6;@ek^rhWN&wWFSjLTj3h%BjUV_3T=x@qi{&0Sto*NE&f~G^&C#%3>;fXLDHH
zL~*R{{w_#F4vCFG-F$jsq4C!T1w3HQhIx~Q*H}UcsF=M7yXc`m;m6V|?Ya04K+JFXXWA?aySXGiQuAV0O-oKV)MTm$LRs+8l3b};aTjU?(M;I
zmIoGdT?wkjSC(tM6Nw(fQc`+db#a13jrdrIAYSaj+dF>rc5r_$3&IX6{pD!pRbGpt
zF~v!APS_Cn|7g1EfF}E{Js2>M6r@X3KpLbwBm|_T87bW{IyMnWL8Tk%?#_vXba#W~
z=&r%H=Xu`m{u}OJoV?FTTRl@_sSb-5#rEsjL3tk0G;c_p3ov89mlXv?5U)4htlR5{v>tB3V&e&^P+
z=vP(46u8Yh>yfwuxewk?xr6w)TbZ+5IYNX;xo;$fGiO=nT;0nvuouZ+?^5($
zSi;FG{!D6kw}IZb5;Y3q?Bsl3^X>~Gy#0iPt&T}`+L(@(q#c<6OAY?aMs~oC)n^q2
zbBF;p0PW|{a)4@&7!DQ@eik8!iGI6v%i8qF3%)-UbzqCC@})huoe;?Q9+>@gdrReE
zh*oBa^jBa+s?ZUH+{Fu>yNZpdfAI&e$&jM!0^z$f9K1T0z;oR!8{y-7-6+TM9UDv4
z!1Sz#uS7q=VzEzOD%>imSX`BZyYcby#k-9BeuV@RhJ60Qgiq_~`e>OMWY&x-0f3j6
zm&=B0lZUL#E*e%|D|sis-2jewIV`O7HiY!p$DKQDlkDNyP_1oW%zSBdsc15TA+18L
z3vi^qCeEEX8q$Loj#=CW-ghVR$2zxg80L(5gmu>MnYUnjg1Y?K8BU~WbE6GGcXp4r5(0Q5
z3K$oJ&s1rEJ8axj+GrBKRT!*^35dt}zpLsiqZacTZgV|=Er65E*_$=@YJlJ>_XJn{tsIe$sK5a)j`Dh^pog)35a=T%#q^0#)Uz9>IR
z6|A|XCa2Su=30K_wmYMMCt4AQ*}y!?Qwj?|36Mm6OWLO=@OVKty78f@XT(t43XCs}
z5@E#<0KM1{h0$^ZL~whC3(eCA%P80LE@I#Ydjg67W5=Oz0La_cOejmfl#
z?_VeS64}S_F{VCb{u(DkNsYD^Yt{#sn+I*`SJTPvC5bM>9za7E68%C339
zpV6NZniG0Ao8&%3bA5|}GvK=I+ZOqp@HFqUA<9fn6AicSB(^J_o@ctFMtS1Jo&dWR
z@MNeD=ms#D3l|ykvzwYT>Ha+3K~B2ZP+~wjIv;|DK4G+)v&77#5&HY*Lapm>