From 715e6887d795a2e52290fd27e98b76c26b898830 Mon Sep 17 00:00:00 2001 From: Arne Schwabe Date: Sat, 27 May 2017 20:55:24 +0200 Subject: Implement graphs for traffic history --- build.gradle | 3 + main/build.gradle | 4 +- .../de/blinkt/openvpn/core/IServiceStatus.aidl | 7 + .../de/blinkt/openvpn/core/IStatusCallbacks.aidl | 1 + .../de/blinkt/openvpn/core/TrafficHistory.aidl | 4 + .../de/blinkt/openvpn/activities/MainActivity.java | 3 + .../de/blinkt/openvpn/core/OpenVPNService.java | 2 +- .../blinkt/openvpn/core/OpenVPNStatusService.java | 5 + .../de/blinkt/openvpn/core/StatusListener.java | 1 + .../de/blinkt/openvpn/core/TrafficHistory.java | 240 +++++++++++++ .../java/de/blinkt/openvpn/core/VpnStatus.java | 26 +- .../de/blinkt/openvpn/fragments/GraphFragment.java | 378 +++++++++++++++++++++ main/src/main/res/layout/graph.xml | 33 ++ main/src/main/res/layout/graph_item.xml | 30 ++ main/src/main/res/values/colours.xml | 3 + main/src/main/res/values/strings.xml | 6 + settings.gradle | 1 - 17 files changed, 732 insertions(+), 15 deletions(-) create mode 100644 main/src/main/aidl/de/blinkt/openvpn/core/TrafficHistory.aidl create mode 100644 main/src/main/java/de/blinkt/openvpn/core/TrafficHistory.java create mode 100644 main/src/main/java/de/blinkt/openvpn/fragments/GraphFragment.java create mode 100644 main/src/main/res/layout/graph.xml create mode 100644 main/src/main/res/layout/graph_item.xml diff --git a/build.gradle b/build.gradle index 9833b1a4..f88bc5d2 100644 --- a/build.gradle +++ b/build.gradle @@ -5,6 +5,9 @@ buildscript { repositories { + maven { + url 'https://maven.google.com' + } jcenter() } dependencies { 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 trafficHistorySeconds = new LinkedList<>(); + private LinkedList trafficHistoryMinutes = new LinkedList<>(); + private LinkedList 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 CREATOR = new Creator() { + @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 getHours() { + return trafficHistoryHours; + } + + public LinkedList getMinutes() { + return trafficHistoryMinutes; + } + + public LinkedList getSeconds() { + return trafficHistorySeconds; + } + + public static LinkedList getDummyList() { + LinkedList 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 CREATOR = new Creator() { + @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 toRemove = new HashSet<>(); + Vector toAverage = new Vector<>(); + + long timePeriod; + LinkedList 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 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 { + + private Context mContext; + + public ChartDataAdapter(Context context, List 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 dataIn = new LinkedList<>(); + LinkedList dataOut = new LinkedList<>(); + + long interval; + + LinkedList 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 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 @@ + + + + + + + + + + + + + \ 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 @@ + + + + + + + + + \ 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 @@ #1AFFFFFF + + #ff0000 + #0000ff \ 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 @@ Profiles sorted by name Config uses option tls-remote that was deprecated in 2.3 and finally removed in 2.4 Behaviour on AUTH_FAILED + Graph + Use logarithmic scale + Not enough data + Average per hour + Average per minute + Last 5 minutes diff --git a/settings.gradle b/settings.gradle index 937181ea..46cb21bf 100644 --- a/settings.gradle +++ b/settings.gradle @@ -4,5 +4,4 @@ */ include ':main' -include ':vpndialogxposed' include ':remoteExample' -- cgit v1.2.3