com.agustinprats.myhrv.fragment.MonitorFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.agustinprats.myhrv.fragment.MonitorFragment.java

Source

/* Copyright (c) 2013-2014 Agustn Prats
 *
 * This file is part of HeartWave.
 *
 *  HeartWave 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.
 *
 *  HeartWave 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.
 *
 * You should have received a copy of the GNU General Public License
 *  along with HeartWave.  If not, see <http://www.gnu.org/licenses/>.
 */

package com.agustinprats.myhrv.fragment;

import android.app.Activity;
import android.app.AlertDialog;
import android.provider.SyncStateContract;
import android.support.v4.app.Fragment;
import android.content.DialogInterface;
import android.graphics.Typeface;
import android.os.Handler;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.WindowManager;
import android.view.animation.Animation;
import android.view.animation.ScaleAnimation;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;

import com.agustinprats.myhrv.MainActivity;
import com.agustinprats.myhrv.R;
import com.agustinprats.myhrv.adapter.HeartRateDeviceListAdapter;
import com.agustinprats.myhrv.model.HeartRateDevice;
import com.agustinprats.myhrv.model.RrIntervalList;
import com.agustinprats.myhrv.service.HeartRateService;
import com.agustinprats.myhrv.service.HeartRateServiceListener;
import com.agustinprats.myhrv.util.Utils;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * Base Fragment used to display the common things in a heart rate variability monitor.
 */
public abstract class MonitorFragment extends Fragment implements HeartRateServiceListener {

    private final static String TAG = MonitorFragment.class.toString();

    // Connection status to the heart rate monitor
    private final static int STATUS_DISCONNECTED = 0;
    private final static int STATUS_CONNECTING = 1;
    private final static int STATUS_CONNECTED = 2;
    private int status = 0;

