package com.openvehicles.OVMS.ui; import android.app.AlertDialog; import android.app.Dialog; import android.content.DialogInterface; import android.content.res.Configuration; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.graphics.drawable.BitmapDrawable; import android.os.Bundle; import android.util.Log; import android.view.LayoutInflater; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.ImageView; import android.widget.SeekBar; import android.widget.Toast; import android.widget.SeekBar.OnSeekBarChangeListener; import android.widget.TextView; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuInflater; import com.actionbarsherlock.view.MenuItem; import com.openvehicles.OVMS.R; import com.openvehicles.OVMS.api.OnResultCommandListener; import com.openvehicles.OVMS.entities.CarData; import com.openvehicles.OVMS.ui.settings.ControlFragment; import com.openvehicles.OVMS.ui.utils.ProgressOverlay; import com.openvehicles.OVMS.ui.utils.Ui; import com.openvehicles.OVMS.ui.utils.Ui.OnChangeListener; import com.openvehicles.OVMS.ui.witdet.ReversedSeekBar; import com.openvehicles.OVMS.ui.witdet.ScaleLayout; import com.openvehicles.OVMS.ui.witdet.SlideNumericView; import com.openvehicles.OVMS.ui.witdet.SwitcherView; public class InfoFragment extends BaseFragment implements OnClickListener, OnResultCommandListener { private static final String TAG = "InfoFragment"; private CarData mCarData; @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { View rootView = inflater.inflate(R.layout.fragment_info, null); final ScaleLayout scaleLayout = (ScaleLayout) rootView .findViewById(R.id.scaleLayout); scaleLayout.setOnScale(new Runnable() { @Override public void run() { SeekBar sb = (SeekBar) scaleLayout .findViewById(R.id.tabInfoSliderChargerControl); ScaleLayout.LayoutParams lp = (ScaleLayout.LayoutParams) sb .getLayoutParams(); Bitmap srcBmp = BitmapFactory .decodeResource( scaleLayout.getContext().getResources(), R.drawable.charger_button); int tw = (int) (srcBmp.getWidth() * (lp.height / srcBmp .getHeight())); int th = lp.height; if (tw < 40) { tw = 61; } // Sane lower limit if (th < 10) { th = 22; } // Sane lower limit Bitmap dstBmp = Bitmap.createScaledBitmap(srcBmp, tw, lp.height, true); srcBmp.recycle(); BitmapDrawable drw = new BitmapDrawable(scaleLayout .getContext().getResources(), dstBmp); sb.setThumb(drw); // sb.setThumbOffset(dstBmp.getWidth() / 9); } }); setHasOptionsMenu(true); return rootView; } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { inflater.inflate(R.menu.battery_options, menu); } @Override public void onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); if (mCarData != null && mCarData.car_type != null) { menu.findItem(R.id.mi_battery_stats).setVisible(mCarData.car_type.equals("RT")); } } @Override public void onDestroyView() { cancelCommand(); super.onDestroyView(); } @Override public void update(CarData pCarData) { mCarData = pCarData; getSherlockActivity().invalidateOptionsMenu(); // update UI: updateLastUpdatedView(pCarData); updateCarInfoView(pCarData); updateChargeAlerts(); } @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); setHasOptionsMenu(true); findViewById(R.id.tabInfoTextSOC).setOnClickListener(this); findViewById(R.id.tabInfoTextChargeMode).setOnClickListener(this); findViewById(R.id.tabInfoImageBatteryChargingOverlay) .setOnClickListener(this); findViewById(R.id.tabInfoImageBatteryOverlay).setOnClickListener(this); ReversedSeekBar bar = (ReversedSeekBar) findViewById(R.id.tabInfoSliderChargerControl); bar.setOnSeekBarChangeListener(new OnSeekBarChangeListener() { private int mStartProgress; @Override public void onStopTrackingTouch(SeekBar seekBar) { int progress = seekBar.getProgress(); if (progress > 50) { seekBar.setProgress(100); progress = 100; } else { seekBar.setProgress(0); progress = 0; } if (mStartProgress == progress) return; if (progress == 0) startCharge(); else stopCharge(); } @Override public void onStartTrackingTouch(SeekBar seekBar) { mStartProgress = seekBar.getProgress(); } @Override public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { } }); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.mi_battery_stats: Bundle args = new Bundle(); BaseFragmentActivity.show(getActivity(), BatteryFragment.class, args, Configuration.ORIENTATION_UNDEFINED); return true; default: return false; } } @Override public void onClick(View v) { chargerSetting(); } @Override public void onResultCommand(String[] result) { if (result.length <= 1) return; int command = Integer.parseInt(result[0]); int resCode = Integer.parseInt(result[1]); String cmdMessage = getSentCommandMessage(result[0]); switch (resCode) { case 0: // ok Toast.makeText(getActivity(), cmdMessage + " => " + getString(R.string.msg_ok), Toast.LENGTH_SHORT).show(); break; case 1: // failed Toast.makeText(getActivity(), cmdMessage + " => " + getString(R.string.err_failed, result[2]), Toast.LENGTH_SHORT).show(); break; case 2: // unsupported Toast.makeText(getActivity(), cmdMessage + " => " + getString(R.string.err_unsupported_operation), Toast.LENGTH_SHORT).show(); break; case 3: // unimplemented Toast.makeText(getActivity(), cmdMessage + " => " + getString(R.string.err_unimplemented_operation), Toast.LENGTH_SHORT).show(); break; } } private void startCharge() { sendCommand(R.string.msg_starting_charge, "11", this); mCarData.car_charge_linevoltage_raw = 0; mCarData.car_charge_current_raw = 0; mCarData.car_charge_state_s_raw = "starting"; mCarData.car_charge_state_i_raw = 0x101; updateCarInfoView(mCarData); } private void stopCharge() { sendCommand(R.string.msg_stopping_charge, "12", this); mCarData.car_charge_linevoltage_raw = 0; mCarData.car_charge_current_raw = 0; mCarData.car_charge_state_s_raw = "stopping"; mCarData.car_charge_state_i_raw = 0x115; updateCarInfoView(mCarData); } private void chargerSetting() { if (mCarData.car_type.equals("RT")) chargerSettingRenaultTwizy(); else chargerSettingDefault(); } private void chargerSettingDefault() { View content = LayoutInflater.from(getActivity()).inflate( R.layout.dlg_charger, null); SwitcherView sw = (SwitcherView) content.findViewById(R.id.sv_state); sw.setOnChangeListener(new OnChangeListener<SwitcherView>() { @Override public void onAction(SwitcherView pData) { TextView txtInfo = (TextView) ((View) pData.getParent()) .findViewById(R.id.txt_info); switch (pData.getSelected()) { case 2: txtInfo.setText(R.string.msg_charger_range); break; case 3: txtInfo.setText(R.string.msg_charger_perform); break; default: txtInfo.setText(null); } } }); new AlertDialog.Builder(getActivity()) .setTitle(R.string.lb_charger_setting) .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; SwitcherView sw = (SwitcherView) dlg .findViewById(R.id.sv_state); SlideNumericView snv = (SlideNumericView) dlg .findViewById(R.id.snv_amps); int ncm = sw.getSelected(); if (ncm >= 2) ncm++; int ncl = snv.getValue(); if (ncm != mCarData.car_charge_mode_i_raw && ncl != mCarData.car_charge_currentlimit_raw) { sendCommand( R.string.msg_setting_charge_mc, String.format("16,%d,%d", ncm, ncl), InfoFragment.this); } else if (ncm != mCarData.car_charge_mode_i_raw) { sendCommand(R.string.msg_setting_charge_m, String.format("10,%d", ncm), InfoFragment.this); } else if (ncl != mCarData.car_charge_currentlimit_raw) { sendCommand(R.string.msg_setting_charge_c, String.format("15,%d", ncl), InfoFragment.this); } } }) .show(); } // Charger settings for Renault Twizy: // (charge alert setup) private void chargerSettingRenaultTwizy() { // create & open dialog: View dialogView = LayoutInflater.from(getActivity()).inflate( R.layout.dlg_charger_twizy, null); // add distance units to range label: TextView lbRange = (TextView) dialogView.findViewById(R.id.lb_sufficient_range); lbRange.setText(getString(R.string.lb_sufficient_range, mCarData.car_distance_units)); // set range: SlideNumericView snvRange = (SlideNumericView) dialogView.findViewById(R.id.snv_sufficient_range); if (snvRange != null) { snvRange.init(0, mCarData.car_max_idealrange_raw, 1); snvRange.setValue(mCarData.car_chargelimit_rangelimit_raw); } // set SOC: SlideNumericView snvSOC = (SlideNumericView) dialogView.findViewById(R.id.snv_sufficient_soc); if (snvSOC != null) { snvSOC.setValue(mCarData.car_chargelimit_soclimit); } new AlertDialog.Builder(getActivity()) .setTitle(R.string.lb_charger_setting_twizy) .setView(dialogView) .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; SlideNumericView snvRange = (SlideNumericView) dlg .findViewById(R.id.snv_sufficient_range); SlideNumericView snvSOC = (SlideNumericView) dlg .findViewById(R.id.snv_sufficient_soc); int suffRange = snvRange.getValue(); int suffSOC = snvSOC.getValue(); // SetChargeAlerts (204): sendCommand( R.string.msg_setting_charge_alerts, String.format("204,%d,%d", suffRange, suffSOC), InfoFragment.this); } }) .show(); } // load ChargeAlerts / ETR data into UI: public void updateChargeAlerts() { String infoEtr = ""; TextView textView; boolean etrVisible = false; int etrFull = mCarData.car_chargefull_minsremaining; int suffSOC = mCarData.car_chargelimit_soclimit; int etrSuffSOC = mCarData.car_chargelimit_minsremaining_soc; int suffRange = mCarData.car_chargelimit_rangelimit_raw; int etrSuffRange = mCarData.car_chargelimit_minsremaining_range; if (etrSuffRange > 0) { String infoEtrRange = getString(R.string.info_etr_suffrange, suffRange, mCarData.car_distance_units, String.format("%02d:%02d", etrSuffRange / 60, etrSuffRange % 60)); if (infoEtr.length() > 0) infoEtr += "\n"; infoEtr += infoEtrRange; } if (etrSuffSOC > 0) { String infoEtrSOC = getString(R.string.info_etr_suffsoc, suffSOC, String.format("%02d:%02d", etrSuffSOC / 60, etrSuffSOC % 60)); if (infoEtr.length() > 0) infoEtr += "\n"; infoEtr += infoEtrSOC; } textView = (TextView) findViewById(R.id.tabInfoTextChargeEtrSuff); if (textView != null) { textView.setText(infoEtr); if (!infoEtr.equals("")) { etrVisible = true; textView.setVisibility(View.VISIBLE); } else { textView.setVisibility(View.INVISIBLE); } } infoEtr = ""; if (etrFull > 0) { String infoEtrFull = getString(R.string.info_etr_full, String.format("%02d:%02d", etrFull / 60, etrFull % 60)); if (infoEtr.length() > 0) infoEtr += "\n"; infoEtr += infoEtrFull; } textView = (TextView) findViewById(R.id.tabInfoTextChargeEtrFull); if (textView != null) { textView.setText(infoEtr); if (!infoEtr.equals("")) { etrVisible = true; textView.setVisibility(View.VISIBLE); } else { textView.setVisibility(View.INVISIBLE); } } // display background if any ETR visible: ImageView bgImg = (ImageView) findViewById(R.id.tabInfoImageChargeEtr); if (bgImg != null) { bgImg.setVisibility(etrVisible ? View.VISIBLE : View.INVISIBLE); } } // This updates the part of the view with times shown. // It is called by a periodic timer so it gets updated every few seconds. public void updateLastUpdatedView(CarData pCarData) { // Quick exit if the car data is not there yet... if ((pCarData == null) || (pCarData.car_lastupdated == null)) return; // Let's update the Info tab view... // First the last updated section... TextView tv = (TextView) findViewById(R.id.txt_last_updated); long now = System.currentTimeMillis(); long seconds = (now - pCarData.car_lastupdated.getTime()) / 1000; long minutes = (seconds) / 60; long hours = minutes / 60; long days = minutes / (60 * 24); Log.d(TAG, "Last updated: " + seconds + " secs ago"); if (pCarData.car_lastupdated == null) { tv.setText(""); tv.setTextColor(0xFFFFFFFF); } else if (minutes == 0) { tv.setText(getText(R.string.live)); tv.setTextColor(0xFFFFFFFF); } else if (minutes == 1) { tv.setText(getText(R.string.min1)); tv.setTextColor(0xFFFFFFFF); } else if (days > 1) { tv.setText(String.format(getText(R.string.ndays).toString(), days)); tv.setTextColor(0xFFFF0000); } else if (hours > 1) { tv.setText(String .format(getText(R.string.nhours).toString(), hours)); tv.setTextColor(0xFFFF0000); } else if (minutes > 60) { tv.setText(String.format(getText(R.string.nmins).toString(), minutes)); tv.setTextColor(0xFFFF0000); } else { tv.setText(String.format(getText(R.string.nmins).toString(), minutes)); tv.setTextColor(0xFFFFFFFF); } // Then the parking timer... tv = (TextView) findViewById(R.id.txt_parked_time); if ((!pCarData.car_started) && (pCarData.car_parked_time != null)) { // Car is parked tv.setVisibility(View.VISIBLE); seconds = (now - pCarData.car_parked_time.getTime()) / 1000; minutes = (seconds) / 60; hours = minutes / 60; days = minutes / (60 * 24); if (minutes == 0) tv.setText(getText(R.string.justnow)); else if (minutes == 1) tv.setText("1 min"); else if (days > 1) tv.setText(String.format(getText(R.string.ndays).toString(), days)); else if (hours > 1) tv.setText(String.format(getText(R.string.nhours).toString(), hours)); else if (minutes > 60) tv.setText(String.format(getText(R.string.nmins).toString(), minutes)); else tv.setText(String.format(getText(R.string.nmins).toString(), minutes)); } else { tv.setVisibility(View.INVISIBLE); } // The signal strength indicator ImageView iv = (ImageView) findViewById(R.id.img_signal_rssi); iv.setImageResource(Ui.getDrawableIdentifier(getActivity(), "signal_strength_" + pCarData.car_gsm_bars)); } // This updates the main informational part of the view. // It is called when the server gets new data. public void updateCarInfoView(CarData pCarData) { TextView tv = (TextView) findViewById(R.id.txt_title); tv.setText(pCarData.sel_vehicle_label); tv = (TextView) findViewById(R.id.tabInfoTextSOC); tv.setText(pCarData.car_soc); TextView cmtv = (TextView) findViewById(R.id.tabInfoTextChargeMode); ImageView coiv = (ImageView) findViewById(R.id.tabInfoImageBatteryChargingOverlay); ReversedSeekBar bar = (ReversedSeekBar) findViewById(R.id.tabInfoSliderChargerControl); TextView tvl = (TextView) findViewById(R.id.tabInfoTextChargeStatusLeft); TextView tvr = (TextView) findViewById(R.id.tabInfoTextChargeStatusRight); TextView tvf = (TextView) findViewById(R.id.tabInfoTextChargeStatus); if ((!pCarData.car_chargeport_open) || (pCarData.car_charge_substate_i_raw == 0x07)) { // Charge port is closed or car is not plugged in findViewById(R.id.tabInfoImageCharger).setVisibility(View.INVISIBLE); bar.setVisibility(View.INVISIBLE); cmtv.setVisibility(View.INVISIBLE); coiv.setVisibility(View.INVISIBLE); tvl.setVisibility(View.INVISIBLE); tvr.setVisibility(View.INVISIBLE); tvf.setVisibility(View.INVISIBLE); } else { // Car is plugged in if (mCarData.car_type.equals("RT")) { // Renault Twizy: no charge control findViewById(R.id.tabInfoImageCharger).setVisibility(View.VISIBLE); bar.setVisibility(View.INVISIBLE); tvl.setVisibility(View.INVISIBLE); tvr.setVisibility(View.INVISIBLE); int chargeStateInfo = 0; switch (pCarData.car_charge_state_i_raw) { case 1: // Charging chargeStateInfo = R.string.state_charging; break; case 2: // Topping off chargeStateInfo = R.string.state_topping_off; break; case 4: // Done chargeStateInfo = R.string.state_done; break; case 21: // Stopped chargeStateInfo = R.string.state_stopped; break; } if (chargeStateInfo != 0) { tvf.setText(String.format( getText(chargeStateInfo).toString(), pCarData.car_charge_linevoltage, pCarData.car_charge_current)); tvf.setVisibility(View.VISIBLE); } coiv.setVisibility(View.VISIBLE); cmtv.setText(String.format("%s %s", pCarData.car_charge_mode, pCarData.car_charge_current).toUpperCase()); cmtv.setVisibility(View.VISIBLE); } else { // Standard car: findViewById(R.id.tabInfoImageCharger).setVisibility(View.VISIBLE); bar.setVisibility(View.VISIBLE); tvl.setVisibility(View.VISIBLE); tvr.setVisibility(View.VISIBLE); switch (pCarData.car_charge_state_i_raw) { case 0x04: // Done case 0x115: // Stopping case 0x15: // Stopped case 0x16: // Stopped case 0x17: // Stopped case 0x18: // Stopped case 0x19: // Stopped // Slider on the left, message is "Slide to charge" bar.setProgress(100); tvl.setText(null); tvr.setText(getText(R.string.slidetocharge)); coiv.setVisibility(View.VISIBLE); cmtv.setVisibility(View.INVISIBLE); break; case 0x0e: // Wait for schedule charge // Slider on the left, message is "Timed Charge" bar.setProgress(100); tvl.setText(null); tvr.setText(getText(R.string.timedcharge)); coiv.setVisibility(View.VISIBLE); cmtv.setVisibility(View.INVISIBLE); break; case 0x01: // Charging case 0x101: // Starting case 0x02: // Top-off case 0x0d: // Preparing to charge case 0x0f: // Heating // Slider on the right, message blank bar.setProgress(0); tvl.setText(String.format( getText(R.string.state_charging).toString(), pCarData.car_charge_linevoltage, pCarData.car_charge_current)); tvr.setText(""); cmtv.setText(String.format("%s %s", pCarData.car_charge_mode, pCarData.car_charge_currentlimit).toUpperCase()); coiv.setVisibility(View.VISIBLE); cmtv.setVisibility(View.VISIBLE); break; default: // Slider on the right, message blank bar.setProgress(100); tvl.setText(null); tvr.setText(null); cmtv.setVisibility(View.INVISIBLE); coiv.setVisibility(View.INVISIBLE); break; } } } tv = (TextView) findViewById(R.id.tabInfoTextIdealRange); tv.setText(pCarData.car_range_ideal); tv = (TextView) findViewById(R.id.tabInfoTextEstimatedRange); tv.setText(pCarData.car_range_estimated); int maxWeight = findViewById(R.id.tabInfoTextSOC).getLayoutParams().width; int realWeight = Math .round((maxWeight * pCarData.car_soc_raw / 100) * 1.1f); View v = findViewById(R.id.tabInfoImageBatteryOverlay); v.getLayoutParams().width = Math.min(maxWeight, realWeight); v.requestLayout(); ImageView iv = (ImageView) findViewById(R.id.tabInfoImageCar); iv.setImageResource(Ui.getDrawableIdentifier(getActivity(), "signal_strength_" + pCarData.car_gsm_bars)); iv = (ImageView) findViewById(R.id.tabInfoImageCar); iv.setImageResource(Ui.getDrawableIdentifier(getActivity(), pCarData.sel_vehicle_image)); } }