package com.openvehicles.OVMS.ui.settings;
import android.graphics.Color;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Toast;
import com.actionbarsherlock.app.SherlockFragmentActivity;
import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.utils.ValueFormatter;
import com.openvehicles.OVMS.R;
import com.openvehicles.OVMS.api.ApiService;
import com.openvehicles.OVMS.api.OnResultCommandListener;
import com.openvehicles.OVMS.entities.CarData;
import com.openvehicles.OVMS.ui.BaseFragment;
import com.openvehicles.OVMS.ui.utils.Ui;
import java.util.ArrayList;
public class CellularStatsFragment extends BaseFragment implements OnResultCommandListener {
private static final String TAG = "CellularStatsFragment";
// chart colors:
private static final int COLOR_TX = Color.parseColor("#33B5E5");
private static final int COLOR_RX = Color.parseColor("#99CC00");
// data model:
private class UsageData {
public String date;
public int carRxBytes;
public int carTxBytes;
public int appRxBytes;
public int appTxBytes;
private UsageData(String date, int carRxBytes, int carTxBytes, int appRxBytes, int appTxBytes) {
// fill object:
this.date = date;
this.carRxBytes = carRxBytes;
this.carTxBytes = carTxBytes;
this.appRxBytes = appRxBytes;
this.appTxBytes = appTxBytes;
// update totals:
carTotalBytes += carRxBytes + carTxBytes;
appTotalBytes += appRxBytes + appTxBytes;
}
}
// data storage:
private long lastRetrieve;
private ArrayList<UsageData> mUsageData;
private int recNr;
private int recCnt;
private long carTotalBytes;
private long appTotalBytes;
// UI:
BarChart mChartViewCar;
BarChart mChartViewApp;
// system services:
private ApiService mService;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
createProgressOverlay(inflater, container, true);
View rootView = inflater.inflate(R.layout.fragment_cellular_stats, null);
//
// setup car chart:
//
XAxis xAxis;
YAxis yAxis;
mChartViewCar = (BarChart) rootView.findViewById(R.id.cellular_usage_chart_car);
mChartViewCar.setDescription("");
mChartViewCar.setMaxVisibleValueCount(30);
mChartViewCar.setDrawValuesForWholeStack(false);
mChartViewCar.setPinchZoom(false);
mChartViewCar.setDrawBarShadow(false);
mChartViewCar.setDrawValueAboveBar(true);
mChartViewCar.getPaint(LineChart.PAINT_DESCRIPTION).setColor(Color.LTGRAY);
mChartViewCar.setDrawGridBackground(false);
mChartViewCar.setDrawBorders(true);
xAxis = mChartViewCar.getXAxis();
xAxis.setTextColor(Color.WHITE);
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
yAxis = mChartViewCar.getAxisLeft();
yAxis.setTextColor(Color.WHITE);
yAxis.setGridColor(Color.LTGRAY);
yAxis.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float v) {
return String.format("%.0f", v);
}
});
yAxis = mChartViewCar.getAxisRight();
yAxis.setEnabled(false);
//
// setup app chart:
//
mChartViewApp = (BarChart) rootView.findViewById(R.id.cellular_usage_chart_app);
mChartViewApp.setDescription("");
mChartViewApp.setMaxVisibleValueCount(30);
mChartViewApp.setDrawValuesForWholeStack(false);
mChartViewApp.setPinchZoom(false);
mChartViewApp.setDrawBarShadow(false);
mChartViewApp.setDrawValueAboveBar(true);
mChartViewApp.getPaint(LineChart.PAINT_DESCRIPTION).setColor(Color.LTGRAY);
mChartViewApp.setDrawGridBackground(false);
mChartViewApp.setDrawBorders(true);
xAxis = mChartViewApp.getXAxis();
xAxis.setTextColor(Color.WHITE);
xAxis.setPosition(XAxis.XAxisPosition.BOTTOM);
yAxis = mChartViewApp.getAxisLeft();
yAxis.setTextColor(Color.WHITE);
yAxis.setGridColor(Color.LTGRAY);
yAxis.setValueFormatter(new ValueFormatter() {
@Override
public String getFormattedValue(float v) {
return String.format("%.0f", v);
}
});
yAxis = mChartViewApp.getAxisRight();
yAxis.setEnabled(false);
return rootView;
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
SherlockFragmentActivity activity = getSherlockActivity();
activity.setTitle(R.string.CellularStats);
}
@Override
public void onServiceAvailable(ApiService pService) {
mService = pService;
}
@Override
public void update(CarData pCarData) {
// called after login / if new data is available
requestData();
}
// request data from server:
public void requestData() {
// check if server is available:
if (mService == null || mService.isLoggedIn() == false)
return;
// check if we need an update:
long now = System.currentTimeMillis() / 1000;
if ((now - lastRetrieve) > 3600) {
// last update was >1 hour ago: refresh data
lastRetrieve = now;
// Init storage:
mUsageData = new ArrayList<UsageData>(90);
carTotalBytes = 0;
appTotalBytes = 0;
recCnt = 0;
recNr = 0;
// Show progress bar:
showProgressOverlay();
// Request cellular usage data:
mService.sendCommand("30", this);
} else {
// need to hide explicitly because onStart=true:
hideProgressOverlay();
}
}
@Override
public void onResultCommand(String[] result) {
if (result.length < 2)
return;
int command = Integer.parseInt(result[0]);
int returnCode = Integer.parseInt(result[1]);
if (command != 30) return; // Not for us
switch (returnCode) {
case 0: // ok
/** Result array structure:
* 0=command
* 1=return code
* 2=record number
* 3=maximum number of records
* 4=date (YYYY-MM-DD) (chronologically reverse)
* 5=car received bytes
* 6=car transmitted bytes
* 7=apps received bytes
* 8=apps transmitted bytes
*/
if (result.length >= 9) {
try {
// Progress info:
recNr = Integer.parseInt(result[2]);
recCnt = Integer.parseInt(result[3]);
stepProgressOverlay(recNr, recCnt);
// parse result data:
String nDate = result[4];
int nCarRxBytes = Integer.parseInt(result[5]);
int nCarTxBytes = Integer.parseInt(result[6]);
int nAppRxBytes = Integer.parseInt(result[7]);
int nAppTxBytes = Integer.parseInt(result[8]);
// add UsageData in reverse order:
mUsageData.add(0, new UsageData(nDate, nCarRxBytes, nCarTxBytes, nAppRxBytes,nAppTxBytes));
// got all?
if (recNr == recCnt) {
cancelCommand();
updateUi();
}
} catch(Exception e) {
// malformed record, ignore
}
}
break;
case 1: // failed
Toast.makeText(getActivity(), getString(R.string.err_failed, result[2]),
Toast.LENGTH_SHORT).show();
cancelCommand();
break;
case 2: // unsupported
Toast.makeText(getActivity(), getString(R.string.err_unsupported_operation),
Toast.LENGTH_SHORT).show();
cancelCommand();
break;
case 3: // unimplemented
Toast.makeText(getActivity(), getString(R.string.err_unimplemented_operation),
Toast.LENGTH_SHORT).show();
cancelCommand();
}
}
public void updateUi() {
if (recCnt == 0 || recNr < recCnt)
return;
// Data ready: show results
int days = mUsageData.size();
float carMBPerMonth = carTotalBytes / days * 30 / 1000000;
float appMBPerMonth = appTotalBytes / days * 30 / 1000000;
View rootView = getView();
Ui.setValue(rootView, R.id.cellular_usage_info_car, getString(R.string.cellular_usage_info_car,
carMBPerMonth, days));
Ui.setValue(rootView, R.id.cellular_usage_info_app, getString(R.string.cellular_usage_info_app,
appMBPerMonth, days));
// Update charts:
UsageData day;
ArrayList<String> xValues = new ArrayList<String>();
ArrayList<BarEntry> carValues = new ArrayList<BarEntry>();
ArrayList<BarEntry> appValues = new ArrayList<BarEntry>();
for (int i = 0; i < mUsageData.size(); ++i) {
day = mUsageData.get(i);
xValues.add(day.date.substring(5));
carValues.add(new BarEntry(new float[] {
Math.round(day.carTxBytes / 100) / 10,
Math.round(day.carRxBytes / 100) / 10
}, i));
appValues.add(new BarEntry(new float[] {
Math.round(day.appTxBytes / 100) / 10,
Math.round(day.appRxBytes / 100) / 10
}, i));
}
BarDataSet dataSet;
BarData data;
ArrayList<BarDataSet> dataSets;
Legend legend;
int stackColors[] = { COLOR_TX, COLOR_RX };
String stackLabels[] = { "Tx", "Rx" };
// ...car chart:
dataSet = new BarDataSet(carValues, getString(R.string.chart_axis_datavolume));
dataSet.setColors(stackColors);
dataSet.setStackLabels(stackLabels);
dataSets = new ArrayList<BarDataSet>();
dataSets.add(dataSet);
data = new BarData(xValues, dataSets);
//data.setValueTextColor(Color.WHITE);
//data.setValueTextSize(9f);
data.setDrawValues(false);
mChartViewCar.setData(data);
mChartViewCar.zoom(3.5f, 1f, 0f, 0f);
mChartViewCar.moveViewToX(mUsageData.size() - 1);
legend = mChartViewCar.getLegend();
legend.setTextColor(Color.WHITE);
legend.setPosition(Legend.LegendPosition.BELOW_CHART_RIGHT);
mChartViewCar.invalidate();
// ...app chart:
dataSet = new BarDataSet(appValues, getString(R.string.chart_axis_datavolume));
dataSet.setColors(stackColors);
dataSet.setStackLabels(stackLabels);
dataSets = new ArrayList<BarDataSet>();
dataSets.add(dataSet);
data = new BarData(xValues, dataSets);
//data.setValueTextColor(Color.WHITE);
//data.setValueTextSize(9f);
data.setDrawValues(false);
mChartViewApp.setData(data);
mChartViewApp.zoom(3.5f, 1f, 0f, 0f);
mChartViewApp.moveViewToX(mUsageData.size()-1);
legend = mChartViewApp.getLegend();
legend.setTextColor(Color.WHITE);
legend.setPosition(Legend.LegendPosition.BELOW_CHART_RIGHT);
mChartViewApp.invalidate();
}
}