    // Heart Beat Animation
    private final static float BEATUP_SCALE = 1f;
    private final static float BEATDOWN_SCALE = 1.3f;
    private final static int BEATUP_MS = 80;
    private final static float BEATUP_ALPHA = 1f;
    private final static float BEATDOWN_ALPHA = 0.75f;
    private final static int BEATDOWN_MS = 160;
    final Animation _beatUp = new ScaleAnimation(BEATUP_SCALE, BEATDOWN_SCALE, BEATUP_SCALE, BEATDOWN_SCALE,
            Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    final Animation _beatDown = new ScaleAnimation(BEATDOWN_SCALE, BEATUP_SCALE, BEATDOWN_SCALE, BEATUP_SCALE,
            Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
    boolean _isHeartBeating = false;

    // UI
    protected ImageView _heartImage;
    protected TextView _statusText;
    protected TextView _searchingDevicesText = null;
    protected HeartRateDeviceListAdapter _heartRateDeviceListAdapter;
    protected TextView _timeText;
    protected boolean _inForeground = false;
    protected Typeface _digitalTypeface;
    protected ImageView _batteryLevelImage;
    protected boolean _lightBattery = false;

    /** This method should be called when the fragment is initialised in the onCreate */
    protected void initUI(View rootView) {

        _heartImage = (ImageView) rootView.findViewById(R.id.heartImage);
        _statusText = (TextView) rootView.findViewById(R.id.statusText);
        _timeText = (TextView) rootView.findViewById(R.id.timeText);
        _batteryLevelImage = (ImageView) rootView.findViewById(R.id.batteryLevelImage);
        _digitalTypeface = Typeface.createFromAsset(getMainActivity().getAssets(), "fonts/digital7.ttf");
        _timeText.setTypeface(_digitalTypeface);
        _heartImage.setAlpha(BEATDOWN_ALPHA);

        setHasOptionsMenu(true);
    }

    /** Sets the connection status of the hrv monitor.
     * It will call the connection callbacks.
     * See @onStatusDisconnected, @onStatusConnecting, @onStatusConnected
     */
    public void setStatus(final int status) {

        if (isAdded() && getMainActivity() != null) {

            getMainActivity().runOnUiThread(new Runnable() {

                @Override
                public void run() {

                    if (isAdded() && getMainActivity() != null) {
                        Log.d(TAG, "setStatus=" + status);

                        switch (status) {
                        case STATUS_DISCONNECTED:
                            onStatusDisconnected();
                            break;
                        case STATUS_CONNECTING:
                            onStatusConnecting();
                            break;
                        case STATUS_CONNECTED:
                            onStatusConnected();
                            break;
                        }

                        getMainActivity().invalidateOptionsMenu();
                    }
                    MonitorFragment.this.status = status;
                }
            });
        }
    }

    /** Returns the connection status to the hrv monitor */
    public int getStatus() {

        return status;
    }

    /** This callback is executed when  the connection status changes to connected */
    protected void onStatusConnected() {
        Log.d(TAG, "onStatusConnected");
        _statusText.setText(getMainActivity().getString(R.string.status_connected));
        onHeartRateServiceBatteryLevelChanged(getMainActivity().getHeartRateService().getBatteryLevel());

    }

    /** This callback is executed when the connection status changes to connecting */
    protected void onStatusConnecting() {
        Log.d(TAG, "onStatusConnecting");
        _statusText.setText(getMainActivity().getString(R.string.status_connecting));
        _timeText.setText("00:00");
        _timeText.setText(R.string.no_value);
        _batteryLevelImage.setVisibility(View.INVISIBLE);
    }

    /** This callback is executed when the connection status changes to disconnected */
    protected void onStatusDisconnected() {
        Log.d(TAG, "onStatusDisconnected");
        _statusText.setText(getMainActivity().getString(R.string.status_disconnected));
        _timeText.setText("00:00");
        _batteryLevelImage.setVisibility(View.INVISIBLE);
    }

    @Override
    public void onStart() {

        super.onStart();

    }

    @Override
    public void onResume() {
        Log.d(TAG, "onResume");
        super.onResume();

        _inForeground = true;

        getMainActivity().requestEnableBt();

        getMainActivity().getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        updateStatus();

        if (getMainActivity().getHeartRateService() != null) {

            getMainActivity().getHeartRateService().addServiceListener(this);
            // elad change to choose device if device is not chosen
            if (getMainActivity().getHeartRateService().getDeviceAddress() == null) {
                showConnectDialog();
            }
            ;
        }
        ;

    }

    @Override
    public void onPause() {
        Log.d(TAG, "onPause");
        super.onPause();

        _inForeground = false;

        getMainActivity().getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);

        _heartImage.clearAnimation();
        _isHeartBeating = false;

        HeartRateService service = getMainActivity().getHeartRateService();
        // if (service != null)
        //   service.removeServiceListener(this);
    }

    /** This method will set the connection status in the fragment
     *  based on the connection status of the @HeartRateService */
    private void updateStatus() {

        HeartRateService service = getMainActivity().getHeartRateService();

        if (service == null || service.isDisconnected()) {

            setStatus(STATUS_DISCONNECTED);
        } else if (service.isConnecting()) {

            setStatus(STATUS_CONNECTING);
        } else if (service.isConnected()) {

            setStatus(STATUS_CONNECTED);
        }
    }

    /** Starts a single heart beat animation */
    public void heartBeat() {

        if (!_isHeartBeating && isAdded()) {

            // Start heart beat
            _beatUp.setDuration(BEATUP_MS);
            _beatDown.setDuration(BEATDOWN_MS);
            Animation.AnimationListener beatingListener = new Animation.AnimationListener() {
                @Override
                public void onAnimationStart(Animation animation) {
                }

                @Override
                public void onAnimationRepeat(Animation animation) {
                }

                @Override
                public void onAnimationEnd(Animation animation) {

                    if (animation == _beatUp) {

                        _heartImage.startAnimation(_beatDown);
                    } else if (animation == _beatDown) {

                        _heartImage.setAlpha(BEATDOWN_ALPHA);
                        _isHeartBeating = false;
                    }
                }
            };
            _beatUp.setAnimationListener(beatingListener);
            _beatDown.setAnimationListener(beatingListener);
            _heartImage.startAnimation(_beatUp);
            _heartImage.setAlpha(BEATUP_ALPHA);
            _isHeartBeating = true;
        }
    }

    public void scanDevices(View view) {
        HeartRateService service = getMainActivity().getHeartRateService();

        service.disconnect();
        service.close();

        showConnectDialog();
    };

    /** Shows the connect dialog displaying a list of available devices to connect */
    private void showConnectDialog() {

        LayoutInflater inflater = getMainActivity().getLayoutInflater();

        // Init view
        RelativeLayout devicesDialogView = (RelativeLayout) inflater.inflate(R.layout.devices_dialog, null);

        AlertDialog.Builder devicesDialogBuilder = new AlertDialog.Builder(getMainActivity());
        devicesDialogBuilder.setTitle(R.string.devices_available);
        devicesDialogBuilder.setView(devicesDialogView);
        final AlertDialog devicesDialog = devicesDialogBuilder.create();

        _searchingDevicesText = (TextView) devicesDialogView.findViewById(R.id.searchingText);

        ListView listView = (ListView) devicesDialogView.findViewById(R.id.listView);
        listView.setEmptyView(_searchingDevicesText);
        final HeartRateDeviceListAdapter mHeartRateDeviceListAdapter = new HeartRateDeviceListAdapter(inflater);
        _heartRateDeviceListAdapter = mHeartRateDeviceListAdapter;

        final HeartRateService service = getMainActivity().getHeartRateService();
        service.startScanningDevices();
        listView.setAdapter(mHeartRateDeviceListAdapter);
        listView.setOnItemClickListener(new AdapterView.OnItemClickListener() {
            @Override
            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {

                final HeartRateDevice device = mHeartRateDeviceListAdapter.getDevice(position);
                if (device != null) {

                    if (service.isScanning())
                        service.stopScanningDevices();

                    Log.d(TAG, "connecting from devices dialog");
                    service.connect(device.getAddress());
                }
                devicesDialog.dismiss();
            }
        });

        devicesDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
            @Override
            public void onDismiss(DialogInterface dialog) {

                service.stopScanningDevices();
                _heartRateDeviceListAdapter = null;
            }
        });

