com.albertcbraun.cms50fw.alert.MainUIFragment.java Source code

Java tutorial

Introduction

Here is the source code for com.albertcbraun.cms50fw.alert.MainUIFragment.java

Source

/*
 * Copyright (c) 2015 Albert C. Braun
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.albertcbraun.cms50fw.alert;

import android.content.SharedPreferences;
import android.media.AudioManager;
import android.media.SoundPool;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.v4.app.Fragment;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ScrollView;
import android.widget.SeekBar;
import android.widget.TextView;

import com.albertcbraun.cms50fwlib.BluetoothNotAvailableException;
import com.albertcbraun.cms50fwlib.BluetoothNotEnabledException;
import com.albertcbraun.cms50fwlib.CMS50FWBluetoothConnectionManager;
import com.albertcbraun.cms50fwlib.CMS50FWConnectionListener;
import com.albertcbraun.cms50fwlib.DataFrame;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;

/**
 * Holds the UI elements for the main CMS50FW alert functionality.
 */
public class MainUIFragment extends Fragment {

    public static final String TAG = MainUIFragment.class.getSimpleName();

    private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("HH:mm:ss", Locale.US);
    private static final String MAXIMUM_SPO2_PERCENTAGE_MAX_VALUE_KEY_NAME = "MAXIMUM_SPO2_PERCENTAGE_MAX_VALUE_KEY_NAME";
    private static final String CURRENT_SPO2_PERCENTAGE_MAX_VALUE_KEY_NAME = "CURRENT_SPO2_PERCENTAGE_MAX_VALUE_KEY_NAME";
    private static final String BPM_STRING = " bpm";
    private static final String PERCENT_SIGN_STRING = "%";
    private static final String EMPTY_STRING = "";
    private static final String FINGER_OUT_MESSAGE = "Finger Out";
    private static final String FINGER_OUT_TOO_LONG_MESSAGE = "Finger Out For Too Many Seconds";
    private static final String OXYGEN_LEVEL_TOO_LOW_MESSAGE = "Oxygen Level Too Low For Too Many Seconds";
    private static final String CMS50FW_BLUETOOTH_DEVICE_NAME = "SpO202";
    private static final String DATA_FRAME_NULL_ALARM_MESSAGE = "Bluetooth connection to CMS50FW has apparently been lost";
    private static final String PLEASE_TURN_BLUETOOTH_ON_MESSAGE = "Please turn Bluetooth on in the Settings for this Android device.";
    private static final String BLUETOOTH_FUNCTIONALITY_IS_NOT_SUPPORTED_MESSAGE = "Bluetooth functionality is not supported on this Android device.";
    private static final String NEWLINE = "\n";
    private static final String LEFT_BRACKET = " [";
    private static final String RIGHT_BRACKET = "] ";
    //private static final String FINGER_IN_LOG_MESSAGE_PREFIX = "FINGER_IN:";
    //private static final String FINGER_OUT_LOG_MESSAGE_PREFIX = "FINGER_OUT:";

    private static final int MAX_MESSAGE_WINDOW_CHARS = 5000;
    private static final int LOWER_INDEX_MESSAGE_WINDOW_CHARS = 1000;
    private static final int MAXIMUM_SPO2_PERCENTAGE_DEFAULT_VALUE = 99;
    private static final int CURRENT_SPO2_PERCENTAGE_DEFAULT_VALUE = 80;
    private static final int FINGER_OUT_MESSAGE_THRESHOLD = 10;
    private static final int FINGER_OUT_ALARM_THRESHOLD = 600;
    private static final int OXYGEN_LEVEL_TOO_LOW_ALARM_THRESHOLD = 600;
    private static final int DATA_FRAME_NULL_ALARM_THRESHOLD = 600;
    private static final int SRC_QUALITY = 0;
    private static final int MAX_STREAMS = 1;
    //private static final String UNEXPECTED_DATA_FRAME_VALUES = "Unexpected Data Frame values:";
    private static final String SEARCHING_FOR_SIGNAL_MESSAGE = "Searching for O2 level and pulse ...";
    private static final int ONE_HUNDRED = 100;

    // alarm and sound related properties
    Integer minimumSpo2Percentage = null;
    private long consecutiveFingerOutDataFrameCount = 0;
    private long consecutiveSpO2OutOfRangeCount = 0;
    boolean uiAlertSet;
    boolean alertSoundEnabled = true;
    SoundPool soundPool = null;
    int soundStreamId = -1;

    // facilitate connections with the threads that talk directly to the CMS50FW
    private CMS50FWBluetoothConnectionManager cms50FWBluetoothConnectionManager = null;

    // UI elements and properties
    private Button connectButton = null;
    private TextView messageWindow = null;
    private TextView timeWindow = null;
    private TextView spo2Window = null;
    private TextView pulseWindow = null;
    private ScrollView messageWindowScrollView = null;
    private int consecutiveDataFrameNullCount = 0;

