package uk.org.smithfamily.mslogger.activity;
import java.util.ArrayList;
import java.util.List;
import uk.org.smithfamily.mslogger.*;
import uk.org.smithfamily.mslogger.comms.Connection;
import uk.org.smithfamily.mslogger.comms.ConnectionFactory;
import uk.org.smithfamily.mslogger.dashboards.*;
import uk.org.smithfamily.mslogger.dashboards.pageindicators.CirclePageIndicator;
import uk.org.smithfamily.mslogger.ecuDef.Megasquirt;
import uk.org.smithfamily.mslogger.log.*;
import android.app.*;
import android.bluetooth.BluetoothAdapter;
import android.content.*;
import android.content.pm.*;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.util.Log;
import android.view.*;
import android.view.View.OnClickListener;
import android.widget.*;
/**
* Main activity class where the main window (gauges) are and where the bottom menu is handled
*/
public class MSLoggerActivity extends Activity implements SharedPreferences.OnSharedPreferenceChangeListener, OnClickListener
{
private final BroadcastReceiver updateReceiver = new Reciever();
private TextView messages;
private TextView rps;
private static Boolean ready = null;
private boolean dashboardEditEnabled;
private static final int SHOW_PREFS = 124230;
private boolean registered;
private DashboardViewPager pager;
/**
* Called when the activity is first created.
*/
@Override
public void onCreate(final Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
final Connection conn = ConnectionFactory.INSTANCE.getConnection();
// Bluetooth is not supported on this Android device
if (!conn.connectionPossible())
{
finishDialogNoBluetooth();
return;
}
checkSDCard();
setGaugesOrientation();
setContentView(R.layout.main);
pager = (DashboardViewPager) findViewById(R.id.dashboardpager);
final DashboardPagerAdapter dashAdapter = new DashboardPagerAdapter(this, pager);
pager.setAdapter(dashAdapter);
// Bind the title indicator to the adapter
final CirclePageIndicator circleIndicator = (CirclePageIndicator) findViewById(R.id.separator);
circleIndicator.setViewPager(pager);
circleIndicator.setOnPageChangeListener(dashAdapter);
messages = (TextView) findViewById(R.id.messages);
rps = (TextView) findViewById(R.id.RPS);
/*
* Get status message from saved instance, for example when switching from landscape to portrait mode
*/
if (savedInstanceState != null)
{
if (!savedInstanceState.getString("status_message").equals(""))
{
messages.setText(savedInstanceState.getString("status_message"));
}
if (!savedInstanceState.getString("rps_message").equals(""))
{
rps.setText(savedInstanceState.getString("rps_message"));
}
if (savedInstanceState.getBoolean("gauge_edit"))
{
dashboardEditEnabled = true;
}
}
final SharedPreferences prefsManager = PreferenceManager.getDefaultSharedPreferences(MSLoggerActivity.this);
prefsManager.registerOnSharedPreferenceChangeListener(MSLoggerActivity.this);
ApplicationSettings.INSTANCE.setDefaultAdapter(BluetoothAdapter.getDefaultAdapter());
GPSLocationManager.INSTANCE.start();
ApplicationSettings.INSTANCE.setAutoConnectOverride(null);
registerMessages();
}
@Override
protected void onResume()
{
super.onResume();
setGaugesOrientation();
}
/**
* Force a screen orientation if specified by the user, otherwise look at the sensor and rotate screen as needed
*/
private void setGaugesOrientation()
{
final String activityOrientation = ApplicationSettings.INSTANCE.getGaugesOrientation();
// Force landscape
if (activityOrientation.equals("FORCE_LANDSCAPE"))
{
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
// Force portrait
else if (activityOrientation.equals("FORCE_PORTRAIT"))
{
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);
}
else
{
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_SENSOR);
}
}
/**
* Save the bottom text views content so they can keep their state while device is rotated
*/
@Override
protected void onSaveInstanceState(final Bundle outState)
{
super.onSaveInstanceState(outState);
outState.putString("status_message", messages.getText().toString());
outState.putString("rps_message", rps.getText().toString());
outState.putBoolean("gauge_edit", dashboardEditEnabled);
}
/**
* Check if the SD card is present
*/
private void checkSDCard()
{
final boolean cardOK = android.os.Environment.getExternalStorageState().equals(android.os.Environment.MEDIA_MOUNTED);
ApplicationSettings.INSTANCE.setWritable(cardOK);
if (!cardOK)
{
showDialog(2);
}
}
/**
* Clean up messages and GPS when the app is stopped
*/
@Override
protected void onDestroy()
{
deRegisterMessages();
GPSLocationManager.INSTANCE.stop();
super.onDestroy();
}
/**
* Save the indicators when the app is stopped
*/
@Override
public void onStop()
{
super.onStop();
}
/**
* Register the receiver with the message to receive from the Megasquirt connection
*/
private void registerMessages()
{
final IntentFilter connectedFilter = new IntentFilter(Megasquirt.CONNECTED);
registerReceiver(updateReceiver, connectedFilter);
final IntentFilter disconnectedFilter = new IntentFilter(Megasquirt.DISCONNECTED);
registerReceiver(updateReceiver, disconnectedFilter);
final IntentFilter dataFilter = new IntentFilter(Megasquirt.NEW_DATA);
registerReceiver(updateReceiver, dataFilter);
final IntentFilter msgFilter = new IntentFilter(ApplicationSettings.GENERAL_MESSAGE);
registerReceiver(updateReceiver, msgFilter);
final IntentFilter rpsFilter = new IntentFilter(ApplicationSettings.RPS_MESSAGE);
registerReceiver(updateReceiver, rpsFilter);
final IntentFilter toastFilter = new IntentFilter(ApplicationSettings.TOAST);
registerReceiver(updateReceiver, toastFilter);
final IntentFilter unknownEcuFilter = new IntentFilter(Megasquirt.UNKNOWN_ECU);
registerReceiver(updateReceiver, unknownEcuFilter);
final IntentFilter unknownEcuBTFilter = new IntentFilter(Megasquirt.UNKNOWN_ECU_BT);
registerReceiver(updateReceiver, unknownEcuBTFilter);
final IntentFilter probeEcuFilter = new IntentFilter(Megasquirt.PROBE_ECU);
registerReceiver(updateReceiver, probeEcuFilter);
registered = true;
}
/**
* Unregister receiver
*/
private void deRegisterMessages()
{
if (registered)
{
unregisterReceiver(updateReceiver);
}
}
/**
*
* @param menu
*/
@Override
public boolean onCreateOptionsMenu(final Menu menu)
{
final MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.menu, menu);
return true;
}
/**
* Triggered just before the bottom menu are displayed, used to update the state of some menu items
*
* @param menu
*/
@Override
public boolean onPrepareOptionsMenu(final Menu menu)
{
final MenuItem editItem = menu.findItem(R.id.dashboardEditing);
final Megasquirt ecuDefinition = ApplicationSettings.INSTANCE.getEcuDefinition();
editItem.setEnabled(ecuDefinition != null);
if (dashboardEditEnabled)
{
editItem.setIcon(R.drawable.ic_menu_disable_gauge_editing);
editItem.setTitle(R.string.disable_dashboard_edit);
}
else
{
editItem.setIcon(R.drawable.ic_menu_enable_gauge_editing);
editItem.setTitle(R.string.enable_dashboard_edit);
}
final MenuItem connectionItem = menu.findItem(R.id.forceConnection);
if ((ecuDefinition != null) && ecuDefinition.isConnected())
{
connectionItem.setIcon(R.drawable.ic_menu_disconnect);
connectionItem.setTitle(R.string.disconnect);
}
else
{
connectionItem.setIcon(R.drawable.ic_menu_connect);
connectionItem.setTitle(R.string.connect);
}
final MenuItem loggingItem = menu.findItem(R.id.forceLogging);
loggingItem.setEnabled((ecuDefinition != null) && ecuDefinition.isConnected());
if ((ecuDefinition != null) && ecuDefinition.isLogging())
{
loggingItem.setIcon(R.drawable.ic_menu_stop_logging);
loggingItem.setTitle(R.string.stop_logging);
}
else
{
loggingItem.setIcon(R.drawable.ic_menu_start_logging);
loggingItem.setTitle(R.string.start_logging);
}
final MenuItem tuningItem = menu.findItem(R.id.tuning);
tuningItem.setEnabled((ecuDefinition != null) && ecuDefinition.isConnected());
return super.onPrepareOptionsMenu(menu);
}
/**
* Triggered when a bottom menu item is clicked
*
* @param item The clicked menu item
*/
@Override
public boolean onOptionsItemSelected(final MenuItem item)
{
final int itemId = item.getItemId();
switch (itemId)
{
case R.id.forceConnection:
toggleConnection();
return true;
case R.id.dashboardEditing:
toggleEditing();
return true;
case R.id.forceLogging:
toggleLogging();
return true;
case R.id.manageDatalogs:
openManageDatalogs();
return true;
case R.id.tuning:
openTuning();
return true;
case R.id.preferences:
openPreferences();
return true;
case R.id.resetIndicators:
resetIndicators();
return true;
case R.id.calibrate:
openCalibrateTPS();
return true;
case R.id.about:
showAbout();
return true;
case R.id.quit:
quit();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
/**
* Start the background task to reset the indicators
*/
public void resetIndicators()
{
}
/**
* Toggle the gauge editing
*/
private void toggleEditing()
{
// Dashboard editing is enabled
if (dashboardEditEnabled)
{
DashboardIO.INSTANCE.saveDash();
}
// Dashboard editing is not enabled
else
{
Toast.makeText(getApplicationContext(), R.string.edit_dashboard_instructions, Toast.LENGTH_LONG).show();
}
dashboardEditEnabled = !dashboardEditEnabled;
pager.setEditMode(dashboardEditEnabled);
}
/**
* Toggle data logging of the Megasquirt
*/
private void toggleLogging()
{
final Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
if ((ecu != null) && ecu.isConnected())
{
if (ecu.isLogging())
{
ecu.stopLogging();
sendLogs();
}
else
{
ecu.startLogging();
}
}
}
/**
* If the user opted in to get logs sent to his email, we prompt him
*/
private void sendLogs()
{
if (ApplicationSettings.INSTANCE.emailEnabled())
{
DatalogManager.INSTANCE.close();
FRDLogManager.INSTANCE.close();
final List<String> paths = new ArrayList<String>();
paths.add(DatalogManager.INSTANCE.getAbsolutePath());
paths.add(FRDLogManager.INSTANCE.getAbsolutePath());
paths.add(DebugLogManager.INSTANCE.getAbsolutePath());
final String emailText = getString(R.string.email_body);
final String subject = String.format(getString(R.string.email_subject), System.currentTimeMillis());
EmailManager.email(this, ApplicationSettings.INSTANCE.getEmailDestination(), null, subject, emailText, paths);
}
}
/**
* Quit the application cleanly by stopping the connection to the Megasquirt if it exists
*/
private void quit()
{
ApplicationSettings.INSTANCE.setAutoConnectOverride(false);
ExtGPSManager.INSTANCE.stop();
final Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
if ((ecu != null) && ecu.isConnected())
{
ecu.stop();
}
sendLogs();
if ((ecu != null) && ecu.isConnected())
{
ecu.reset();
}
this.finish();
}
/**
* Toggle the connection to the Megasquirt
*/
private void toggleConnection()
{
final Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
if ((ecu != null) && ecu.isConnected())
{
ecu.stop();
}
else
{
ecu.start();
}
}
/**
* Open an about dialog
*/
private void showAbout()
{
final Dialog dialog = new Dialog(this);
dialog.setContentView(R.layout.about);
final TextView text = (TextView) dialog.findViewById(R.id.text);
String title = "";
try
{
final PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_META_DATA);
final ApplicationInfo ai = pInfo.applicationInfo;
final String applicationName = (ai != null) ? getPackageManager().getApplicationLabel(ai).toString() : "(unknown)";
title = applicationName + " " + pInfo.versionName;
}
catch (final NameNotFoundException e)
{
DebugLogManager.INSTANCE.logException(e);
}
dialog.setTitle(title);
text.setText(R.string.about_text);
final ImageView image = (ImageView) dialog.findViewById(R.id.image);
image.setImageResource(R.drawable.icon);
dialog.show();
}
/**
* Open the calibrate TPS activity
*/
private void openCalibrateTPS()
{
final Intent launchCalibrate = new Intent(this, CalibrateActivity.class);
startActivity(launchCalibrate);
}
/**
* Open the manage datalogs activity
*/
private void openManageDatalogs()
{
final Intent lauchManageDatalogs = new Intent(this, ManageDatalogsActivity.class);
startActivity(lauchManageDatalogs);
}
/**
* Open the tuning activity
*/
private void openTuning()
{
final Intent launchTuning = new Intent(this, TuningActivity.class);
startActivity(launchTuning);
}
/**
* Open the preferences activity
*/
private void openPreferences()
{
final Intent launchPrefs = new Intent(this, PreferencesActivity.class);
startActivityForResult(launchPrefs, SHOW_PREFS);
}
/**
*
* @param requestCode
* @param resultCode
* @param data
*/
@Override
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data)
{
super.onActivityResult(requestCode, resultCode, data);
if (resultCode == RESULT_OK)
{
switch (requestCode)
{
case SHOW_PREFS:
final Boolean dirty = (Boolean) data.getExtras().get(PreferencesActivity.DIRTY);
if (dirty)
{
final Megasquirt ecuDefinition = ApplicationSettings.INSTANCE.getEcuDefinition();
if (ecuDefinition != null)
{
ecuDefinition.refreshFlags();
}
}
break;
case MSLoggerApplication.REQUEST_CONNECT_DEVICE:
// When DeviceListActivity returns with a device to connect
if (resultCode == Activity.RESULT_OK)
{
// Get the device MAC address
final String address = data.getExtras().getString(DeviceListActivity.EXTRA_DEVICE_ADDRESS);
ApplicationSettings.INSTANCE.setECUBluetoothMac(address);
}
break;
}
}
}
/**
* Called when a preference change
*
* @param prefs
* @param key
*/
@Override
public void onSharedPreferenceChanged(final SharedPreferences prefs, final String key)
{
if ((ready == null) || !ready)
{
return;
}
final Megasquirt ecuDefinition = ApplicationSettings.INSTANCE.getEcuDefinition();
if (ApplicationSettings.INSTANCE.btDeviceSelected() && (ecuDefinition != null))
{
ecuDefinition.refreshFlags();
}
// When the TEMP/MAP/EGO are changed in preferences, we refresh the ECU flags if we are online
if ((key.equals("temptype") || key.equals("maptype") || key.equals("egotype")) && (ecuDefinition != null))
{
ecuDefinition.refreshFlags();
}
}
/**
*
* @param v
*/
@Override
public void onClick(final View v)
{
}
/**
* Receiver that get events from other activities about Megasquirt status and activities
*/
private final class Reciever extends BroadcastReceiver
{
/**
* When an event is received
*
* @param context
* @param intent
*/
@Override
public void onReceive(final Context context, final Intent intent)
{
final String action = intent.getAction();
final boolean autoLoggingEnabled = ApplicationSettings.INSTANCE.getAutoLogging();
if (action.equals(Megasquirt.CONNECTED))
{
final Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
DebugLogManager.INSTANCE.log(action, Log.INFO);
if (autoLoggingEnabled && (ecu != null) && ecu.isConnected())
{
ecu.startLogging();
}
}
else if (action.equals(Megasquirt.DISCONNECTED))
{
DebugLogManager.INSTANCE.log(action, Log.INFO);
if (autoLoggingEnabled)
{
DatalogManager.INSTANCE.mark("Connection lost");
}
messages.setText(R.string.disconnected_from_ms);
rps.setText("");
final Megasquirt ecu = ApplicationSettings.INSTANCE.getEcuDefinition();
if ((ecu != null) && ecu.isConnected())
{
ecu.stop();
}
}
else if (action.equals(ApplicationSettings.GENERAL_MESSAGE))
{
final String msg = intent.getStringExtra(ApplicationSettings.MESSAGE);
messages.setText(msg);
DebugLogManager.INSTANCE.log("Message : " + msg, Log.INFO);
}
else if (action.equals(ApplicationSettings.RPS_MESSAGE))
{
final String RPS = intent.getStringExtra(ApplicationSettings.RPS);
rps.setText(RPS + " reads / second");
}
else if (action.equals(ApplicationSettings.TOAST))
{
final String msg = intent.getStringExtra(ApplicationSettings.TOAST_MESSAGE);
// The toast is called in a loop so it can be displayed longer (Toast.LENGTH_LONG = 3.5 seconds)
for (int j = 0; j < 2; j++)
{
Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
}
}
else if (action.equals(Megasquirt.UNKNOWN_ECU))
{
if (isFinishing())
{
return;
}
final AlertDialog.Builder builder = new AlertDialog.Builder(MSLoggerActivity.this);
builder.setMessage(R.string.unrecognised_ecu).setTitle(R.string.app_name).setPositiveButton(R.string.bt_ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(final DialogInterface dialog, final int id)
{
constructEmail(ApplicationSettings.INSTANCE.getEcuDefinition().getTrueSignature());
}
private void constructEmail(final String trueSignature)
{
EmailManager.email(MSLoggerActivity.this, "mslogger.android@gmail.com", null, "Unrecognised firmware signature", "An unknown firmware was detected with a signature of '" + trueSignature
+ "'.\n\nPlease consider this for the next release.", null);
}
}).setNegativeButton(R.string.bt_cancel, new DialogInterface.OnClickListener()
{
@Override
public void onClick(final DialogInterface dialog, final int which)
{
dialog.cancel();
}
});
final AlertDialog alert = builder.create();
if (!isFinishing())
{
alert.show();
}
}
else if (action.equals(Megasquirt.UNKNOWN_ECU_BT))
{
final Intent serverIntent = new Intent(MSLoggerActivity.this, DeviceListActivity.class);
startActivityForResult(serverIntent, MSLoggerApplication.REQUEST_CONNECT_DEVICE);
}
}
}
/**
* It was determinated that the android device don't support Bluetooth, so we tell the user
*/
public void finishDialogNoBluetooth()
{
final AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setMessage(R.string.no_bt).setIcon(android.R.drawable.ic_dialog_info).setTitle(R.string.app_name).setCancelable(false).setPositiveButton(R.string.bt_ok, new DialogInterface.OnClickListener()
{
@Override
public void onClick(final DialogInterface dialog, final int id)
{
finish();
}
});
final AlertDialog alert = builder.create();
alert.show();
}
}