        devicesDialog.show();
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        /* Elad change to take out menu
                final HeartRateService service = getMainActivity().getHeartRateService();
                inflater.inflate(R.menu.monitor, menu);
                menu.findItem(R.id.menu_reconnect).setVisible(service != null && service.isDisconnected() && service.reconnectAvailable());
                menu.findItem(R.id.menu_scan).setVisible(service != null && service.isDisconnected());
                 menu.findItem(R.id.menu_disconnect).setVisible(service != null && !service.isDisconnected());
        */

    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {

        final HeartRateService service = getMainActivity().getHeartRateService();

        if (item.getItemId() == R.id.menu_reconnect)
            service.reconnect();
        if (item.getItemId() == R.id.menu_scan)
            showConnectDialog();
        if (item.getItemId() == R.id.menu_disconnect) {
            service.disconnect();
            service.stopScanningDevices();
            this.setMenuVisibility(false);
            this.setMenuVisibility(true);
        }
        if (item.getItemId() == R.id.menu_help)
            showHelp();

        return true;
    }

    /** Displays help to the user. */
    private void showHelp() {

        AlertDialog.Builder alert = new AlertDialog.Builder(getActivity());
        alert.setTitle(getActivity().getString(R.string.Help));

        WebView wv = new WebView(getActivity());

        // loading the html file
        String htmlData = readAsset("help.html");

        // selecting the css based on the activity theme
        //        htmlData = htmlData.replaceFirst("CSS_FILE_NAME", ((MainActivity) getActivity()).isLightTheme() ? "light" : "dark");
        htmlData = htmlData.replaceFirst("CSS_FILE_NAME", "dark");
        // loading html in the webview
        wv.loadDataWithBaseURL("file:///android_asset/", htmlData, "text/html", "UTF-8", null);

        wv.setWebViewClient(new WebViewClient() {
            @Override
            public boolean shouldOverrideUrlLoading(WebView view, String url) {
                view.loadUrl(url);
                return true;
            }
        });

        alert.setView(wv);
        alert.setNegativeButton(getActivity().getString(R.string.Close), new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int id) {
                dialog.dismiss();
            }
        });
        alert.show();
    }

    private String readAsset(String name) {

        StringBuilder buf = new StringBuilder();
        try {
            InputStream json = getMainActivity().getAssets().open(name);
            BufferedReader in = new BufferedReader(new InputStreamReader(json, "UTF-8"));
            String str;
            while ((str = in.readLine()) != null) {
                buf.append(str);
            }
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return buf.toString();
    }

    /** Starts so many heart beat animations as specified in the parameter @count.
     *  Each animation duration is based on the RR intervals
     *  passed in the @intervals parameter */
    private void heartBeats(RrIntervalList intervals, int count) {

        heartBeat();
        for (int i = intervals.size() - count; i < intervals.size(); i++) {

            final Handler handler = new Handler();
            handler.postDelayed(new Runnable() {
                @Override
                public void run() {

                    heartBeat();
                }
            }, intervals.get(i).getRRInterval());
        }
    }

    //--------- HeartRateService Listener Implementation

    /** Called when connects to a HeartRateDevice */
    public void onHeartRateServiceConnected() {

        //   setBatteryLevel(getMainActivity().getHeartRateService().getBatteryLevel());
    }

    /** Called when it's connecting to a HeartRateDevice */
    public void onHeartRateServiceConnecting() {

        if (isAdded() && _inForeground)
            getMainActivity().runOnUiThread(new Runnable() {

                @Override
                public void run() {

                    setStatus(STATUS_CONNECTING);
                }
            });
    }

    /** Called when disconnect from a HeartRateDevice */
    public void onHeartRateServiceDisconnected(final Integer errorCode) {

        if (isAdded() && _inForeground)
            getMainActivity().runOnUiThread(new Runnable() {

                @Override
                public void run() {

                    setStatus(STATUS_DISCONNECTED);

                    MainActivity mainActivity = getMainActivity();
                    if (errorCode != null && mainActivity != null) {

                        if (!mainActivity.isDestroyed() || !mainActivity.isFinishing()) {

                            AlertDialog.Builder builder = new AlertDialog.Builder(mainActivity);
                            builder.setTitle(R.string.disconnected);
                            builder.setMessage(getString(errorCode));
                            AlertDialog dialog = builder.create();
                            dialog.show();
                        }
                    }
                }
            });
    }

    /** Sets a new battery level. */
    private void setBatteryLevel(final int newLevel) {

        if (isAdded() && _inForeground)
            getMainActivity().runOnUiThread(new Runnable() {

                @Override
                public void run() {

                    if (newLevel < 0) {

                        _batteryLevelImage.setVisibility(View.INVISIBLE);
                    } else if (newLevel < 25) {

                        if (_lightBattery)
                            _batteryLevelImage.setImageResource(R.drawable.battery_0_light);
                        else
                            _batteryLevelImage.setImageResource(R.drawable.battery_0_dark);
                    } else if (newLevel < 50) {

                        if (_lightBattery)
                            _batteryLevelImage.setImageResource(R.drawable.battery_25_light);
                        else
                            _batteryLevelImage.setImageResource(R.drawable.battery_25_dark);
                    } else if (newLevel < 75) {

                        if (_lightBattery)
                            _batteryLevelImage.setImageResource(R.drawable.battery_50_light);
                        else
                            _batteryLevelImage.setImageResource(R.drawable.battery_50_dark);
                    } else if (newLevel < 90) {

                        if (_lightBattery)
                            _batteryLevelImage.setImageResource(R.drawable.battery_75_light);
                        else
                            _batteryLevelImage.setImageResource(R.drawable.battery_75_dark);
                    } else {

                        if (_lightBattery)
                            _batteryLevelImage.setImageResource(R.drawable.battery_100_light);
                        else
                            _batteryLevelImage.setImageResource(R.drawable.battery_100_dark);
                    }
                    _batteryLevelImage.setVisibility(View.VISIBLE);
                }
            });
    }

    /** Called when
     * the battery level is received from a HeartRateDevice */
    public void onHeartRateServiceBatteryLevelChanged(final int newLevel) {

        setBatteryLevel(newLevel);
    }

    /** Called when new RR intervals are received
     *  @intervals All intervals received while the device's been connected
     *  @count Number of RR intervals that are new. They can be found at the end of the list */
    public void onHeartRateServiceNewRrIntervals(final RrIntervalList intervals, final int count) {

        if (isAdded() && _inForeground)
            getMainActivity().runOnUiThread(new Runnable() {

                @Override
                public void run() {

                    if (getStatus() == STATUS_DISCONNECTED || getStatus() == STATUS_CONNECTING) {
                        setStatus(STATUS_CONNECTED);
                    }
                    heartBeats(intervals, count);
                    _timeText.setText(Utils.getDigitalDuration(intervals.getMeasuredTime() / 1000));
                }
            });
    }

    /** Called when intervals are dropped because
     *  are out of range of because changed to fast */
    public void onHeartRateServiceDroppedIntervals(final int count) {

        if (isAdded() && _inForeground)
            getMainActivity().runOnUiThread(new Runnable() {

                @Override
                public void run() {

                }
            });
    }

    /** Called when a new @HeartRateDevice is found while scanning */
    public void onHeartRateServiceDeviceFound(final HeartRateDevice device) {

        if (isAdded() && _inForeground)
            getMainActivity().runOnUiThread(new Runnable() {

                @Override
                public void run() {

                    if (isAdded() && getMainActivity() != null && _heartRateDeviceListAdapter != null) {

                        _heartRateDeviceListAdapter.addDevice(device);
                    }
                }
            });
    }

    /** Called when stopped scanning for new devices */
    public void onHeartRateServiceDeviceScanStopped() {

        if (isAdded() && _inForeground)
            getMainActivity().runOnUiThread(new Runnable() {

                @Override
                public void run() {

                    if (_searchingDevicesText != null) {

                        _searchingDevicesText.setText("No devices found.");
                    }
                }
            });
    }

    /** Called when the service is binded to the activity */
    public void onHeartRateServiceBinded(final HeartRateService service) {

        service.addServiceListener(MonitorFragment.this);
        updateStatus();

        // Elad change to show device list if device not connected
        if (getMainActivity().isBluetoothEnabled())
            if (getMainActivity().getHeartRateService() != null) {
                // elad change to choose device if device is not chosen
                if (getMainActivity().getHeartRateService().getDeviceAddress() == null) {
                    showConnectDialog();
                }
                ;
            }
        ;
    }

    /** Called when the service is unbinded from the activity */
    public void onHeartRateServiceUnbinded(final HeartRateService service) {

        service.removeServiceListener(MonitorFragment.this);
    }

    public MainActivity getMainActivity() {

        Activity activity = getActivity();
        if (activity != null && activity instanceof MainActivity) {
            return (MainActivity) activity;
        }
        return null;
    }
}