summaryrefslogtreecommitdiff
path: root/main
diff options
context:
space:
mode:
authorArne Schwabe <arne@rfc2549.org>2017-05-27 20:55:24 +0200
committerArne Schwabe <arne@rfc2549.org>2017-05-27 20:55:24 +0200
commit715e6887d795a2e52290fd27e98b76c26b898830 (patch)
tree0509a3618b4ff2aa0d4463ffa88508f385a1de64 /main
parenta45c9521e35dec3c64e71de2cc4006cf3d2be785 (diff)
Implement graphs for traffic history
Diffstat (limited to 'main')
-rw-r--r--main/build.gradle4
-rw-r--r--main/src/main/aidl/de/blinkt/openvpn/core/IServiceStatus.aidl7
-rw-r--r--main/src/main/aidl/de/blinkt/openvpn/core/IStatusCallbacks.aidl1
-rw-r--r--main/src/main/aidl/de/blinkt/openvpn/core/TrafficHistory.aidl4
-rw-r--r--main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java3
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java2
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java5
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/StatusListener.java1
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java240
-rw-r--r--main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java26
-rw-r--r--main/src/main/java/de/blinkt/openvpn/fragments/GraphFragment.java378
-rw-r--r--main/src/main/res/layout/graph.xml33
-rw-r--r--main/src/main/res/layout/graph_item.xml30
-rw-r--r--main/src/main/res/values/colours.xml3
-rwxr-xr-xmain/src/main/res/values/strings.xml6
15 files changed, 729 insertions, 14 deletions
diff --git a/main/build.gradle b/main/build.gradle
index dd68e170..f2b416bb 100644
--- a/main/build.gradle
+++ b/main/build.gradle
@@ -9,6 +9,7 @@ apply plugin: 'com.android.application'
repositories {
jcenter()
+ maven { url "https://jitpack.io" }
}
dependencies {
@@ -16,13 +17,14 @@ dependencies {
compile 'com.android.support:cardview-v7:25.3.1'
compile 'com.android.support:recyclerview-v7:25.3.1'
// compile 'ch.acra:acra:4.5.0'
+ compile 'com.github.PhilJay:MPAndroidChart:v3.0.2'
testCompile 'junit:junit:4.12'
}
android {
compileSdkVersion 25
- buildToolsVersion '25.0.2'
+ buildToolsVersion '25.0.3'
defaultConfig {
minSdkVersion 14
diff --git a/main/src/main/aidl/de/blinkt/openvpn/core/IServiceStatus.aidl b/main/src/main/aidl/de/blinkt/openvpn/core/IServiceStatus.aidl
index 6254566a..5a5cbdb5 100644
--- a/main/src/main/aidl/de/blinkt/openvpn/core/IServiceStatus.aidl
+++ b/main/src/main/aidl/de/blinkt/openvpn/core/IServiceStatus.aidl
@@ -4,6 +4,8 @@ package de.blinkt.openvpn.core;
// Declare any non-default types here with import statements
import de.blinkt.openvpn.core.IStatusCallbacks;
import android.os.ParcelFileDescriptor;
+import de.blinkt.openvpn.core.TrafficHistory;
+
interface IServiceStatus {
/**
@@ -26,4 +28,9 @@ interface IServiceStatus {
* Sets a cached password
*/
void setCachedPassword(in String uuid, int type, String password);
+
+ /**
+ * Gets the traffic history
+ */
+ TrafficHistory getTrafficHistory();
}
diff --git a/main/src/main/aidl/de/blinkt/openvpn/core/IStatusCallbacks.aidl b/main/src/main/aidl/de/blinkt/openvpn/core/IStatusCallbacks.aidl
index b72a2ffa..75860b81 100644
--- a/main/src/main/aidl/de/blinkt/openvpn/core/IStatusCallbacks.aidl
+++ b/main/src/main/aidl/de/blinkt/openvpn/core/IStatusCallbacks.aidl
@@ -9,6 +9,7 @@ import de.blinkt.openvpn.core.LogItem;
import de.blinkt.openvpn.core.ConnectionStatus;
+
interface IStatusCallbacks {
/**
* Called when the service has a new status for you.
diff --git a/main/src/main/aidl/de/blinkt/openvpn/core/TrafficHistory.aidl b/main/src/main/aidl/de/blinkt/openvpn/core/TrafficHistory.aidl
new file mode 100644
index 00000000..5bd255fc
--- /dev/null
+++ b/main/src/main/aidl/de/blinkt/openvpn/core/TrafficHistory.aidl
@@ -0,0 +1,4 @@
+package de.blinkt.openvpn.core;
+
+
+parcelable TrafficHistory;
diff --git a/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java b/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java
index 8f1eca5b..43c816cc 100644
--- a/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java
+++ b/main/src/main/java/de/blinkt/openvpn/activities/MainActivity.java
@@ -23,6 +23,7 @@ import de.blinkt.openvpn.R;
import de.blinkt.openvpn.fragments.AboutFragment;
import de.blinkt.openvpn.fragments.FaqFragment;
import de.blinkt.openvpn.fragments.GeneralSettings;
+import de.blinkt.openvpn.fragments.GraphFragment;
import de.blinkt.openvpn.fragments.LogFragment;
import de.blinkt.openvpn.fragments.SendDumpFragment;
import de.blinkt.openvpn.fragments.VPNProfileList;
@@ -55,6 +56,7 @@ public class MainActivity extends BaseActivity {
mPagerAdapter.addTab(R.string.vpn_list_title, VPNProfileList.class);
+ mPagerAdapter.addTab(R.string.graph, GraphFragment.class);
mPagerAdapter.addTab(R.string.generalsettings, GeneralSettings.class);
mPagerAdapter.addTab(R.string.faq, FaqFragment.class);
@@ -63,6 +65,7 @@ public class MainActivity extends BaseActivity {
mPagerAdapter.addTab(R.string.crashdump, SendDumpFragment.class);
}
+
if (isDirectToTV())
mPagerAdapter.addTab(R.string.openvpn_log, LogFragment.class);
diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
index 2e68b202..a4c66e9d 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNService.java
@@ -122,7 +122,7 @@ public class OpenVPNService extends VpnService implements StateListener, Callbac
bytes = bytes * 8;
int unit = mbit ? 1000 : 1024;
if (bytes < unit)
- return bytes + (mbit ? " bit" : " B");
+ return bytes + (mbit ? "\u2009bit" : "\u2009B");
int exp = (int) (Math.log(bytes) / Math.log(unit));
String pre = (mbit ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (mbit ? "" : "");
diff --git a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java
index d025b2d4..6df1379a 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/OpenVPNStatusService.java
@@ -122,6 +122,11 @@ public class OpenVPNStatusService extends Service implements VpnStatus.LogListen
PasswordCache.setCachedPassword(uuid, type, password);
}
+ @Override
+ public TrafficHistory getTrafficHistory() throws RemoteException {
+ return VpnStatus.trafficHistory;
+ }
+
};
@Override
diff --git a/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java b/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java
index 16d580b7..5d0b7037 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/StatusListener.java
@@ -36,6 +36,7 @@ public class StatusListener {
if (service.queryLocalInterface("de.blinkt.openvpn.core.IServiceStatus") == null) {
// Not a local service
VpnStatus.setConnectedVPNProfile(serviceStatus.getLastConnectedVPN());
+ VpnStatus.setTrafficHistory(serviceStatus.getTrafficHistory());
ParcelFileDescriptor pfd = serviceStatus.registerStatusCallback(mCallback);
DataInputStream fd = new DataInputStream(new ParcelFileDescriptor.AutoCloseInputStream(pfd));
diff --git a/main/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java b/main/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java
new file mode 100644
index 00000000..019f58be
--- /dev/null
+++ b/main/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java
@@ -0,0 +1,240 @@
+/*
+ * Copyright (c) 2012-2017 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.core;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Vector;
+
+import static java.lang.Math.max;
+
+/**
+ * Created by arne on 23.05.17.
+ */
+
+public class TrafficHistory implements Parcelable {
+
+ private LinkedList<TrafficDatapoint> trafficHistorySeconds = new LinkedList<>();
+ private LinkedList<TrafficDatapoint> trafficHistoryMinutes = new LinkedList<>();
+ private LinkedList<TrafficDatapoint> trafficHistoryHours = new LinkedList<>();
+
+ private TrafficDatapoint lastSecondUsedForMinute;
+ private TrafficDatapoint lastMinuteUsedForHours;
+
+ public TrafficHistory() {
+
+ }
+
+ protected TrafficHistory(Parcel in) {
+ in.readList(trafficHistorySeconds, getClass().getClassLoader());
+ in.readList(trafficHistoryMinutes, getClass().getClassLoader());
+ in.readList(trafficHistoryHours, getClass().getClassLoader());
+ lastSecondUsedForMinute = in.readParcelable(getClass().getClassLoader());
+ lastMinuteUsedForHours = in.readParcelable(getClass().getClassLoader());
+ }
+
+ public static final Creator<TrafficHistory> CREATOR = new Creator<TrafficHistory>() {
+ @Override
+ public TrafficHistory createFromParcel(Parcel in) {
+ return new TrafficHistory(in);
+ }
+
+ @Override
+ public TrafficHistory[] newArray(int size) {
+ return new TrafficHistory[size];
+ }
+ };
+
+ public LastDiff getLastDiff(TrafficDatapoint tdp) {
+
+ TrafficDatapoint lasttdp;
+
+
+ if (trafficHistorySeconds.size() == 0)
+ lasttdp = new TrafficDatapoint(0, 0, System.currentTimeMillis());
+
+ else
+ lasttdp = trafficHistorySeconds.getLast();
+
+ if (tdp == null) {
+ tdp = lasttdp;
+ if (trafficHistorySeconds.size() < 2)
+ lasttdp = tdp;
+ else {
+ trafficHistorySeconds.descendingIterator().next();
+ tdp = trafficHistorySeconds.descendingIterator().next();
+ }
+ }
+
+ return new LastDiff(lasttdp, tdp);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeList(trafficHistorySeconds);
+ dest.writeList(trafficHistoryMinutes);
+ dest.writeList(trafficHistoryHours);
+ dest.writeParcelable(lastSecondUsedForMinute, 0);
+ dest.writeParcelable(lastMinuteUsedForHours, 0);
+
+ }
+
+ public LinkedList<TrafficDatapoint> getHours() {
+ return trafficHistoryHours;
+ }
+
+ public LinkedList<TrafficDatapoint> getMinutes() {
+ return trafficHistoryMinutes;
+ }
+
+ public LinkedList<TrafficDatapoint> getSeconds() {
+ return trafficHistorySeconds;
+ }
+
+ public static LinkedList<TrafficDatapoint> getDummyList() {
+ LinkedList<TrafficDatapoint> list = new LinkedList<>();
+ list.add(new TrafficDatapoint(0, 0, System.currentTimeMillis()));
+ return list;
+ }
+
+
+ public static class TrafficDatapoint implements Parcelable {
+ private TrafficDatapoint(long inBytes, long outBytes, long timestamp) {
+ this.in = inBytes;
+ this.out = outBytes;
+ this.timestamp = timestamp;
+ }
+
+ public final long timestamp;
+ public final long in;
+ public final long out;
+
+ private TrafficDatapoint(Parcel in) {
+ timestamp = in.readLong();
+ this.in = in.readLong();
+ out = in.readLong();
+ }
+
+ public static final Creator<TrafficDatapoint> CREATOR = new Creator<TrafficDatapoint>() {
+ @Override
+ public TrafficDatapoint createFromParcel(Parcel in) {
+ return new TrafficDatapoint(in);
+ }
+
+ @Override
+ public TrafficDatapoint[] newArray(int size) {
+ return new TrafficDatapoint[size];
+ }
+ };
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(timestamp);
+ dest.writeLong(in);
+ dest.writeLong(out);
+ }
+ }
+
+ LastDiff add(long in, long out) {
+ TrafficDatapoint tdp = new TrafficDatapoint(in, out, System.currentTimeMillis());
+
+ LastDiff diff = getLastDiff(tdp);
+ addDataPoint(tdp);
+ return diff;
+ }
+
+ private void addDataPoint(TrafficDatapoint tdp) {
+ trafficHistorySeconds.add(tdp);
+
+ if (lastSecondUsedForMinute == null) {
+ lastSecondUsedForMinute = new TrafficDatapoint(0, 0, 0);
+ lastMinuteUsedForHours = new TrafficDatapoint(0, 0, 0);
+ }
+
+ removeAndAverage(tdp, true);
+ }
+
+ private void removeAndAverage(TrafficDatapoint newTdp, boolean seconds) {
+ HashSet<TrafficDatapoint> toRemove = new HashSet<>();
+ Vector<TrafficDatapoint> toAverage = new Vector<>();
+
+ long timePeriod;
+ LinkedList<TrafficDatapoint> tpList, nextList;
+ TrafficDatapoint lastTsPeriod;
+
+ if (seconds) {
+ timePeriod = 60 * 1000;
+ tpList = trafficHistorySeconds;
+ nextList = trafficHistoryMinutes;
+ lastTsPeriod = lastSecondUsedForMinute;
+ } else {
+ timePeriod = 3600 * 1000;
+ tpList = trafficHistoryMinutes;
+ nextList = trafficHistoryHours;
+ lastTsPeriod = lastMinuteUsedForHours;
+ }
+
+ if (newTdp.timestamp / timePeriod > (lastTsPeriod.timestamp / timePeriod)) {
+ nextList.add(newTdp);
+
+ if (seconds) {
+ lastSecondUsedForMinute = newTdp;
+ removeAndAverage(newTdp, false);
+ } else
+ lastMinuteUsedForHours = newTdp;
+
+ for (TrafficDatapoint tph : tpList) {
+ // List is iteratered from oldest to newest, remembert first one that we did not
+ if ((newTdp.timestamp - tph.timestamp) / timePeriod > 4)
+ toRemove.add(tph);
+ }
+ tpList.removeAll(toRemove);
+ }
+ }
+
+ static class LastDiff {
+
+ final private TrafficDatapoint tdp;
+ final private TrafficDatapoint lasttdp;
+
+ private LastDiff(TrafficDatapoint lasttdp, TrafficDatapoint tdp) {
+ this.lasttdp = lasttdp;
+ this.tdp = tdp;
+ }
+
+ public long getDiffOut() {
+ return max(0, tdp.out - lasttdp.out);
+ }
+
+ public long getDiffIn() {
+ return max(0, tdp.in - lasttdp.in);
+ }
+
+ public long getIn() {
+ return tdp.in;
+ }
+
+ public long getOut() {
+ return tdp.out;
+ }
+
+ }
+
+
+} \ No newline at end of file
diff --git a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
index c1f8a736..3adf7c88 100644
--- a/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
+++ b/main/src/main/java/de/blinkt/openvpn/core/VpnStatus.java
@@ -15,7 +15,9 @@ import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.LinkedList;
import java.util.Locale;
+import java.util.Queue;
import java.util.Vector;
+import java.util.concurrent.ConcurrentLinkedQueue;
import de.blinkt.openvpn.R;
import de.blinkt.openvpn.VpnProfile;
@@ -35,13 +37,15 @@ public class VpnStatus {
private static int mLastStateresid = R.string.state_noprocess;
- private static long mlastByteCount[] = {0, 0, 0, 0};
private static HandlerThread mHandlerThread;
private static String mLastConnectedVPNUUID;
static boolean readFileLog =false;
final static java.lang.Object readFileLock = new Object();
+
+ public static TrafficHistory trafficHistory;
+
public static void logException(LogLevel ll, String context, Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
@@ -141,6 +145,10 @@ public class VpnStatus {
return mLastConnectedVPNUUID;
}
+ public static void setTrafficHistory(TrafficHistory trafficHistory) {
+ VpnStatus.trafficHistory = trafficHistory;
+ }
+
public enum LogLevel {
INFO(2),
@@ -194,7 +202,7 @@ public class VpnStatus {
logListener = new Vector<>();
stateListener = new Vector<>();
byteCountListener = new Vector<>();
-
+ trafficHistory = new TrafficHistory();
logInformation();
@@ -248,7 +256,8 @@ public class VpnStatus {
}
public synchronized static void addByteCountListener(ByteCountListener bcl) {
- bcl.updateByteCount(mlastByteCount[0], mlastByteCount[1], mlastByteCount[2], mlastByteCount[3]);
+ TrafficHistory.LastDiff diff = trafficHistory.getLastDiff(null);
+ bcl.updateByteCount(diff.getIn(), diff.getOut(), diff.getDiffIn(),diff.getDiffOut());
byteCountListener.add(bcl);
}
@@ -457,17 +466,10 @@ public class VpnStatus {
public static synchronized void updateByteCount(long in, long out) {
- long lastIn = mlastByteCount[0];
- long lastOut = mlastByteCount[1];
- long diffIn = mlastByteCount[2] = Math.max(0, in - lastIn);
- long diffOut = mlastByteCount[3] = Math.max(0, out - lastOut);
-
+ TrafficHistory.LastDiff diff = trafficHistory.add(in, out);
- mlastByteCount = new long[]{in, out, diffIn, diffOut};
for (ByteCountListener bcl : byteCountListener) {
- bcl.updateByteCount(in, out, diffIn, diffOut);
+ bcl.updateByteCount(in, out, diff.getDiffIn(), diff.getDiffOut());
}
}
-
-
}
diff --git a/main/src/main/java/de/blinkt/openvpn/fragments/GraphFragment.java b/main/src/main/java/de/blinkt/openvpn/fragments/GraphFragment.java
new file mode 100644
index 00000000..c3a63fa2
--- /dev/null
+++ b/main/src/main/java/de/blinkt/openvpn/fragments/GraphFragment.java
@@ -0,0 +1,378 @@
+/*
+ * Copyright (c) 2012-2017 Arne Schwabe
+ * Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ */
+
+package de.blinkt.openvpn.fragments;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.os.Handler;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.CheckBox;
+import android.widget.CompoundButton;
+import android.widget.ListView;
+import android.widget.TextView;
+
+import com.github.mikephil.charting.charts.LineChart;
+import com.github.mikephil.charting.components.AxisBase;
+import com.github.mikephil.charting.components.XAxis;
+import com.github.mikephil.charting.components.YAxis;
+import com.github.mikephil.charting.data.Entry;
+import com.github.mikephil.charting.data.LineData;
+import com.github.mikephil.charting.data.LineDataSet;
+import com.github.mikephil.charting.formatter.IAxisValueFormatter;
+import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
+
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Locale;
+
+import de.blinkt.openvpn.R;
+import de.blinkt.openvpn.core.OpenVPNManagement;
+import de.blinkt.openvpn.core.TrafficHistory;
+import de.blinkt.openvpn.core.VpnStatus;
+
+import static android.content.Context.MODE_PRIVATE;
+import static de.blinkt.openvpn.core.OpenVPNService.humanReadableByteCount;
+import static java.lang.Math.max;
+
+/**
+ * Created by arne on 19.05.17.
+ */
+
+public class GraphFragment extends Fragment implements VpnStatus.ByteCountListener {
+ private static final String PREF_USE_LOG = "useLogGraph";
+ private ListView mListView;
+
+ private ChartDataAdapter mChartAdapter;
+ private int mColorIn;
+ private int mColorOut;
+
+ private long firstTs;
+ private TextView mSpeedStatus;
+ private boolean mLogScale;
+
+ private static final int TIME_PERIOD_SECDONS = 0;
+ private static final int TIME_PERIOD_MINUTES = 1;
+ private static final int TIME_PERIOD_HOURS = 2;
+ private Handler mHandler;
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.graph, container, false);
+ mListView = (ListView) v.findViewById(R.id.graph_listview);
+ mSpeedStatus = (TextView) v.findViewById(R.id.speedStatus);
+ CheckBox logScaleView = (CheckBox) v.findViewById(R.id.useLogScale);
+ mLogScale = getActivity().getPreferences(MODE_PRIVATE).getBoolean(PREF_USE_LOG, false);
+ logScaleView.setChecked(mLogScale);
+
+ List<Integer> charts = new LinkedList<>();
+ charts.add(TIME_PERIOD_SECDONS);
+ charts.add(TIME_PERIOD_MINUTES);
+ charts.add(TIME_PERIOD_HOURS);
+
+ mChartAdapter = new ChartDataAdapter(getActivity(), charts);
+ mListView.setAdapter(mChartAdapter);
+
+ mColorIn = getActivity().getResources().getColor(R.color.dataIn);
+ mColorOut = getActivity().getResources().getColor(R.color.dataOut);
+
+
+ logScaleView.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
+ @Override
+ public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
+ mLogScale = isChecked;
+ mChartAdapter.notifyDataSetChanged();
+ getActivity().getPreferences(MODE_PRIVATE).edit().putBoolean(PREF_USE_LOG, isChecked).apply();
+ }
+ });
+
+ mHandler = new Handler();
+
+ return v;
+
+
+ }
+
+ private Runnable triggerRefresh = new Runnable() {
+ @Override
+ public void run() {
+ mChartAdapter.notifyDataSetChanged();
+ mHandler.postDelayed(triggerRefresh, OpenVPNManagement.mBytecountInterval*1500);
+ }
+ };
+
+ @Override
+ public void onResume() {
+ super.onResume();
+
+ VpnStatus.addByteCountListener(this);
+ mHandler.postDelayed(triggerRefresh, OpenVPNManagement.mBytecountInterval*1500);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+
+ mHandler.removeCallbacks(triggerRefresh);
+ VpnStatus.removeByteCountListener(this);
+ }
+
+ @Override
+ public void updateByteCount(long in, long out, long diffIn, long diffOut) {
+ if (firstTs == 0)
+ firstTs = System.currentTimeMillis() / 100;
+
+ long now = (System.currentTimeMillis() / 100) - firstTs;
+ int interval = OpenVPNManagement.mBytecountInterval * 10;
+
+ final String netstat = String.format(getString(R.string.statusline_bytecount),
+ humanReadableByteCount(in, false),
+ humanReadableByteCount(diffIn / OpenVPNManagement.mBytecountInterval, true),
+ humanReadableByteCount(out, false),
+ humanReadableByteCount(diffOut / OpenVPNManagement.mBytecountInterval, true));
+
+ getActivity().runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mHandler.removeCallbacks(triggerRefresh);
+ mSpeedStatus.setText(netstat);
+ mChartAdapter.notifyDataSetChanged();
+ mHandler.postDelayed(triggerRefresh, OpenVPNManagement.mBytecountInterval*1500);
+ }
+ });
+
+ }
+
+ private class ChartDataAdapter extends ArrayAdapter<Integer> {
+
+ private Context mContext;
+
+ public ChartDataAdapter(Context context, List<Integer> trafficData) {
+ super(context, 0, trafficData);
+ mContext = context;
+ }
+
+ @NonNull
+ @Override
+ public View getView(final int position, @Nullable View convertView, @NonNull ViewGroup parent) {
+ ViewHolder holder = null;
+
+ if (convertView == null) {
+
+ holder = new ViewHolder();
+
+ convertView = LayoutInflater.from(mContext).inflate(
+ R.layout.graph_item, parent, false);
+ holder.chart = (LineChart) convertView.findViewById(R.id.chart);
+ holder.title = (TextView) convertView.findViewById(R.id.tvName);
+ convertView.setTag(holder);
+
+ } else {
+ holder = (ViewHolder) convertView.getTag();
+ }
+
+ // apply styling
+ // holder.chart.setValueTypeface(mTf);
+ holder.chart.getDescription().setEnabled(false);
+ holder.chart.setDrawGridBackground(false);
+
+ XAxis xAxis = holder.chart.getXAxis();
+ xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
+ xAxis.setDrawGridLines(false);
+ xAxis.setDrawAxisLine(true);
+
+ switch (position){
+ case TIME_PERIOD_HOURS:
+ holder.title.setText(R.string.avghour);
+ break;
+ case TIME_PERIOD_MINUTES:
+ holder.title.setText(R.string.avgmin);
+ break;
+ default:
+ holder.title.setText(R.string.last5minutes);
+ break;
+ }
+
+ xAxis.setValueFormatter(new IAxisValueFormatter() {
+
+
+ @Override
+ public String getFormattedValue(float value, AxisBase axis) {
+ switch (position){
+ case TIME_PERIOD_HOURS:
+ return String.format(Locale.getDefault(), "%.0f\u2009h ago", (axis.getAxisMaximum() - value) / 10/3600);
+ case TIME_PERIOD_MINUTES:
+ return String.format(Locale.getDefault(), "%.0f\u2009m ago", (axis.getAxisMaximum() - value) / 10/60);
+ default:
+ return String.format(Locale.getDefault(), "%.0f\u2009s ago", (axis.getAxisMaximum() - value) / 10);
+ }
+
+ }
+ });
+ xAxis.setLabelCount(5);
+
+ YAxis yAxis = holder.chart.getAxisLeft();
+ yAxis.setLabelCount(5, false);
+
+ yAxis.setValueFormatter(new IAxisValueFormatter() {
+ @Override
+ public String getFormattedValue(float value, AxisBase axis) {
+ if (mLogScale && value < 2.1f)
+ return "< 100\u2009bit";
+ if (mLogScale)
+ value = (float) Math.pow(10, value)/8;
+
+ return humanReadableByteCount((long) value, true);
+ }
+ });
+
+ holder.chart.getAxisRight().setEnabled(false);
+
+ LineData data = getDataSet(position);
+ float ymax = data.getYMax();
+
+ if (mLogScale) {
+ yAxis.setAxisMinimum(2f);
+ yAxis.setAxisMaximum((float) Math.ceil(ymax));
+ yAxis.setLabelCount((int) (Math.ceil(ymax -2f)));
+ } else {
+ yAxis.setAxisMinimum(0f);
+ yAxis.resetAxisMaximum();
+ yAxis.setLabelCount(6);
+ }
+
+ if (data.getDataSetByIndex(0).getEntryCount() < 3)
+ holder.chart.setData(null);
+ else
+ holder.chart.setData(data);
+
+ holder.chart.setNoDataText(getString(R.string.notenoughdata));
+
+ holder.chart.invalidate();
+ //holder.chart.animateX(750);
+
+ return convertView;
+ }
+
+
+ public void getAverageForGraphList(boolean in, int timeperiod) {
+
+
+
+ }
+
+ private LineData getDataSet(int timeperiod) {
+
+ LinkedList<Entry> dataIn = new LinkedList<>();
+ LinkedList<Entry> dataOut = new LinkedList<>();
+
+ long interval;
+
+ LinkedList<TrafficHistory.TrafficDatapoint> list;
+ switch (timeperiod) {
+ case TIME_PERIOD_HOURS:
+ list = VpnStatus.trafficHistory.getHours();
+ interval = 3600 ;
+ break;
+ case TIME_PERIOD_MINUTES:
+ list = VpnStatus.trafficHistory.getMinutes();
+ interval = 60;
+ break;
+ default:
+ list = VpnStatus.trafficHistory.getSeconds();
+ interval = OpenVPNManagement.mBytecountInterval ;
+ break;
+ }
+ if (list.size()==0) {
+ list = TrafficHistory.getDummyList();
+ }
+
+ long firstTimestamp = list.peek().timestamp;
+ long lastBytecountIn = list.peek().in;
+ long lastBytecountOut = list.peek().out;
+
+ long lastts=0;
+
+ for (TrafficHistory.TrafficDatapoint tdp: list){
+ float t = (tdp.timestamp - firstTimestamp) / 100f;
+
+ float in = (tdp.in - lastBytecountIn)/ (float) interval;
+ float out = (tdp.out - lastBytecountOut) / (float) interval;
+
+ lastBytecountIn = tdp.in;
+ lastBytecountOut = tdp.out;
+
+ if (mLogScale) {
+ in = max(2f, (float) Math.log10(in*8));
+ out = max(2f, (float) Math.log10(out* 8));
+ }
+
+ if (lastts > 0 && ( tdp.timestamp -lastts> 2 * interval*1000)){
+ dataIn.add(new Entry((lastts- firstTimestamp+ interval)/100f, 0));
+ dataOut.add(new Entry((lastts- firstTimestamp+ interval)/100f, 0));
+
+ dataIn.add(new Entry(t - interval/100f, 0));
+ dataOut.add(new Entry(t - interval/100f, 0));
+ }
+
+ lastts = tdp.timestamp;
+
+ dataIn.add(new Entry(t,in));
+ dataOut.add(new Entry(t,out));
+
+ }
+ long now = System.currentTimeMillis();
+ if (list.peekLast().timestamp < now-interval*1000l) {
+ dataIn.add(new Entry((now-firstTimestamp)/100, 0));
+ dataOut.add(new Entry((now-firstTimestamp) /100, 0));
+
+ if (now -lastts > 2 * interval*1000) {
+ dataIn.add(new Entry((lastts- firstTimestamp+ interval)/100f, 0));
+ dataOut.add(new Entry((lastts- firstTimestamp+ interval)/100f, 0));
+ }
+ }
+
+ ArrayList<ILineDataSet> dataSets = new ArrayList<>();
+
+
+ LineDataSet indata = new LineDataSet(dataIn, "In");
+ LineDataSet outdata = new LineDataSet(dataOut, "Out");
+
+ setLineDataAttributes(indata, mColorIn);
+ setLineDataAttributes(outdata, mColorOut);
+
+ dataSets.add(indata);
+ dataSets.add(outdata);
+
+ return new LineData(dataSets);
+ }
+
+ private void setLineDataAttributes(LineDataSet dataSet, int colour) {
+ dataSet.setLineWidth(2);
+ dataSet.setCircleRadius(1);
+ dataSet.setDrawCircles(false);
+ dataSet.setDrawFilled(true);
+ dataSet.setFillAlpha(42);
+ dataSet.setFillColor(colour);
+ dataSet.setColor(colour);
+ dataSet.setMode(LineDataSet.Mode.LINEAR);
+
+ dataSet.setDrawValues(false);
+ }
+ }
+
+ private static class ViewHolder {
+ LineChart chart;
+ TextView title;
+ }
+}
diff --git a/main/src/main/res/layout/graph.xml b/main/src/main/res/layout/graph.xml
new file mode 100644
index 00000000..d734623e
--- /dev/null
+++ b/main/src/main/res/layout/graph.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (c) 2012-2017 Arne Schwabe
+ ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+
+ <TextView
+ android:id="@+id/speedStatus"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_alignParentBottom="true"
+ tools:text="some speed status" />
+
+ <CheckBox
+ android:id="@+id/useLogScale"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_above="@id/speedStatus"
+ android:text="@string/use_logarithmic_scale" />
+
+ <ListView
+ android:id="@+id/graph_listview"
+ android:layout_above="@id/useLogScale"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+
+</RelativeLayout> \ No newline at end of file
diff --git a/main/src/main/res/layout/graph_item.xml b/main/src/main/res/layout/graph_item.xml
new file mode 100644
index 00000000..da90342b
--- /dev/null
+++ b/main/src/main/res/layout/graph_item.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+ ~ Copyright (c) 2012-2017 Arne Schwabe
+ ~ Distributed under the GNU GPL v2 with additional terms. For full terms see the file doc/LICENSE.txt
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ xmlns:tools="http://schemas.android.com/tools"
+ android:padding="8dp">
+
+ <TextView
+ android:id="@+id/tvName"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentLeft="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentTop="true"
+ android:layout_marginLeft="4dp"
+ android:layout_marginStart="4dp"
+ tools:text="Medium Text"
+ android:textSize="16sp" />
+
+ <com.github.mikephil.charting.charts.LineChart
+ android:layout_below="@id/tvName"
+ android:id="@+id/chart"
+ android:layout_width="match_parent"
+ android:layout_height="200dp" />
+
+</RelativeLayout> \ No newline at end of file
diff --git a/main/src/main/res/values/colours.xml b/main/src/main/res/values/colours.xml
index d06bc233..cf4a2a7f 100644
--- a/main/src/main/res/values/colours.xml
+++ b/main/src/main/res/values/colours.xml
@@ -18,4 +18,7 @@
<color name="background_tab_pressed">#1AFFFFFF</color>
+
+ <color name="dataIn">#ff0000</color>
+ <color name="dataOut">#0000ff</color>
</resources> \ No newline at end of file
diff --git a/main/src/main/res/values/strings.xml b/main/src/main/res/values/strings.xml
index 69ddadd4..2077d02e 100755
--- a/main/src/main/res/values/strings.xml
+++ b/main/src/main/res/values/strings.xml
@@ -424,5 +424,11 @@
<string name="sorted_az">Profiles sorted by name</string>
<string name="deprecated_tls_remote">Config uses option tls-remote that was deprecated in 2.3 and finally removed in 2.4</string>
<string name="auth_failed_behaviour">Behaviour on AUTH_FAILED</string>
+ <string name="graph">Graph</string>
+ <string name="use_logarithmic_scale">Use logarithmic scale</string>
+ <string name="notenoughdata">Not enough data</string>
+ <string name="avghour">Average per hour</string>
+ <string name="avgmin">Average per minute</string>
+ <string name="last5minutes">Last 5 minutes</string>
</resources>