/* * This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0. If a copy of * the MPL was not distributed with this file, You can obtain one at http://mozilla.org/MPL/2.0/. * * Copyright (c) 2014 Digi International Inc., All Rights Reserved. */ package com.digi.android.wva; import android.annotation.SuppressLint; import android.content.Intent; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.preference.PreferenceManager; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; import android.support.v4.app.FragmentPagerAdapter; import android.support.v4.app.FragmentTransaction; import android.support.v4.app.NavUtils; import android.support.v4.app.TaskStackBuilder; import android.support.v4.view.ViewPager; import android.text.TextUtils; import android.util.Log; import android.widget.Toast; import com.actionbarsherlock.app.SherlockFragmentActivity; import com.actionbarsherlock.view.Menu; import com.actionbarsherlock.view.MenuItem; import com.actionbarsherlock.view.Window; import com.digi.android.wva.adapters.EndpointsAdapter; import com.digi.android.wva.adapters.LogAdapter; import com.digi.android.wva.adapters.VariableAdapter; import com.digi.android.wva.fragments.ConnectionErrorDialog; import com.digi.android.wva.fragments.ConnectionErrorDialog.ErrorDialogListener; import com.digi.android.wva.fragments.EndpointsFragment; import com.digi.android.wva.fragments.LogFragment; import com.digi.android.wva.fragments.PreConnectionDialog; import com.digi.android.wva.fragments.PreConnectionDialog.PreConnectionDialogListener; import com.digi.android.wva.fragments.VariableListFragment; import com.digi.android.wva.util.MessageCourier; import com.digi.wva.async.WvaCallback; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; /** * Activity to be launched when the user selects a device to connect to. * * <p>On tablets, the three content fragments * ({@link VariableListFragment}, {@link LogFragment}, {@link EndpointsFragment}) * are displayed all at once (see layouts <b>res/layout-sw600dp/dashboard</b> * and <b>res/layout-sw600dp-port/dashboard</b>).</p> * * <p>On phones (and mid-sized tablets?) the fragments will be displayed in a * {@link TabsAdapter} which allows the fragments to be paged through.</p> * * @author mwadsten * */ public class DashboardActivity extends SherlockFragmentActivity implements ErrorDialogListener, PreConnectionDialogListener { public static final String INTENT_IP = "ip_address"; private static final String TAG = "DashboardActivity"; private ViewPager mViewPager; private String mActionBarTitle; private String mActionBarSubtitle; private static final int MESSAGE_LOOP_INTERVAL = 2000; private class MessageHandler extends Handler { @Override public void handleMessage(Message msg) { DashboardActivity.this.processMessages(); } public void sleep() { this.removeMessages(0); sendMessageDelayed(obtainMessage(0), MESSAGE_LOOP_INTERVAL); } } private final MessageHandler mHandler = new MessageHandler(); private boolean isPaused = false; private boolean showIndeterminateProgress = true; protected void processMessages() { MessageCourier.DashboardMessage[] messages = MessageCourier.getDashboardMessages(); for (MessageCourier.DashboardMessage message : messages) { if (message.isError()) { setIsConnecting(false); Log.d(TAG, "processMessages -- got error"); showErrorDialog(message.getContents()); return; // stop processing and handler loop } else if (message.isReconnecting() && !isPaused) { // Reconnecting to device, and we're not paused. setIsConnecting(true); Log.d(TAG, "processMessages -- got reconnecting"); Toast.makeText(this, "Reconnecting...", Toast.LENGTH_SHORT).show(); } else { Log.i(TAG, "Successfully connected to device."); setIsConnecting(false); Toast.makeText(DashboardActivity.this, getString(R.string.connected_toast_contents), Toast.LENGTH_SHORT).show(); mActionBarTitle = getString(R.string.connected_dashboard_title); mActionBarSubtitle = message.getContents(); setActionBarText(); } } if (isPaused) { return; } mHandler.sleep(); } @Override protected void onPause() { // Ensure messages stop being processed. mHandler.removeMessages(0); isPaused = true; // Log.i(TAG, "onPause"); super.onPause(); } @Override protected void onResume() { // Log.i(TAG, "onResume"); super.onResume(); WvaApplication app = (WvaApplication)getApplication(); app.dismissAlarmNotification(); isPaused = false; processMessages(); } /** * <b>finish()</b> the activity, while also sending a * "disconnect" intent to the {@link VehicleInfoService} and * dismissing any alarm notifications */ @Override public void finish() { // Tell VehicleInfoService to disconnect startService(VehicleInfoService.buildDisconnectIntent( getApplicationContext())); ((WvaApplication)getApplication()).dismissAlarmNotification(); super.finish(); } @SuppressLint("CommitTransaction") protected void showErrorDialog(String error) { ConnectionErrorDialog dialog = ConnectionErrorDialog.newInstance( getString(R.string.dashboard_error_dialog_title), error); FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); ft.addToBackStack(null); dialog.show(ft, "error_dialog"); } /** * Implementation of the ErrorDialogListener interface defined in * ConnectionErrorDialog class. When the user acknowledges the * error message, the dialog will automatically go away, and we * want to finish the current dashboard activity and return to * the device discovery activity. */ @Override public void onOkay() { navigateBackToDevices(); } /** * Implementation of the PreConnectionDialogListener interface defined * in PreConnectionDialog class. When the user presses the "Okay" button * in that dialog, the dialog will automatically go away, and we need to * use the input from that dialog to configure the connection with the * device (username, password, whether we need to use HTTPS, etc.). */ @Override public void onOkay(String ipAddress, String username, String password, boolean useHttps) { // Use VehicleInfoService to connect to the device. startService(VehicleInfoService.buildConnectIntent( getApplicationContext(), ipAddress, username, password, useHttps)); } /** * Implementation of {@link PreConnectionDialogListener#onCancelConnection()}. * When the user presses the "Cancel" button in that dialog, the dialog will * automatically go away, and we want to respond by {@link #finish}ing the * DashboardActivity. (The user apparently wishes to cancel the attempted connection * with this device.) */ @Override public void onCancelConnection() { Toast.makeText(this, "Cancelled connection to " + getConnectionIp(), Toast.LENGTH_SHORT).show(); navigateBackToDevices(); } protected void setIsConnecting(boolean is) { setSupportProgressBarIndeterminateVisibility(is); showIndeterminateProgress = is; } protected String getConnectionIp() { String ipAddr = getIntent().getStringExtra(INTENT_IP); if (ipAddr == null) { // Log.e(TAG, "Got intent with null ip address!"); ipAddr = PreferenceManager.getDefaultSharedPreferences(this) .getString("pref_device_manual_ip", getString(R.string.default_ip)); } return ipAddr; } @Override protected void onCreate(Bundle savedInstanceState) { // Log.i(TAG, "onCreate"); super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setContentView(R.layout.dashboard); getSupportActionBar().setDisplayHomeAsUpEnabled(true); // We want to wipe out variable and log data if this is a // fresh launch into this activity. if (savedInstanceState == null) { // Log.i(TAG, "Clearing data in onCreate"); clearData(); mActionBarTitle = getString(R.string.pre_connected_dashboard_title); // Send connect command to service... String ipAddr = getConnectionIp(); FragmentManager fm = getSupportFragmentManager(); FragmentTransaction ft = fm.beginTransaction(); PreConnectionDialog dlg = PreConnectionDialog.newInstance(ipAddr); ft.addToBackStack(null); dlg.show(ft, "pre_connect"); } else { // there is a saved instance state mActionBarTitle = savedInstanceState.getString("title"); if (TextUtils.isEmpty(mActionBarTitle)) mActionBarTitle = getString(R.string.pre_connected_dashboard_title); mActionBarSubtitle = savedInstanceState.getString("subtitle"); // If subtitle text is null, no subtitle will be added showIndeterminateProgress = savedInstanceState.getBoolean("indeterminate"); } setActionBarText(); // Set up the view pager if we are running on a device which will // display the view pager. mViewPager = (ViewPager)findViewById(R.id.pager); if (mViewPager != null) { // Running on a phone. TabsAdapter mTabsAdapter = new TabsAdapter(getSupportFragmentManager()); mViewPager.setAdapter(mTabsAdapter); if (savedInstanceState != null) mViewPager.setCurrentItem(savedInstanceState.getInt("page", 2)); else mViewPager.setCurrentItem(2); } setIsConnecting(showIndeterminateProgress); } @Override protected void onSaveInstanceState(Bundle outState) { // Log.i(TAG, "onSaveInstanceState"); super.onSaveInstanceState(outState); // Save view pager current page. if (mViewPager != null) outState.putInt("page", mViewPager.getCurrentItem()); outState.putString("title", mActionBarTitle); outState.putString("subtitle", mActionBarSubtitle); outState.putBoolean("indeterminate", showIndeterminateProgress); } @Override public boolean onCreateOptionsMenu(Menu menu) { getSupportMenuInflater().inflate(R.menu.dashboard, menu); return true; } protected static void clearData() { VariableAdapter.getInstance().clear(); LogAdapter.getInstance().clear(); EndpointsAdapter.getInstance().clear(); MessageCourier.clear(); } /** * Title and subtitle are stored in instance variables so that * screen rotation, etc. can end with their contents restored. */ protected void setActionBarText() { getSupportActionBar().setTitle(mActionBarTitle); getSupportActionBar().setSubtitle(mActionBarSubtitle); } /** * Uses NavUtils task stack builder to help make it so that we can leave * the dashboard activity and go back to the device list. */ protected void navigateBackToDevices() { // Log.d(TAG, "navigateBackToDevices"); Log.d(TAG, "Exiting dashboard, returning to device discovery."); ((WvaApplication)getApplication()).clearDevice(); // developer.android.com/training/implementing-navigation/ancestral.html Intent upIntent = new Intent(this, DeviceListActivity.class); if (NavUtils.shouldUpRecreateTask(this, upIntent)) { // Create new task with synthesized back stack. TaskStackBuilder.create(this).addNextIntent(upIntent).startActivities(); finish(); } else { // Navigate up to the parent activity (DevicesActivity) // -- this is exactly the support library's implementation // of NavUtils.navigateUpTo(this, upIntent)... finish() wasn't // being called for some reason (at least on 4.2.2) and this // works better. upIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(upIntent); finish(); } } /** * Make it so that when the user hits the back button, we explicitly * return to the device list activity. */ @Override public void onBackPressed() { navigateBackToDevices(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: navigateBackToDevices(); return true; case R.id.sync_time: Log.d(TAG, "Executing time sync."); WvaApplication app = (WvaApplication)getApplication(); if (app.getDevice() != null) app.getDevice().setTime(DateTime.now(DateTimeZone.UTC), new WvaCallback<DateTime>() { @Override public void onResponse(Throwable error, DateTime response) { Log.d(TAG, "Time sync error: " + error); String message; if (error == null) message = "Time sync successful."; else message = "Time sync: " + error; Toast.makeText(DashboardActivity.this, message, Toast.LENGTH_SHORT).show(); } }); else Log.d(TAG, "Can't execute time sync: no Device in WvaApplication"); return true; // startActivityForResult should make it so that backing out (i.e. finish()ing) // from the launched activities returns us here to DashboardActivity case R.id.action_settings: startActivityForResult(new Intent(this, SettingsActivity.class), 0); return true; case R.id.launch_chart: startActivityForResult(new Intent(this, ChartActivity.class), 0); return true; case R.id.fault_codes: startActivity(new Intent(this, FaultCodeActivity.class)); return true; } return false; } /** * {@link FragmentPagerAdapter} implementation which is used when creating * the dashboard activity on small screens. Allows the user to swipe between * the variable data, log, and alarms/subscriptions fragments. * @author mwadsten * */ public class TabsAdapter extends FragmentPagerAdapter { public TabsAdapter(FragmentManager fm) { super(fm); } @Override public Fragment getItem(int position) { switch (position) { case 0: return VariableListFragment.newInstance(); case 1: return LogFragment.newInstance(); default: return EndpointsFragment.newInstance(); } } /** * Get the title to put above a given page * @param pos page position * @return page title for the given page. */ @Override public CharSequence getPageTitle(int pos) { switch (pos) { case 0: return getString(R.string.variables_header); case 1: return getString(R.string.log_header); default: // 2 return getString(R.string.subscriptions_header); } } /** * Get the number of pages in the adapter. * @return 3 ({@link VariableListFragment}, {@link LogFragment}, * {@link EndpointsFragment}) */ @Override public int getCount() { return 3; } } }