package com.openvehicles.OVMS.ui.settings; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.graphics.Typeface; import android.os.Bundle; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TabHost; import android.widget.TextView; import android.widget.ToggleButton; import com.openvehicles.OVMS.R; import com.openvehicles.OVMS.entities.CarData; import com.openvehicles.OVMS.entities.CmdSeries; import com.openvehicles.OVMS.entities.LogsData; import com.openvehicles.OVMS.ui.BaseFragment; import com.openvehicles.OVMS.ui.utils.ProgressOverlay; import com.openvehicles.OVMS.ui.utils.Ui; import com.openvehicles.OVMS.utils.CarsStorage; import java.util.ArrayList; /** * Created by balzer on 07.03.15. * * This is the user interface for the SEVCON diagnostic logs * as provided by the Twizy firmware. * * It allows to query, fetch and reset the SEVCON log classes * alerts, fault events, system events, counters and min/max sensors. * * Storage: LogsData * Layouts: fragment_logs, dlg_logs_cmd * * TODO: check if the TableAdapter class should become a common class * * */ public class LogsFragment extends BaseFragment implements View.OnClickListener, CmdSeries.Listener, ProgressOverlay.OnCancelListener { private static final String TAG = "LogsFragment"; // Data storage: private LogsData logsData; private int mEditPosition; private CarData mCarData; // System: private LayoutInflater layoutInflater; private CmdSeries cmdSeries; // UI: private TextView listAlertsInfo, listFaultEventsInfo, listSystemEventsInfo, listCountersInfo, listMinMaxesInfo; private ListView listAlerts, listFaultEvents, listSystemEvents, listCounters, listMinMaxes; private TableAdapter<LogsData.Alert> listAlertsAdapter; private TableAdapter<LogsData.Event> listFaultEventsAdapter; private TableAdapter<LogsData.Event> listSystemEventsAdapter; private TableAdapter<LogsData.Counter> listCountersAdapter; private TableAdapter<LogsData.MinMax> listMinMaxesAdapter; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // Init data storage: logsData = new LogsData(); // Setup UI: layoutInflater = inflater; ProgressOverlay progressOverlay = createProgressOverlay(inflater, container, false); progressOverlay.setOnCancelListener(this); View rootView = inflater.inflate(R.layout.fragment_logs, null); // Setup TabHost: TabHost tabHost = (TabHost) rootView.findViewById(R.id.tabHost); tabHost.setup(); tabHost.addTab(tabHost.newTabSpec("tab1") .setContent(R.id.tab1) .setIndicator(getString(R.string.logs_lb_alerts))); tabHost.addTab(tabHost.newTabSpec("tab2") .setContent(R.id.tab2) .setIndicator(getString(R.string.logs_lb_faults))); tabHost.addTab(tabHost.newTabSpec("tab3") .setContent(R.id.tab3) .setIndicator(getString(R.string.logs_lb_events))); tabHost.addTab(tabHost.newTabSpec("tab4") .setContent(R.id.tab4) .setIndicator(getString(R.string.logs_lb_counter))); tabHost.addTab(tabHost.newTabSpec("tab5") .setContent(R.id.tab5) .setIndicator(getString(R.string.logs_lb_minmax))); LinearLayout tab; // Setup ListView "Alerts": tab = (LinearLayout) tabHost.findViewById(R.id.tab1); listAlertsInfo = (TextView) tab.findViewById(R.id.listInfo1); listAlerts = (ListView) tab.findViewById(R.id.listView1); listAlertsAdapter = new TableAdapter<LogsData.Alert>(container.getContext(), logsData.alerts) { @Override public void makeColumns() { addColumn(getString(R.string.logs_th_code), 1, Gravity.LEFT); addColumn(getString(R.string.logs_th_description), 3, Gravity.LEFT); } @Override public String getValue(LogsData.Alert item, int row, int column) { switch (column) { case 0: return item.code; case 1: return item.description; default: return ""; } } }; ((LinearLayout)listAlerts.getParent()).addView( listAlertsAdapter.makeRow(container.getContext(), TableAdapter.HEAD), 0); listAlerts.setAdapter(listAlertsAdapter); // Setup ListView "Faults": tab = (LinearLayout) tabHost.findViewById(R.id.tab2); listFaultEventsInfo = (TextView) tab.findViewById(R.id.listInfo2); listFaultEvents = (ListView) tab.findViewById(R.id.listView2); listFaultEventsAdapter = new TableAdapter<LogsData.Event>(container.getContext(), logsData.faultEvents) { @Override public Object getItem(int i) { // reverse order: return mItems.get(mItems.size() - 1 - i); } @Override public void makeColumns() { addColumn(getString(R.string.logs_th_code), 1, Gravity.LEFT); addColumn(getString(R.string.logs_th_description), 3, Gravity.LEFT); addColumn(getString(R.string.logs_th_keytime), 2, Gravity.CENTER_HORIZONTAL); addColumn(getString(R.string.logs_th_data, 1), 1, Gravity.LEFT); addColumn(getString(R.string.logs_th_data, 2), 1, Gravity.LEFT); addColumn(getString(R.string.logs_th_data, 3), 1, Gravity.LEFT); } @Override public String getValue(LogsData.Event item, int row, int column) { switch (column) { case 0: return item.code; case 1: return item.description; case 2: return (item.time != null) ? item.time.fmtKeyTime() : ""; default: if (item.data != null && item.data.length > column) return item.data[column]; return ""; } } }; ((LinearLayout)listFaultEvents.getParent()).addView( listFaultEventsAdapter.makeRow(container.getContext(), TableAdapter.HEAD), 0); listFaultEvents.setAdapter(listFaultEventsAdapter); // Setup ListView "Events": tab = (LinearLayout) tabHost.findViewById(R.id.tab3); listSystemEventsInfo = (TextView) tab.findViewById(R.id.listInfo3); listSystemEvents = (ListView) tab.findViewById(R.id.listView3); listSystemEventsAdapter = new TableAdapter<LogsData.Event>(container.getContext(), logsData.systemEvents) { @Override public Object getItem(int i) { // reverse order: return mItems.get(mItems.size() - 1 - i); } @Override public void makeColumns() { addColumn(getString(R.string.logs_th_code), 1, Gravity.LEFT); addColumn(getString(R.string.logs_th_description), 3, Gravity.LEFT); addColumn(getString(R.string.logs_th_keytime), 2, Gravity.CENTER_HORIZONTAL); addColumn(getString(R.string.logs_th_data, 1), 1, Gravity.LEFT); addColumn(getString(R.string.logs_th_data, 2), 1, Gravity.LEFT); addColumn(getString(R.string.logs_th_data, 3), 1, Gravity.LEFT); } @Override public String getValue(LogsData.Event item, int row, int column) { switch (column) { case 0: return item.code; case 1: return item.description; case 2: return (item.time != null) ? item.time.fmtKeyTime() : ""; default: if (item.data != null && item.data.length > column) return item.data[column]; return ""; } } }; ((LinearLayout)listSystemEvents.getParent()).addView( listSystemEventsAdapter.makeRow(container.getContext(), TableAdapter.HEAD), 0); listSystemEvents.setAdapter(listSystemEventsAdapter); // Setup ListView "Counters": tab = (LinearLayout) tabHost.findViewById(R.id.tab4); listCountersInfo = (TextView) tab.findViewById(R.id.listInfo4); listCounters = (ListView) tab.findViewById(R.id.listView4); listCountersAdapter = new TableAdapter<LogsData.Counter>(container.getContext(), logsData.counters) { @Override public void makeColumns() { addColumn(getString(R.string.logs_th_code), 1, Gravity.LEFT); addColumn(getString(R.string.logs_th_description), 3, Gravity.LEFT); addColumn(getString(R.string.logs_th_lasttime), 2, Gravity.CENTER_HORIZONTAL); addColumn(getString(R.string.logs_th_firsttime), 2, Gravity.CENTER_HORIZONTAL); addColumn(getString(R.string.logs_th_count), 1, Gravity.CENTER_HORIZONTAL); } @Override public String getValue(LogsData.Counter item, int row, int column) { switch (column) { case 0: return item.code; case 1: return item.description; case 2: return (item.lastTime != null) ? item.lastTime.fmtKeyTime() : ""; case 3: return (item.firstTime != null) ? item.firstTime.fmtKeyTime() : ""; case 4: return "" + item.count; default: return ""; } } }; ((LinearLayout)listCounters.getParent()).addView( listCountersAdapter.makeRow(container.getContext(), TableAdapter.HEAD), 0); listCounters.setAdapter(listCountersAdapter); // Setup ListView "MinMax": tab = (LinearLayout) tabHost.findViewById(R.id.tab5); listMinMaxesInfo = (TextView) tab.findViewById(R.id.listInfo5); listMinMaxes = (ListView) tab.findViewById(R.id.listView5); listMinMaxesAdapter = new TableAdapter<LogsData.MinMax>(container.getContext(), logsData.minMaxes) { private LogsData.MinMax dummy = new LogsData.MinMax(); private String[] sensorNames = getResources().getStringArray(R.array.logs_th_sensornames); @Override public int getCount() { return 10; // number of sensors } @Override public Object getItem(int i) { // horizontal layout needs all items per row return dummy; } @Override public void makeColumns() { addColumn(getString(R.string.logs_th_sensor), 3, Gravity.LEFT); addColumn(getString(R.string.logs_th_user), 1, Gravity.CENTER); addColumn(getString(R.string.logs_th_sevcon), 1, Gravity.CENTER); } @Override public String getValue(LogsData.MinMax item, int row, int column) { switch (column) { case 0: return sensorNames[row]; case 1: item = (mItems.size() > 0) ? mItems.get(0) : null; return (item != null) ? item.getSensor(row) : ""; case 2: item = (mItems.size() > 1) ? mItems.get(1) : null; return (item != null) ? item.getSensor(row) : ""; default: return ""; } } }; ((LinearLayout)listMinMaxes.getParent()).addView( listMinMaxesAdapter.makeRow(container.getContext(), TableAdapter.HEAD), 0); listMinMaxes.setAdapter(listMinMaxesAdapter); // Done creating View: return rootView; } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); getSherlockActivity().setTitle(R.string.logs_title); // get data of car to edit: mEditPosition = getArguments().getInt("position", -1); if (mEditPosition >= 0) { mCarData = CarsStorage.get().getStoredCars().get(mEditPosition); } // load vehicle logs data: LogsData saved = LogsData.loadFile(mCarData.sel_vehicleid); if (saved != null) { //Log.v(TAG, "LogsData loaded for " + mCarData.sel_vehicleid); logsData = saved; updateUi(); } View rootView = getView(); Ui.setOnClick(rootView, R.id.btn_get_logs, this); Ui.setOnClick(rootView, R.id.btn_reset_logs, this); } @Override public void onClick(View view) { View content; switch (view.getId()) { case R.id.btn_get_logs: content = layoutInflater.inflate(R.layout.dlg_logs_cmd, null); new AlertDialog.Builder(getActivity()) .setTitle(R.string.logs_btn_get) .setView(content) .setNegativeButton(R.string.Cancel, null) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface pDlg, int pWhich) { Dialog dlg = (Dialog) pDlg; ToggleButton tbAlerts = (ToggleButton) dlg.findViewById(R.id.tbAlerts); ToggleButton tbFaults = (ToggleButton) dlg.findViewById(R.id.tbFaults); ToggleButton tbEvents = (ToggleButton) dlg.findViewById(R.id.tbEvents); ToggleButton tbCounter = (ToggleButton) dlg.findViewById(R.id.tbCounter); ToggleButton tbMinMax = (ToggleButton) dlg.findViewById(R.id.tbMinMax); cmdSeries = new CmdSeries(getService(), LogsFragment.this); if (tbAlerts.isChecked()) { cmdSeries.add(R.string.logs_msg_query_alerts, "210,1"); } if (tbFaults.isChecked()) { cmdSeries.add(R.string.logs_msg_query_faults, "210,2,0") .add(R.string.logs_msg_query_faults, "210,2,10") .add(R.string.logs_msg_query_faults, "210,2,20") .add(R.string.logs_msg_query_faults, "210,2,30"); } if (tbEvents.isChecked()) { cmdSeries.add(R.string.logs_msg_query_events, "210,3,0") .add(R.string.logs_msg_query_events, "210,3,10"); } if (tbCounter.isChecked()) { cmdSeries.add(R.string.logs_msg_query_counter, "210,4"); } if (tbMinMax.isChecked()) { cmdSeries.add(R.string.logs_msg_query_minmax, "210,5"); } if (cmdSeries.size() > 0) { cmdSeries.add(R.string.logs_msg_fetch_keytime, "32,RT-ENG-LogKeyTime"); if (tbAlerts.isChecked()) { cmdSeries.add(R.string.logs_msg_fetch_alerts, "32,RT-ENG-LogAlerts"); } if (tbFaults.isChecked()) { cmdSeries.add(R.string.logs_msg_fetch_faults, "32,RT-ENG-LogFaults"); } if (tbEvents.isChecked()) { cmdSeries.add(R.string.logs_msg_fetch_events, "32,RT-ENG-LogSystem"); } if (tbCounter.isChecked()) { cmdSeries.add(R.string.logs_msg_fetch_counter, "32,RT-ENG-LogCounts"); } if (tbMinMax.isChecked()) { cmdSeries.add(R.string.logs_msg_fetch_minmax, "32,RT-ENG-LogMinMax"); } cmdSeries.start(); } } }) .show(); break; case R.id.btn_reset_logs: content = layoutInflater.inflate(R.layout.dlg_logs_cmd, null); content.findViewById(R.id.tbAlerts).setVisibility(View.GONE); new AlertDialog.Builder(getActivity()) .setTitle(R.string.logs_btn_reset) .setView(content) .setNegativeButton(R.string.Cancel, null) .setPositiveButton(android.R.string.ok, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface pDlg, int pWhich) { Dialog dlg = (Dialog) pDlg; ToggleButton tbFaults = (ToggleButton) dlg.findViewById(R.id.tbFaults); ToggleButton tbEvents = (ToggleButton) dlg.findViewById(R.id.tbEvents); ToggleButton tbCounter = (ToggleButton) dlg.findViewById(R.id.tbCounter); ToggleButton tbMinMax = (ToggleButton) dlg.findViewById(R.id.tbMinMax); cmdSeries = new CmdSeries(getService(), LogsFragment.this); if (tbFaults.isChecked()) { cmdSeries.add(R.string.logs_msg_reset_faults, "209,2"); } if (tbEvents.isChecked()) { cmdSeries.add(R.string.logs_msg_reset_events, "209,3"); } if (tbCounter.isChecked()) { cmdSeries.add(R.string.logs_msg_reset_counter, "209,4"); } if (tbMinMax.isChecked()) { cmdSeries.add(R.string.logs_msg_reset_minmax, "209,5"); } if (cmdSeries.size() > 0) { cmdSeries.start(); } } }) .show(); break; } } @Override public void onStop() { if (cmdSeries != null) cmdSeries.cancel(); super.onStop(); } @Override public void onCmdSeriesProgress(String message, int pos, int posCnt, int step, int stepCnt) { stepProgressOverlay(message, pos, posCnt, step, stepCnt); } @Override public void onProgressCancel() { if (cmdSeries != null) cmdSeries.cancel(); hideProgressOverlay(); } @Override public void onCmdSeriesFinish(CmdSeries cmdSeries, int returnCode) { String errorMsg = ""; String errorDetail = ""; hideProgressOverlay(); switch (returnCode) { case -1: // abort return; case 0: // ok showProgressOverlay(getString(R.string.msg_processing_data)); logsData.processCmdResults(cmdSeries); logsData.saveFile(mCarData.sel_vehicleid); updateUi(); hideProgressOverlay(); return; case 1: // failed errorDetail = cmdSeries.getErrorDetail(); if (errorDetail.contains("B ")) errorDetail += getString(R.string.hint_sevcon_offline); errorMsg = getString(R.string.err_failed, errorDetail); break; case 2: // unsupported errorMsg = getString(R.string.err_unsupported_operation); break; case 3: // unimplemented errorMsg = getString(R.string.err_unimplemented_operation); break; } // getting here means error: new AlertDialog.Builder(getActivity()) .setTitle(R.string.Error) .setMessage(cmdSeries.getMessage() + " => " + errorMsg) .setPositiveButton(android.R.string.ok, null) .show(); } public void updateUi() { // Data ready/changed: show listAlertsInfo.setText(getString(R.string.logs_fmt_listinfo, logsData.alertsTime.fmtTimeStamp(), logsData.alertsTime.fmtKeyTime())); listAlertsAdapter.setItems(logsData.alerts); listFaultEventsInfo.setText(getString(R.string.logs_fmt_listinfo, logsData.faultEventsTime.fmtTimeStamp(), logsData.faultEventsTime.fmtKeyTime())); listFaultEventsAdapter.setItems(logsData.faultEvents); listSystemEventsInfo.setText(getString(R.string.logs_fmt_listinfo, logsData.systemEventsTime.fmtTimeStamp(), logsData.systemEventsTime.fmtKeyTime())); listSystemEventsAdapter.setItems(logsData.systemEvents); listCountersInfo.setText(getString(R.string.logs_fmt_listinfo, logsData.countersTime.fmtTimeStamp(), logsData.countersTime.fmtKeyTime())); listCountersAdapter.setItems(logsData.counters); listMinMaxesInfo.setText(getString(R.string.logs_fmt_listinfo, logsData.minMaxesTime.fmtTimeStamp(), logsData.minMaxesTime.fmtKeyTime())); listMinMaxesAdapter.setItems(logsData.minMaxes); } public abstract class TableAdapter<T> extends BaseAdapter { protected ArrayList<T> mItems; private class ColumnDef { String title; float weight; int gravity; private ColumnDef(String title, float weight, int gravity) { this.title = title; this.weight = weight; this.gravity = gravity; } } public static final boolean HEAD = true; public static final boolean BODY = false; private ArrayList<ColumnDef> columns; public TableAdapter(Context context, ArrayList<T> items) { super(); mItems = items; columns = new ArrayList<ColumnDef>(10); makeColumns(); } public void addColumn(String title, float weight, int gravity) { columns.add(new ColumnDef(title, weight, gravity)); } // insert addColumn() calls in implementation of... public abstract void makeColumns(); // insert data mapping in implementation of... public abstract String getValue(T item, int row, int column); public void setItems(ArrayList<T> items) { mItems = items; notifyDataSetChanged(); } @Override public int getCount() { return mItems.size(); } @Override public Object getItem(int i) { return mItems.get(i); } @Override public long getItemId(int i) { return i; } @Override public View getView(int position, View convertView, ViewGroup parent) { LinearLayout row = (LinearLayout) convertView; if (row == null) { row = (LinearLayout) makeRow(parent.getContext(), BODY); } T item = (T) getItem(position); if (item != null) { TextView textView; for (int col = 0; col < columns.size(); col++) { textView = (TextView) row.getChildAt(col); if (textView != null) { textView.setText(getValue(item, position, col)); } } } return row; } public View makeRow(Context context, boolean isHeader) { LinearLayout row = new LinearLayout(context); row.setOrientation(LinearLayout.HORIZONTAL); row.setPadding(2, 2, 2, 2); if (isHeader) { row.setBackgroundResource( R.drawable.abs__list_selector_background_transition_holo_light); } LinearLayout.LayoutParams layout; TextView textView; ColumnDef columnDef; for (int col = 0; col < columns.size(); col++) { columnDef = columns.get(col); layout = new LinearLayout.LayoutParams(0, LinearLayout.LayoutParams.WRAP_CONTENT); layout.gravity = columnDef.gravity; layout.weight = columnDef.weight; textView = new TextView(context); textView.setLayoutParams(layout); textView.setPadding(2, 2, 2, 2); // TODO: gravity does not work. setTextAlignment() necessary but needs API >= 17 if (isHeader) { textView.setTypeface(Typeface.DEFAULT_BOLD); textView.setText(columnDef.title); } row.addView(textView); } return row; } } }