Commit e759fe1c authored by Drahomír Karchňák's avatar Drahomír Karchňák

Merge branch 'master' into feature-desync-endpoint

parents 988e2825 ca45002c
Pipeline #42431 passed with stages
in 8 seconds
## Java
*.class
*.war
*.ear
hs_err_pid*
## Android Studio
*.ipr
*.iws
*.iml
.idea/
out/
## Gradle
build/
.gradle/
*.iml
gradle.properties
## OS Specific
.DS_Store
## General
*.zip
......@@ -16,8 +16,6 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="cz.nic.netmetr"
android:versionCode="100"
android:versionName="1.0.0"
android:installLocation="auto" >
<uses-sdk android:minSdkVersion="15" android:targetSdkVersion="22"/>
......@@ -80,7 +78,6 @@
<meta-data android:name="com.google.android.maps.v2.API_KEY" android:value="GOOGLEMAPS_APIKEY"/>
<meta-data android:name="com.google.android.gms.version" android:value="@integer/google_play_services_version" />
<meta-data android:name="com.crashlytics.ApiKey" android:value="RMBT_CRASHLYTICS_APIKEY"/>
<meta-data android:name="cz.nic.labs.rmbt.android.test.RMBTBugReport" android:value="RMBT_USE_BUGREPORT"/>
</application>
......
This diff is collapsed.
......@@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-2.2-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
......@@ -21,16 +21,4 @@
<integer name="default_loop_min_delay">30</integer>
<integer name="default_loop_max_delay">900</integer>
<integer name="default_loop_max_movement">250</integer>
<string translatable="false" name="default_control_host">CONTROL_IPV4_HOST</string>
<string translatable="false" name="default_control_host_ipv4_only">CONTROL_IPV4_HOST</string>
<string translatable="false" name="default_control_host_ipv6_only">CONTROL_IPV6_HOST</string>
<string translatable="false" name="default_control_check_ipv4_url">CONTROL_IPV4_CHECK_HOST</string>
<string translatable="false" name="default_control_check_ipv6_url">CONTROL_IPV6_CHECK_HOST</string>
<string translatable="false" name="default_control_port">RMBT_CONTROL_PORT</string>
<string translatable="false" name="default_control_ssl">RMBT_CONTROL_SSL</string>
<string translatable="false" name="default_qos_ssl">RMBT_QOS_SSL</string>
</resources>
......@@ -47,7 +47,6 @@ import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.crashlytics.android.Crashlytics;
import com.google.android.gms.maps.model.LatLng;
import android.app.ActionBar;
......@@ -345,19 +344,19 @@ public class RMBTMainActivity extends Activity implements MapProperties, RMBTLoo
@Override
public void onCreate(final Bundle savedInstanceState)
{
//Log.i("MAIN ACTIVITY", "onCreate");
restoreInstance(savedInstanceState);
super.onCreate(savedInstanceState);
// BUG REPORT
try {
boolean bugReportEnabled = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA).metaData.getBoolean(BUG_REPORT_MANIFEST_PROPERTY, false);
if (bugReportEnabled) {
Crashlytics.start(this);
}
} catch (NameNotFoundException e) {
// nothing needed
}
// TODO - Add Sentry
// try {
// boolean bugReportEnabled = getPackageManager().getApplicationInfo(getPackageName(), PackageManager.GET_META_DATA).metaData.getBoolean(BUG_REPORT_MANIFEST_PROPERTY, false);
// if (bugReportEnabled) {
// Crashlytics.start(this);
// }
// } catch (NameNotFoundException e) {
// // nothing needed
// }
NetworkInfoCollector.init(this);
networkInfoCollector = NetworkInfoCollector.getInstance();
......
......@@ -24,6 +24,7 @@ import org.restlet.routing.Template;
import at.alladin.rmbt.statisticServer.export.ExportDirtyResource;
import at.alladin.rmbt.statisticServer.export.ExportResource;
import at.alladin.rmbt.statisticServer.export.TestExportResource;
import at.alladin.rmbt.statisticServer.export.ImageExport;
import at.alladin.rmbt.statisticServer.report.TestReport;
public class StatisticServer extends Application
......@@ -67,7 +68,7 @@ public class StatisticServer extends Application
router.attach("/exportDirty/NetMetr-opendata-dirty-{year}-{month}.", ExportDirtyResource.class, Template.MODE_STARTS_WITH);
router.attach("/export", ExportResource.class, Template.MODE_STARTS_WITH);
// router.attach("/{lang}/{open_test_uuid}/{size}.png", ImageExport.class);
router.attach("/{lang}/{open_test_uuid}/{size}.png", ImageExport.class);
// administrative resources (access restrictions might be applied to /admin/
......
package at.alladin.rmbt.statisticServer.export;
import java.awt.Font;
import java.awt.FontFormatException;
import java.awt.FontMetrics;
import java.awt.Graphics;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.io.InputStream;
import java.text.FieldPosition;
import java.text.Format;
import java.util.Locale;
import javax.imageio.ImageIO;
import at.alladin.rmbt.shared.SignificantFormat;
public abstract class AbstractImageGenerator {
private static final String TEMPLATES_PATH = "templates/";
private static final String FONT_PATH = "fonts/";
private static final String FALLBACK_FONT_NAME = "Droid Sans";
/**
* Generates a image for showing the user its speed test result
* @param lang: Language of the image, currently either 'de' or 'en'
* @param upload: Upload speed in mbps
* @param download: Download speed in mbps
* @param ping: Ping in ms
* @param isp: ISP name
* @param typ: Test type (LAN, 3G, 4G, etc.)
* @param signal: Signal strength in dbm
* @param os: Plattform used for conducting the test (Android, IOS, Applet, Browser)
* @return rendered image
*/
public abstract BufferedImage generateImage(
String lang,
double upload,
double download,
double ping,
String isp,
String typ,
String signal,
String os,
String formattedTime,
String formattedDate
) throws IOException;
protected BufferedImage loadTemplateImage(String name) throws IOException {
return ImageIO.read(super.getClass().getResourceAsStream(TEMPLATES_PATH + name));
}
protected Font loadFont(String name) {
try {
InputStream fontInputStream = super.getClass().getResourceAsStream(FONT_PATH + name);
return Font.createFont(Font.TRUETYPE_FONT, fontInputStream);
} catch (FontFormatException | IOException e) {
e.printStackTrace();
System.out.println("Couldn't load font w/ name: " + name + ". Using fallback font!");
return new Font(FALLBACK_FONT_NAME, Font.PLAIN, 1);
}
}
/**
* Formats a number to 2 significant digits
* @param number the number
* @return the formatted number
*/
protected String formatNumber(double number, String lang) {
final Locale locale = new Locale(lang);
final Format format = new SignificantFormat(2, locale);
final StringBuffer buf = format.format(number, new StringBuffer(), new FieldPosition(0));
return buf.toString();
}
protected void drawCenteredString(String s, int x, int y, int w, int h, Graphics g) {
FontMetrics fm = g.getFontMetrics();
x += (w - fm.stringWidth(s)) / 2;
y += (fm.getAscent() + (h - (fm.getAscent() + fm.getDescent())) / 2);
g.drawString(s, x, y);
}
}
package at.alladin.rmbt.statisticServer.export;
import java.awt.Color;
import java.awt.Font;
import java.awt.FontMetrics;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.IOException;
public class ForumBannerGenerator extends AbstractImageGenerator {
private static final int BANNER_WIDTH = 560;
private static final int BANNER_HEIGHT = 274;
private static final String UTC_FORMAT = "%s UTC";
private static final String TEMPLATE_FORMAT = "forumbanner_%s.png";
private static final String PRIMARY_FONT = "Roboto-Light.ttf";
private static final String SECONDARY_FONT = "Roboto-Medium.ttf";
private Font primaryFont = null;
private Font secondaryFont = null;
private enum UnknownValueMapping {
EN("en", "Unknown"),
CS("cs", "Neznamé"),
DE("de", "Unbekannt");
private static final String FALLBACK = "n/a";
private final String lang;
private final String unknownValue;
UnknownValueMapping(String lang, String unknownValue) {
this.lang = lang;
this.unknownValue = unknownValue;
}
protected static String getUnknownValue(String lang) {
for(UnknownValueMapping item : values()) {
if(item.lang.equals(lang)) return item.unknownValue;
}
return FALLBACK;
}
}
private enum AdditionalValuePositionMapping {
EN("en", 151, 432),
CS("cs", 130, 394),
DE("de", 128, 398);
private static final AdditionalValuePositionMapping FALLBACK = AdditionalValuePositionMapping.EN;
private final String lang;
private final int firstColumnX, secondColumnX;
AdditionalValuePositionMapping(String lang, int firstColumnX, int secondColumnX) {
this.lang = lang;
this.firstColumnX = firstColumnX;
this.secondColumnX = secondColumnX;
}
protected static int getFirstColumnX(String lang) {
for(AdditionalValuePositionMapping item : values()) {
if(item.lang.equals(lang)) return item.firstColumnX;
}
return FALLBACK.firstColumnX;
}
protected static int getSecondColumnX(String lang) {
for(AdditionalValuePositionMapping item : values()) {
if(item.lang.equals(lang)) return item.secondColumnX;
}
return FALLBACK.secondColumnX;
}
}
@Override
public BufferedImage generateImage(String lang, double upload, double download, double ping, String isp, String typ, String signal, String os, String formattedTime, String formattedDate) throws IOException {
//Initial setup of final image
BufferedImage finalImage = new BufferedImage(BANNER_WIDTH, BANNER_HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D finalImageGraphics = finalImage.createGraphics();
finalImageGraphics.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
prepareFonts();
renderTemplate(lang, finalImageGraphics);
String downloadValue = formatNumber(download, lang);
String uploadValue = formatNumber(upload, lang);
String pingValue = formatNumber(ping, lang);
int optimalFontSize = getOptimalFontSize(
new String[] {downloadValue, uploadValue, pingValue},
new int[] {80, 74, 66, 60, 54, 48, 42, 36, 30, 24},
167,
primaryFont,
finalImageGraphics
);
if(optimalFontSize == -1) {
//At this point something went horribly wrong. Our values wont fit in the image even on the smallest size.
//We should probably give up on life at this point.
//TODO - Error handling?
System.out.println(ForumBannerGenerator.class + " Couldn't find optimal font size!");
optimalFontSize = 1; //Why not?
}
renderMainValue(downloadValue, 93, 118, optimalFontSize, finalImageGraphics);
renderMainValue(uploadValue, 280, 118, optimalFontSize, finalImageGraphics);
renderMainValue(pingValue, 469, 118, optimalFontSize, finalImageGraphics);
String unknownText = UnknownValueMapping.getUnknownValue(lang);
renderAdditionalValue(isp, unknownText, AdditionalValuePositionMapping.getFirstColumnX(lang), 219, finalImageGraphics);
renderAdditionalValue(typ, unknownText, AdditionalValuePositionMapping.getFirstColumnX(lang), 254, finalImageGraphics);
renderAdditionalValue(signal, unknownText, AdditionalValuePositionMapping.getSecondColumnX(lang), 219, finalImageGraphics);
renderAdditionalValue(os, unknownText, AdditionalValuePositionMapping.getSecondColumnX(lang), 254, finalImageGraphics);
renderTimeDateValue(formattedDate, unknownText, 550, 16, finalImageGraphics);
renderTimeDateValue(String.format(UTC_FORMAT, formattedTime), unknownText, 550, 40, finalImageGraphics);
return finalImage;
}
private void prepareFonts() {
primaryFont = loadFont(PRIMARY_FONT);
secondaryFont = loadFont(SECONDARY_FONT);
}
private void renderTemplate(String language, Graphics2D graphics) {
try {
BufferedImage templateImage = loadTemplateImage(String.format(TEMPLATE_FORMAT, language));
graphics.drawImage(templateImage, null, 0, 0);
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* Gets optimal size from possibleSizes for a font and maximumSize.
* @param values - Strings that need to fit in to maximumSize when rendered
* @param possibleSizes - Possible sizes to try
* @param maximumSize - All Strings from values array need to be smaller than this
* @param font - Font to use
* @param graphics - Graphics to use
* @return Actual optimal font size or -1 on failure.
*/
private int getOptimalFontSize(String[] values, int[] possibleSizes, int maximumSize, Font font, Graphics2D graphics) {
for(int currentSize : possibleSizes) {
Font currentFont = font.deriveFont((float)(currentSize));
FontMetrics fontMetrics = graphics.getFontMetrics(currentFont);
boolean success = true;
for(String currentValue : values) {
if(fontMetrics.stringWidth(currentValue) > maximumSize) {
success = false;
break;
}
}
if(success) return currentSize;
}
//Fallback - return -1
return -1;
}
private void renderMainValue(String value, int centerX, int centerY, float fontSize, Graphics2D graphics) {
graphics.setColor(Color.WHITE);
Font font = primaryFont.deriveFont(fontSize); //Need to be float
graphics.setFont(font);
FontMetrics fontMetrics = graphics.getFontMetrics(font);
int width = fontMetrics.stringWidth(value);
int height = fontMetrics.getHeight();
graphics.drawString(value, centerX - width/2f, centerY + height/2f);
}
private void renderAdditionalValue(String value, String unknownValue, int posX, int posY, Graphics2D graphics) {
if(value == null || value.isEmpty()) value = unknownValue;
graphics.setColor(Color.WHITE);
Font font = primaryFont.deriveFont(18f);
graphics.setFont(font);
FontMetrics fontMetrics = graphics.getFontMetrics(font);
int height = fontMetrics.getHeight();
graphics.drawString(value, posX, posY + height/2f);
}
private void renderTimeDateValue(String value, String unknownValue, int posX, int posY, Graphics2D graphics) {
if(value == null || value.isEmpty()) value = unknownValue;
graphics.setColor(new Color(22, 47, 103));
Font font = secondaryFont.deriveFont(20f);
graphics.setFont(font);
FontMetrics fontMetrics = graphics.getFontMetrics(font);
int width = fontMetrics.stringWidth(value);
int height = fontMetrics.getHeight();
graphics.drawString(value, posX - width, posY + height/2f);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment