diff options
Diffstat (limited to 'main/src/main/java/de/blinkt/openvpn/fragments/GraphFragment.java')
-rw-r--r-- | main/src/main/java/de/blinkt/openvpn/fragments/GraphFragment.java | 378 |
1 files changed, 378 insertions, 0 deletions
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; + } +} |