/** * Copyright (C) 2013 - 2015 the enviroCar community * <p> * This file is part of the enviroCar app. * <p> * The enviroCar app is free software: you can redistribute it and/or * modify it under the terms of the GNU General Public License as published * by the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * <p> * The enviroCar app is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General * Public License for more details. * <p> * You should have received a copy of the GNU General Public License along * with the enviroCar app. If not, see http://www.gnu.org/licenses/. */ package org.envirocar.app.view.dashboard; import android.bluetooth.BluetoothDevice; import android.content.Intent; import android.graphics.Color; import android.os.Bundle; import android.support.annotation.Nullable; import android.support.annotation.UiThread; import android.support.design.widget.Snackbar; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentTransaction; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import com.squareup.otto.Subscribe; import org.envirocar.app.R; import org.envirocar.app.handler.BluetoothHandler; import org.envirocar.app.handler.CarPreferenceHandler; import org.envirocar.app.handler.LocationHandler; import org.envirocar.app.handler.TrackRecordingHandler; import org.envirocar.app.services.OBDConnectionService; import org.envirocar.app.view.utils.DialogUtils; import org.envirocar.core.events.NewCarTypeSelectedEvent; import org.envirocar.core.events.bluetooth.BluetoothStateChangedEvent; import org.envirocar.core.events.gps.GpsStateChangedEvent; import org.envirocar.core.injection.BaseInjectorFragment; import org.envirocar.core.logging.Logger; import org.envirocar.obd.events.BluetoothServiceStateChangedEvent; import org.envirocar.obd.service.BluetoothServiceState; import javax.inject.Inject; import butterknife.ButterKnife; import butterknife.InjectView; import butterknife.OnClick; import rx.Scheduler; import rx.Subscriber; import rx.android.schedulers.AndroidSchedulers; /** * @author dewall */ public class DashboardMainFragment extends BaseInjectorFragment { private static final Logger LOG = Logger.getLogger(DashboardMainFragment.class); private OBDConnectionService mOBDConnectionService; private boolean mIsOBDConnectionBounded; @Inject protected BluetoothHandler mBluetoothHandler; @Inject protected CarPreferenceHandler mCarManager; @Inject protected TrackRecordingHandler mTrackRecordingHandler; @Inject protected LocationHandler mLocationHandler; @InjectView(R.id.fragment_startup_info_field) protected View mInfoField; @InjectView(R.id.fragment_startup_info_text) protected TextView mInfoText; @InjectView(R.id.fragment_startup_start_button) protected View mStartStopButton; @InjectView(R.id.fragment_startup_start_button_inner) protected TextView mStartStopButtonInner; private MaterialDialog mConnectingDialog; private Scheduler.Worker mMainThreadScheduler = AndroidSchedulers.mainThread().createWorker(); private BluetoothServiceState mServiceState = BluetoothServiceState.SERVICE_STOPPED; // All the sub-fragments to show in this fragment. private Fragment mCurrentlyVisible; private Fragment mDashboardHeaderFragment; private Fragment mDashboardSettingsFragment; private Fragment mDashboardMapFragment = new DashboardMapFragment(); private Fragment mDashboardTempomatFragment = new DashboardTempomatFragment(); private Fragment mDashboardTrackMapFragment = new DashboardTrackMapFragment(); @Nullable @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { LOG.info("onCreateView()"); // This setting is an essential requirement to catch the events of a sub-fragment's // options shown in the toolbar. setHasOptionsMenu(true); // First inflate the general dashboard view. View contentView = inflater.inflate(R.layout.fragment_startup, container, false); // Inject all dashboard-related views. ButterKnife.inject(this, contentView); // Get the settings fragment and the header fragment. mDashboardSettingsFragment = getChildFragmentManager() .findFragmentById(R.id.fragment_startup_settings_layout); mDashboardHeaderFragment = getChildFragmentManager() .findFragmentById(R.id.fragment_dashboard_header_fragment); // TODO fix this. The static remoteService state is just a workaround. onShowServiceStateUI(OBDConnectionService.CURRENT_SERVICE_STATE); // Initially hide the header fragment. // hideFragment(mDashboardHeaderFragment, -1, -1); return contentView; } @Override public void onResume() { updateStartStopButton(OBDConnectionService.CURRENT_SERVICE_STATE); updateInfoField(); super.onResume(); } @Override public void onDestroyView() { if (!getActivity().isFinishing() && mDashboardSettingsFragment != null) { try { getFragmentManager().beginTransaction() .remove(mDashboardSettingsFragment) .remove(mDashboardHeaderFragment) .commit(); } catch (IllegalStateException e) { LOG.warn(e.getMessage(), e); } } super.onDestroyView(); } @Override public void onDestroy() { LOG.info("onDestroy()"); if(!getActivity().isFinishing() && mDashboardSettingsFragment != null){ try{ getFragmentManager().beginTransaction() .remove(mDashboardMapFragment) .commit(); } catch (IllegalStateException e){ LOG.warn(e.getMessage(), e); } } super.onDestroy(); } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.menu_dashboard_tempomat_map: if (mDashboardTrackMapFragment.isVisible()) return false; replaceFragment(mDashboardTrackMapFragment, R.id.fragment_startup_container, R.anim.translate_slide_in_left_fragment, R.anim.translate_slide_out_right_fragment); return true; case R.id.menu_dashboard_tempomat_show_cruise: if (mDashboardTempomatFragment.isVisible()) return false; replaceFragment(mDashboardTempomatFragment, R.id.fragment_startup_container, R.anim.translate_slide_in_right_fragment, R.anim.translate_slide_out_left_fragment); return true; } return false; } @OnClick(R.id.fragment_startup_start_button) public void onStartStopButtonClicked() { switch (OBDConnectionService.CURRENT_SERVICE_STATE) { case SERVICE_STOPPED: onButtonStartClicked(); break; case SERVICE_STARTED: onButtonStopClicked(); break; default: break; } } private void onButtonStartClicked() { if (!mBluetoothHandler.isBluetoothEnabled()) { Snackbar.make(getView(), R.string.dashboard_bluetooth_disabled_snackbar, Snackbar.LENGTH_LONG); return; } final BluetoothDevice device = mBluetoothHandler.getSelectedBluetoothDevice(); mBluetoothHandler.startBluetoothDiscoveryForSingleDevice(device) .subscribe(new Subscriber<BluetoothDevice>() { private boolean found = false; private View contentView; private TextView textView; @Override public void onStart() { contentView = getActivity().getLayoutInflater().inflate( R.layout.fragment_dashboard_connecting_dialog, null, false); textView = (TextView) contentView.findViewById( R.id.fragment_dashboard_connecting_dialog_text); textView.setText(String.format( getString(R.string.dashboard_connecting_find_template), device.getName())); mConnectingDialog = DialogUtils.createDefaultDialogBuilder(getContext(), R.string.dashboard_connecting, R.drawable.ic_bluetooth_searching_white_24dp, contentView) .cancelable(false) .negativeText(R.string.cancel) .onNegative((materialDialog, dialogAction) -> { // On cancel, first stop the discovery of other // bluetooth devices. mBluetoothHandler.stopBluetoothDeviceDiscovery(); if (found) { // and if the remoteService is already started, then // stop it. getActivity().stopService(new Intent (getActivity(), OBDConnectionService .class)); } found = true; }) .show(); } @Override public void onCompleted() { if (!found) { mConnectingDialog.dismiss(); mConnectingDialog = DialogUtils.createDefaultDialogBuilder(getContext(), R.string.dashboard_dialog_obd_not_found, R.drawable.ic_bluetooth_searching_white_24dp, String.format(getString( R.string.dashboard_dialog_obd_not_found_content_template), device.getName())) .negativeText(R.string.ok) .show(); } } @Override public void onError(Throwable e) { mConnectingDialog.setActionButton(DialogAction.NEGATIVE, "Okay"); } @Override public void onNext(BluetoothDevice bluetoothDevice) { found = true; // Stop the Bluetooth discovery such that the connection can be // faster established. mBluetoothHandler.stopBluetoothDeviceDiscovery(); // Update the content of the connecting dialog. textView.setText(String.format(getString( R.string.dashboard_connecting_found_template), device.getName())); // Start the background remoteService. getActivity().startService( new Intent(getActivity(), OBDConnectionService.class)); } }); } private void onButtonStopClicked() { new MaterialDialog.Builder(getActivity()) .title(R.string.dashboard_dialog_stop_track) .content(R.string.dashboard_dialog_stop_track_content) .negativeText(R.string.cancel) .positiveText(R.string.ok) .callback(new MaterialDialog.ButtonCallback() { @Override public void onPositive(MaterialDialog dialog) { mTrackRecordingHandler.finishCurrentTrack(); } }) .show(); } /** * Receiver method for {@link BluetoothServiceStateChangedEvent}s posted on the event bus. * * @param event the corresponding event type. */ @Subscribe public void onReceiveBluetoothServiceStateChangedEvent( BluetoothServiceStateChangedEvent event) { LOG.info(String.format("onReceiveBluetoothServiceStateChangedEvent(): %s", event.toString())); mServiceState = event.mState; if (mServiceState == BluetoothServiceState.SERVICE_STARTED && mConnectingDialog != null) { mConnectingDialog.dismiss(); mConnectingDialog = null; } mMainThreadScheduler.schedule(() -> { onShowServiceStateUI(event.mState); // Update the start stop button. updateStartStopButton(event.mState); // Update the info field. updateInfoField(); }); } @Subscribe public void onReceiveBluetoothStateChangedEvent(BluetoothStateChangedEvent event) { LOG.info(String.format("onReceiveBluetoothStateChangedEvent(isEnabled=%s)", "" + event.isBluetoothEnabled)); mMainThreadScheduler.schedule(() -> { updateStartStopButton(OBDConnectionService.CURRENT_SERVICE_STATE); // Update the info field. updateInfoField(); }); } @Subscribe public void onReceiveNewCarTypeSelectedEvent(NewCarTypeSelectedEvent event) { mMainThreadScheduler.schedule(() -> { updateStartStopButton(OBDConnectionService.CURRENT_SERVICE_STATE); // Update the info field. updateInfoField(); }); } @Subscribe public void onReceiveGpsStatusChangedEvent(GpsStateChangedEvent event) { mMainThreadScheduler.schedule(() -> { updateStartStopButton(OBDConnectionService.CURRENT_SERVICE_STATE); // Update the info field. updateInfoField(); }); } private void onShowServiceStateUI(BluetoothServiceState state) { switch (state) { case SERVICE_STOPPED: if (getFragmentManager() == null) return; if (!mDashboardSettingsFragment.isVisible()) showFragment(mDashboardSettingsFragment, R.anim.translate_slide_in_left_fragment, R.anim.translate_slide_out_left_fragment); // Hide the header fragment hideFragment(mDashboardHeaderFragment, mCurrentlyVisible != null ? R.anim.translate_slide_in_top_fragment : -1, mCurrentlyVisible != null ? R.anim.translate_slide_out_top_fragment : -1); // Replace the container with the mapview. if (mCurrentlyVisible != mDashboardMapFragment) { // TODO HERE CHANGE TO TRACK MAP FRAGMENT replaceFragment(mDashboardMapFragment, R.id.fragment_startup_container, mCurrentlyVisible != null ? R.anim.translate_slide_in_left_fragment : -1, mCurrentlyVisible != null ? R.anim.translate_slide_out_right_fragment : -1); } mCurrentlyVisible = mDashboardMapFragment; break; case SERVICE_STARTED: // Hide the settings if visible // if (mDashboardSettingsFragment.isVisible()) { hideFragment(mDashboardSettingsFragment, R.anim.translate_slide_in_left_fragment, R.anim.translate_slide_out_left_fragment); // } // if (!mDashboardHeaderFragment.isVisible()) showFragment(mDashboardHeaderFragment, R.anim.translate_slide_in_top_fragment, R.anim.translate_slide_out_top_fragment); // Show the tempomat fragment if (!mDashboardTempomatFragment.isVisible()) replaceFragment(mDashboardTempomatFragment, R.id.fragment_startup_container, R.anim.translate_slide_in_right_fragment, R.anim.translate_slide_out_left_fragment); mCurrentlyVisible = mDashboardTempomatFragment; break; case SERVICE_STOPPING: break; } } /** * Updates the info field of the default startup fragment. */ @UiThread private void updateInfoField() { boolean showInfo = false; StringBuilder sb = new StringBuilder(); sb.append(getString(R.string.dashboard_info_base)); if (!mLocationHandler.isGPSEnabled()) { sb.append(getString(R.string.dashboard_info_activate_gps)); showInfo = true; } if (!mBluetoothHandler.isBluetoothEnabled()) { sb.append(getString(R.string.dashboard_info_activate_bluetooth)); showInfo = true; } else if (mBluetoothHandler.getSelectedBluetoothDevice() == null) { sb.append(getString(R.string.dashboard_info_select_obd_adapter)); showInfo = true; } if (mCarManager.getCar() == null) { sb.append(getString(R.string.dashboard_info_select_car_type)); showInfo = true; } if (showInfo) { mInfoText.setText(sb.toString()); mInfoField.setVisibility(View.VISIBLE); } else { mInfoField.setVisibility(View.INVISIBLE); } } private void updateStartStopButton(BluetoothServiceState state) { switch (state) { case SERVICE_STOPPED: if (hasSettingsSelected()) { updateStartStopButton(getResources().getColor(R.color.green_dark_cario), getString(R.string.dashboard_start_track), true); } else { updateStartStopButton(Color.GRAY, getString(R.string.dashboard_start_track), false); } break; case SERVICE_STARTED: // Update the StartStopButton updateStartStopButton(Color.RED, getString(R.string.dashboard_stop_track), true); // hide the info field when the track is started. mInfoField.setVisibility(View.INVISIBLE); break; case SERVICE_STARTING: updateStartStopButton(Color.GRAY, getString(R.string.dashboard_track_is_starting), false); break; case SERVICE_STOPPING: updateStartStopButton(Color.GRAY, getString(R.string.dashboard_track_is_stopping), false); break; default: break; } } private boolean hasSettingsSelected() { return mBluetoothHandler.isBluetoothEnabled() && mBluetoothHandler.getSelectedBluetoothDevice() != null && mLocationHandler.isGPSEnabled() && mCarManager.getCar() != null; } private void updateStartStopButton(int color, String text, boolean enabled) { mMainThreadScheduler.schedule(() -> { mStartStopButtonInner.setBackgroundColor(color); mStartStopButtonInner.setText(text); mStartStopButton.setEnabled(enabled); }); } /** * @param fragment * @param container * @param enterAnimation * @param exitAnimation */ private void replaceFragment(Fragment fragment, int container, int enterAnimation, int exitAnimation) { if (fragment == null || getFragmentManager() == null) return; FragmentTransaction transaction = getActivity() .getSupportFragmentManager() .beginTransaction(); if (enterAnimation != -1 && exitAnimation != -1) { transaction.setCustomAnimations(enterAnimation, exitAnimation); } transaction.replace(container, fragment); transaction.commitAllowingStateLoss(); mCurrentlyVisible = fragment; } /** * @param fragment * @param enterAnimation * @param exitAnimation */ private void showFragment(Fragment fragment, int enterAnimation, int exitAnimation) { if (fragment == null || getFragmentManager() == null) return; FragmentTransaction transaction = getActivity().getSupportFragmentManager() .beginTransaction(); if (enterAnimation != -1) { transaction.setCustomAnimations(enterAnimation, exitAnimation); } transaction.show(fragment); transaction.commitAllowingStateLoss(); } /** * @param fragment * @param enterAnimation * @param exitAnimation */ private void hideFragment(Fragment fragment, int enterAnimation, int exitAnimation) { if (fragment == null || getFragmentManager() == null) return; FragmentTransaction transaction = getActivity().getSupportFragmentManager() .beginTransaction(); if (exitAnimation != -1) { transaction.setCustomAnimations(enterAnimation, exitAnimation); } transaction.hide(fragment); transaction.commitAllowingStateLoss(); } @UiThread private void onServiceStarting() { // bindService(); } @UiThread private void onServiceStarted() { // Hide the dashboard settings fragment if visible. if (mDashboardSettingsFragment.isVisible()) { getFragmentManager() .beginTransaction() .hide(mDashboardSettingsFragment) .commitAllowingStateLoss(); } getFragmentManager() .beginTransaction() .show(mDashboardHeaderFragment) .commitAllowingStateLoss(); getFragmentManager() .beginTransaction() .replace(R.id.fragment_startup_container, new DashboardTempomatFragment()) .commitAllowingStateLoss(); updateStartToStopButton(); } @UiThread private void onServiceStopping() { // unbindService(); } @UiThread private void onServiceStopped() { getFragmentManager().beginTransaction() .hide(mDashboardTempomatFragment) .hide(mDashboardHeaderFragment) .replace(R.id.fragment_startup_container, mDashboardMapFragment) .commitAllowingStateLoss(); if (mDashboardSettingsFragment != null) { getFragmentManager() .beginTransaction() .show(mDashboardSettingsFragment) .commitAllowingStateLoss(); } updateStartToStopButton(); } @UiThread private void updateStartToStopButton() { if (mServiceState == BluetoothServiceState.SERVICE_STARTED) { mStartStopButtonInner.setBackgroundColor(Color.RED); mStartStopButtonInner.setText("STOP TRACK"); } else if (mServiceState == BluetoothServiceState.SERVICE_STOPPED) { mStartStopButtonInner.setBackgroundColor( getResources().getColor(R.color.green_dark_cario)); mStartStopButtonInner.setText("START TRACK"); } } }