    public MainUIFragment() {
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.v(TAG, "onCreate");

        // keep this fragment around even if app and resource reconfiguration occurs
        // so this instance can hold on to the live bluetooth connection and IO streams.
        // this is desirable because we want the alarm functionality to be durable across
        // app lifecycle and teardown/restart events, to the degree possible
        setRetainInstance(true);

        // set up apparatus for bluetooth communication with the CMS50
        cms50FWBluetoothConnectionManager = new CMS50FWBluetoothConnectionManager(CMS50FW_BLUETOOTH_DEVICE_NAME);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        Log.v(TAG, "onCreateView");
        View rootView = inflater.inflate(R.layout.fragment_main_activity, container, false);

        // UI elements
        messageWindow = (TextView) rootView.findViewById(R.id.message_window);
        messageWindowScrollView = (ScrollView) rootView.findViewById(R.id.message_window_scroll_view);
        timeWindow = (TextView) rootView.findViewById(R.id.current_time_window);
        spo2Window = (TextView) rootView.findViewById(R.id.current_spo2_window);
        pulseWindow = (TextView) rootView.findViewById(R.id.current_pulse_window);
        connectButton = (Button) rootView.findViewById(R.id.connect_button);
        Button startReadingDataButton = (Button) rootView.findViewById(R.id.start_reading_data_button);
        Button stopReadingDataButton = (Button) rootView.findViewById(R.id.stop_reading_data_button);
        TextView minimumSpo2PercentageText = (TextView) rootView.findViewById(R.id.minimum_spo2_percentage_text);
        minimumSpo2PercentageText.addTextChangedListener(new Spo2PercentageTextChangedListener(this));
        SeekBar minimumSpo2PercentageSeekBar = (SeekBar) rootView.findViewById(R.id.minimum_spo2_percentage);
        setUpSeekBar(PreferenceManager.getDefaultSharedPreferences(getActivity().getApplicationContext()),
                minimumSpo2PercentageSeekBar, minimumSpo2PercentageText, minimumSpo2Percentage,
                MAXIMUM_SPO2_PERCENTAGE_MAX_VALUE_KEY_NAME, MAXIMUM_SPO2_PERCENTAGE_DEFAULT_VALUE,
                CURRENT_SPO2_PERCENTAGE_MAX_VALUE_KEY_NAME, CURRENT_SPO2_PERCENTAGE_DEFAULT_VALUE);

        connectButton.setEnabled(true);
        startReadingDataButton.setEnabled(false);
        stopReadingDataButton.setEnabled(false);

        soundPool = new SoundPool(MAX_STREAMS, AudioManager.STREAM_ALARM, SRC_QUALITY);

        // set a custom callback which is fully aware of the main fragment's UI
        CMS50FWConnectionListener cms50fwCallbacks = new CMS50FWCallbacks(this, connectButton,
                startReadingDataButton, stopReadingDataButton);
        this.cms50FWBluetoothConnectionManager.setCMS50FWConnectionListener(cms50fwCallbacks);

        return rootView;
    }

    private void setUpSeekBar(SharedPreferences defaultSettingsPreference, SeekBar seekBar,
            TextView correspondingTextView, Integer correspondingInteger,
            @SuppressWarnings("SameParameterValue") String maxValueSharedPrefsKeyName, int defaultMaxValue,
            String currentValueSharedPrefsKeyName, int defaultCurrValue) {

        SeekBar.OnSeekBarChangeListener seekBarChangeListener = new SeekBarChangeListener(getActivity(),
                correspondingTextView, correspondingInteger, currentValueSharedPrefsKeyName);

        seekBar.setMax(defaultSettingsPreference.getInt(maxValueSharedPrefsKeyName, defaultMaxValue));
        int currValue = defaultSettingsPreference.getInt(currentValueSharedPrefsKeyName, defaultCurrValue);
        correspondingTextView.setText(String.valueOf(currValue));
        seekBar.setProgress(currValue);
        seekBar.setOnSeekBarChangeListener(seekBarChangeListener);
    }

    public void connect(View view) {
        try {
            this.cms50FWBluetoothConnectionManager.connect(view.getContext());
            connectButton.setEnabled(false);
        } catch (BluetoothNotAvailableException e) {
            writeMessage(System.currentTimeMillis(), BLUETOOTH_FUNCTIONALITY_IS_NOT_SUPPORTED_MESSAGE);
            Log.w(TAG, e.toString());
        } catch (BluetoothNotEnabledException e) {
            writeMessage(System.currentTimeMillis(), PLEASE_TURN_BLUETOOTH_ON_MESSAGE);
            Log.w(TAG, e.toString());
        }
    }

    public void resetState(@SuppressWarnings("UnusedParameters") View v) {
        unsetUIAlert();
        cms50FWBluetoothConnectionManager.reset();
    }

    public void startData(@SuppressWarnings("UnusedParameters") View view) {
        cms50FWBluetoothConnectionManager.startData();
    }

    public void stopData(@SuppressWarnings("UnusedParameters") View ignored) {
        cms50FWBluetoothConnectionManager.stopData();
    }

    public void clearWindow(@SuppressWarnings("UnusedParameters") View v) {
        messageWindow.post(new Runnable() {
            @Override
            public void run() {
                messageWindow.setText(EMPTY_STRING);
            }
        });
    }

    void updateUI(long time, final String spo2, final String pulse) {
        final Date d = new Date(time);
        timeWindow.setText(DATE_FORMAT.format(d));
        spo2Window.setText(spo2);
        pulseWindow.setText(pulse);
    }

    void disableAlertSound() {
        alertSoundEnabled = false;
    }

    void enableAlertSound() {
        alertSoundEnabled = true;
    }

    void setUIAlert(final String alertMessage) {
        this.onResume();
        if (!uiAlertSet) {
            if (getView() != null) {
                getView().post(new ActivateAlertTask(this, alertMessage));
            }
        }
    }

    void unsetUIAlert() {
        if (uiAlertSet) {
            if (getView() != null) {
                getView().post(new DeactivateAlertTask(this));
            }
        }
    }

    void stopAlertSound() {
        if (soundPool != null && soundStreamId > -1) {
            soundPool.stop(soundStreamId);
        }
    }

    void writeMessage(long timeStamp, final String s) {
        final Date d = new Date(timeStamp);
        messageWindow.post(new Runnable() {
            @Override
            public void run() {
                String text = messageWindow.getText().toString();
                if (text.length() > MAX_MESSAGE_WINDOW_CHARS) {
                    messageWindow.setText(text.substring(LOWER_INDEX_MESSAGE_WINDOW_CHARS, text.length() - 1));
                }
                messageWindow.append(NEWLINE + LEFT_BRACKET + DATE_FORMAT.format(d) + RIGHT_BRACKET + s);
                messageWindowScrollView.post(new Runnable() {
                    public void run() {
                        messageWindowScrollView.fullScroll(View.FOCUS_DOWN);
                    }
                });

            }
        });
    }

    @Override
    public void onDestroy() {
        Log.v(TAG, "onDestroy!!");
        super.onDestroy();
        cms50FWBluetoothConnectionManager.dispose(getActivity().getApplicationContext());
    }

    public void processDataFrame(DataFrame dataFrame) {
        if (dataFrame == null) {
            consecutiveDataFrameNullCount++;
            if (consecutiveDataFrameNullCount > DATA_FRAME_NULL_ALARM_THRESHOLD) {
                setUIAlert(DATA_FRAME_NULL_ALARM_MESSAGE);
            }
        } else {
            consecutiveDataFrameNullCount = 0;
            if (dataFrame.spo2Percentage <= ONE_HUNDRED) { // valid data frame
                consecutiveFingerOutDataFrameCount = 0;
                //Log.v(TAG, FINGER_IN_LOG_MESSAGE_PREFIX + dataFrame.toString());
                updateUI(dataFrame.time, dataFrame.spo2Percentage + PERCENT_SIGN_STRING,
                        dataFrame.pulseRate + BPM_STRING);
                if (dataFrame.spo2Percentage < minimumSpo2Percentage) {
                    consecutiveSpO2OutOfRangeCount++;
                } else {
                    consecutiveSpO2OutOfRangeCount = 0;
                }
                if (consecutiveSpO2OutOfRangeCount > OXYGEN_LEVEL_TOO_LOW_ALARM_THRESHOLD) {
                    setUIAlert(OXYGEN_LEVEL_TOO_LOW_MESSAGE);
                } else {
                    unsetUIAlert();
                }
            } else { // probably not valid data frame
                if (dataFrame.isFingerOutOfSleeve) {
                    consecutiveFingerOutDataFrameCount++;
                    if (consecutiveFingerOutDataFrameCount > FINGER_OUT_MESSAGE_THRESHOLD) {
                        //Log.v(TAG, FINGER_OUT_LOG_MESSAGE_PREFIX + dataFrame.toString());
                        updateUI(dataFrame.time, FINGER_OUT_MESSAGE, EMPTY_STRING);
                    }
                    if (consecutiveFingerOutDataFrameCount > FINGER_OUT_ALARM_THRESHOLD) {
                        setUIAlert(FINGER_OUT_TOO_LONG_MESSAGE);
                    }
                } else {
                    //Log.w(TAG, UNEXPECTED_DATA_FRAME_VALUES + dataFrame.toString());
                    updateUI(dataFrame.time, SEARCHING_FOR_SIGNAL_MESSAGE, EMPTY_STRING);
                }
            }
        }
    }

}