org.noise_planet.noisecapture.CalibrationLinearityActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.noise_planet.noisecapture.CalibrationLinearityActivity.java

Source

/*
 * This file is part of the NoiseCapture application and OnoMap system.
 *
 * The 'OnoMaP' system is led by Lab-STICC and Ifsttar and generates noise maps via
 * citizen-contributed noise data.
 *
 * This application is co-funded by the ENERGIC-OD Project (European Network for
 * Redistributing Geospatial Information to user Communities - Open Data). ENERGIC-OD
 * (http://www.energic-od.eu/) is partially funded under the ICT Policy Support Programme (ICT
 * PSP) as part of the Competitiveness and Innovation Framework Programme by the European
 * Community. The application work is also supported by the French geographic portal GEOPAL of the
 * Pays de la Loire region (http://www.geopal.org).
 *
 * Copyright (C) IFSTTAR - LAE and Lab-STICC  CNRS UMR 6285 Equipe DECIDE Vannes
 *
 * NoiseCapture is a 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. NoiseCapture 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 this
 * program; if not, write to the Free Software Foundation,Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301  USA or see For more information,  write to Ifsttar,
 * 14-20 Boulevard Newton Cite Descartes, Champs sur Marne F-77447 Marne la Vallee Cedex 2 FRANCE
 *  or write to scientific.computing@ifsttar.fr
 */

package org.noise_planet.noisecapture;

import android.app.AlertDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.AudioTrack;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.preference.PreferenceManager;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentPagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.widget.AppCompatImageButton;
import android.view.Menu;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.ProgressBar;
import android.widget.TextView;
import android.widget.Toast;

import com.github.mikephil.charting.charts.BarChart;
import com.github.mikephil.charting.charts.LineChart;
import com.github.mikephil.charting.charts.ScatterChart;
import com.github.mikephil.charting.components.Legend;
import com.github.mikephil.charting.components.XAxis;
import com.github.mikephil.charting.components.YAxis;
import com.github.mikephil.charting.data.BarData;
import com.github.mikephil.charting.data.BarDataSet;
import com.github.mikephil.charting.data.BarEntry;
import com.github.mikephil.charting.data.Entry;
import com.github.mikephil.charting.data.LineData;
import com.github.mikephil.charting.data.LineDataSet;
import com.github.mikephil.charting.data.ScatterData;
import com.github.mikephil.charting.data.ScatterDataSet;
import com.github.mikephil.charting.interfaces.datasets.IBarDataSet;
import com.github.mikephil.charting.interfaces.datasets.ILineDataSet;
import com.github.mikephil.charting.interfaces.datasets.IScatterDataSet;
import com.github.mikephil.charting.utils.ColorTemplate;

import org.apache.commons.math3.stat.correlation.PearsonsCorrelation;
import org.orbisgis.sos.FFTSignalProcessing;
import org.orbisgis.sos.LeqStats;
import org.orbisgis.sos.ThirdOctaveBandsFiltering;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TreeSet;

public class CalibrationLinearityActivity extends MainActivity implements PropertyChangeListener,
        SharedPreferences.OnSharedPreferenceChangeListener, ViewPager.OnPageChangeListener {
    private enum CALIBRATION_STEP {
        IDLE, WARMUP, CALIBRATION, END
    }

    private int splLoop = 0;
    private double splBackroundNoise = 0;
    private static final int DB_STEP = 3;
    private double whiteNoisedB = 0;
    private ProgressBar progressBar_wait_calibration_recording;
    private TextView startButton;
    private TextView applyButton;
    private TextView resetButton;
    private CALIBRATION_STEP calibration_step = CALIBRATION_STEP.IDLE;
    private TextView textStatus;
    private TextView textDeviceLevel;
    private CheckBox testGainCheckBox;
    private Handler timeHandler;
    private int defaultWarmupTime;
    private int defaultCalibrationTime;
    private LeqStats leqStats;
    private List<LinearCalibrationResult> freqLeqStats = new ArrayList<>();
    private boolean mIsBound = false;
    private TabLayout tabLayout;
    private MeasurementService measurementService;
    private static final Logger LOGGER = LoggerFactory.getLogger(CalibrationLinearityActivity.class);
    private static final int COUNTDOWN_STEP_MILLISECOND = 125;
    private ProgressHandler progressHandler = new ProgressHandler(this);
    private Set<Integer> selectedFrequencies = new HashSet<>(Arrays.asList(1000));
    private ViewPager viewPager;
    private static final int PAGE_SCATTER_CHART = 0;
    private static final int PAGE_LINE_CHART = 1;
    private static final int PAGE_BAR_CHART = 2;

    private static final String SETTINGS_CALIBRATION_WARMUP_TIME = "settings_calibration_warmup_time";
    private static final String SETTINGS_CALIBRATION_TIME = "settings_calibration_time";

    private AudioTrack audioTrack;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_calibration_linearity);
        initDrawer();
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        sharedPref.registerOnSharedPreferenceChangeListener(this);
        defaultCalibrationTime = getInteger(sharedPref, SETTINGS_CALIBRATION_TIME, 10);
        defaultWarmupTime = getInteger(sharedPref, SETTINGS_CALIBRATION_WARMUP_TIME, 5);

        progressBar_wait_calibration_recording = (ProgressBar) findViewById(
                R.id.progressBar_wait_calibration_recording);
        applyButton = (TextView) findViewById(R.id.btn_apply);
        applyButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onApply();
            }
        });
        textStatus = (TextView) findViewById(R.id.textView_recording_state);
        textDeviceLevel = (TextView) findViewById(R.id.textView_value_SL_i);
        testGainCheckBox = (CheckBox) findViewById(R.id.checkbox_test_gain);
        startButton = (TextView) findViewById(R.id.btn_start);
        viewPager = (ViewPager) findViewById(R.id.viewpager);
        setupViewPager(viewPager);
        tabLayout = (TabLayout) findViewById(R.id.tabs);
        tabLayout.setupWithViewPager(viewPager);
        resetButton = (TextView) findViewById(R.id.btn_reset);
        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onCalibrationStart();
            }
        });
        resetButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                onReset();
            }
        });
        viewPager.addOnPageChangeListener(this);
        initCalibration();
    }

    @Override
    public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

    }

    @Override
    public void onPageSelected(int position) {

    }

    @Override
    public void onPageScrollStateChanged(int state) {
        if (state == ViewPager.SCROLL_STATE_SETTLING) {
            initSelectedGraph();
        } else if (state == ViewPager.SCROLL_STATE_IDLE) {
            updateSelectedGraph();
        }
    }

    private void initSelectedGraph() {
        try {
            switch (viewPager.getCurrentItem()) {
            case PAGE_SCATTER_CHART:
                initScatter();
                break;
            case PAGE_LINE_CHART:
                initLine();
                break;
            case PAGE_BAR_CHART:
                initBar();
                break;
            }
        } catch (NullPointerException ex) {
            // May arise while measuring
            LOGGER.error(ex.getLocalizedMessage(), ex);
        }
    }

    private void updateSelectedGraph() {
        try {
            switch (viewPager.getCurrentItem()) {
            case PAGE_SCATTER_CHART:
                updateScatterChart();
                break;
            case PAGE_LINE_CHART:
                updateLineChart();
                break;
            case PAGE_BAR_CHART:
                updateBarChart();
                break;
            }
        } catch (NullPointerException ex) {
            // May arise while measuring
            LOGGER.error(ex.getLocalizedMessage(), ex);
        }
    }

    private ScatterChart getScatterChart() {
        View view = ((ViewPagerAdapter) viewPager.getAdapter()).getItem(PAGE_SCATTER_CHART).getView();
        if (view != null) {
            return (ScatterChart) view.findViewById(R.id.autoCalibrationScatterChart);
        } else {
            return null;
        }
    }

    private BarChart getBarChart() {
        View view = ((ViewPagerAdapter) viewPager.getAdapter()).getItem(PAGE_BAR_CHART).getView();
        if (view != null) {
            return (BarChart) view.findViewById(R.id.autoCalibrationBarChart);
        } else {
            return null;
        }
    }

    private LineChart getLineChart() {
        View view = ((ViewPagerAdapter) viewPager.getAdapter()).getItem(PAGE_LINE_CHART).getView();
        if (view != null) {
            return (LineChart) view.findViewById(R.id.autoCalibrationLineChart);
        } else {
            return null;
        }
    }

    private void setupViewPager(ViewPager viewPager) {
        ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager());
        adapter.addFragment(new CalibrationScatterFragment(), "Scatter");
        adapter.addFragment(new CalibrationLineFragment(), "Line");
        adapter.addFragment(new CalibrationBarFragment(), "Pearson");
        viewPager.setAdapter(adapter);
    }

    private void initBar() {
        BarChart barChart = getBarChart();
        if (barChart == null) {
            return;
        }
        barChart.setDescription("");

        barChart.setDrawGridBackground(false);

        barChart.setMaxVisibleValueCount(200);

        Legend l = barChart.getLegend();
        l.setPosition(Legend.LegendPosition.ABOVE_CHART_LEFT);
        l.setTextColor(Color.WHITE);

        YAxis yl = barChart.getAxisLeft();
        yl.setTextColor(Color.WHITE);
        yl.setGridColor(Color.WHITE);
        barChart.getAxisRight().setEnabled(false);

        XAxis xl = barChart.getXAxis();
        xl.setDrawGridLines(false);
        xl.setTextColor(Color.WHITE);
        xl.setGridColor(Color.WHITE);
        xl.setPosition(XAxis.XAxisPosition.BOTTOM);
        xl.setDrawAxisLine(true);
        xl.setLabelRotationAngle(-90);
        xl.setDrawLabels(true);
        xl.setLabelsToSkip(0);
    }

    private static final class ItemActionOnClickListener implements DialogInterface.OnClickListener {
        CalibrationLinearityActivity calibrationLinearityActivity;
        ScatterChart scatterChart;

        public ItemActionOnClickListener(CalibrationLinearityActivity calibrationLinearityActivity,
                ScatterChart scatterChart) {
            this.calibrationLinearityActivity = calibrationLinearityActivity;
            this.scatterChart = scatterChart;
        }

        @Override
        public void onClick(DialogInterface dialog, int which) {
            if (which > 0) {
                calibrationLinearityActivity.selectedFrequencies = new HashSet<Integer>(Collections
                        .singletonList((int) ThirdOctaveBandsFiltering.STANDARD_FREQUENCIES_REDUCED[which - 1]));
            } else {
                // Select all frequencies
                calibrationLinearityActivity.selectedFrequencies = new HashSet<Integer>();
                for (double freq : ThirdOctaveBandsFiltering.STANDARD_FREQUENCIES_REDUCED) {
                    calibrationLinearityActivity.selectedFrequencies.add((int) freq);
                }
            }
            calibrationLinearityActivity.updateSelectedGraph();
        }
    }

    private void initScatter() {
        final ScatterChart scatterChart = getScatterChart();
        if (scatterChart == null) {
            return;
        }
        scatterChart.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // Show
                AlertDialog.Builder builder = new AlertDialog.Builder(CalibrationLinearityActivity.this);
                builder.setTitle(CalibrationLinearityActivity.this.getText(R.string.calibration_select_frequency));
                ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource(
                        CalibrationLinearityActivity.this, R.array.calibrate_type_list_array,
                        android.R.layout.simple_selectable_list_item);
                builder.setAdapter(adapter,
                        new ItemActionOnClickListener(CalibrationLinearityActivity.this, scatterChart));
                builder.show();
            }
        });

        scatterChart.setDescription("");

        scatterChart.setDrawGridBackground(false);

        scatterChart.setMaxVisibleValueCount(200);

        Legend l = scatterChart.getLegend();
        l.setPosition(Legend.LegendPosition.RIGHT_OF_CHART);
        l.setTextColor(Color.WHITE);

        YAxis yl = scatterChart.getAxisLeft();
        yl.setTextColor(Color.WHITE);
        yl.setGridColor(Color.WHITE);
        scatterChart.getAxisRight().setEnabled(false);

        XAxis xl = scatterChart.getXAxis();
        xl.setDrawGridLines(false);
        xl.setTextColor(Color.WHITE);
        xl.setGridColor(Color.WHITE);
    }

    private void initLine() {
        LineChart lineChart = getLineChart();
        if (lineChart == null) {
            return;
        }
        lineChart.setDescription("");

        lineChart.setDrawGridBackground(false);

        // enable scaling and dragging

        Legend l = lineChart.getLegend();
        l.setPosition(Legend.LegendPosition.RIGHT_OF_CHART);
        l.setTextColor(Color.WHITE);

        YAxis yl = lineChart.getAxisLeft();
        yl.setTextColor(Color.WHITE);
        yl.setGridColor(Color.WHITE);
        lineChart.getAxisRight().setEnabled(false);

        XAxis xl = lineChart.getXAxis();
        xl.setDrawGridLines(false);
        xl.setTextColor(Color.WHITE);
        xl.setGridColor(Color.WHITE);
        xl.setPosition(XAxis.XAxisPosition.BOTTOM);
        xl.setDrawAxisLine(true);
        xl.setLabelRotationAngle(-90);
        xl.setDrawLabels(true);
        xl.setLabelsToSkip(0);
    }

    private void onApply() {
        try {
            // TODO
        } finally {
            onReset();
        }
    }

    @Override
    public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
        if (SETTINGS_CALIBRATION_TIME.equals(key)) {
            defaultCalibrationTime = getInteger(sharedPreferences, SETTINGS_CALIBRATION_TIME, 10);
        } else if (SETTINGS_CALIBRATION_WARMUP_TIME.equals(key)) {
            defaultWarmupTime = getInteger(sharedPreferences, SETTINGS_CALIBRATION_WARMUP_TIME, 5);
        } else if ("settings_recording_gain".equals(key) && measurementService != null) {
            measurementService.setdBGain(getDouble(sharedPreferences, key, 0));
        }
    }

    private void initCalibration() {
        textStatus.setText(R.string.calibration_status_waiting_for_user_start);
        progressBar_wait_calibration_recording.setProgress(progressBar_wait_calibration_recording.getMax());
        textDeviceLevel.setText(R.string.no_valid_dba_value);
        startButton.setEnabled(true);
        applyButton.setEnabled(false);
        resetButton.setEnabled(false);
        testGainCheckBox.setEnabled(true);
        splBackroundNoise = 0;
        splLoop = 0;
        calibration_step = CALIBRATION_STEP.IDLE;
        freqLeqStats = new ArrayList<>();
    }

    private void onCalibrationStart() {
        textStatus.setText(R.string.calibration_status_waiting_for_start_timer);
        calibration_step = CALIBRATION_STEP.WARMUP;
        // Link measurement service with gui
        if (checkAndAskPermissions()) {
            // Application have right now all permissions
            doBindService();
        }
        startButton.setEnabled(false);
        testGainCheckBox.setEnabled(false);
        timeHandler = new Handler(Looper.getMainLooper(), progressHandler);
        progressHandler.start(defaultWarmupTime * 1000);
    }

    private int getAudioOutput() {
        SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this);
        String value = sharedPref.getString("settings_calibration_audio_output", "STREAM_RING");

        if ("STREAM_VOICE_CALL".equals(value)) {
            return AudioManager.STREAM_VOICE_CALL;
        } else if ("STREAM_SYSTEM".equals(value)) {
            return AudioManager.STREAM_SYSTEM;
        } else if ("STREAM_RING".equals(value)) {
            return AudioManager.STREAM_RING;
        } else if ("STREAM_MUSIC".equals(value)) {
            return AudioManager.STREAM_MUSIC;
        } else if ("STREAM_ALARM".equals(value)) {
            return AudioManager.STREAM_ALARM;
        } else if ("STREAM_NOTIFICATION".equals(value)) {
            return AudioManager.STREAM_NOTIFICATION;
        } else if ("STREAM_DTMF".equals(value)) {
            return AudioManager.STREAM_DTMF;
        } else {
            return AudioManager.STREAM_RING;
        }
    }

    private void playNewTrack() {

        double rms = dbToRms(99 - (splLoop++) * DB_STEP);
        short[] data = makeWhiteNoiseSignal(44100, rms);
        double[] fftCenterFreq = FFTSignalProcessing
                .computeFFTCenterFrequency(AudioProcess.REALTIME_SAMPLE_RATE_LIMITATION);
        FFTSignalProcessing fftSignalProcessing = new FFTSignalProcessing(44100, fftCenterFreq, 44100);
        fftSignalProcessing.addSample(data);
        whiteNoisedB = fftSignalProcessing.computeGlobalLeq();
        freqLeqStats.add(new LinearCalibrationResult(fftSignalProcessing.processSample(true, false, false)));
        LOGGER.info("Emit white noise of " + whiteNoisedB + " dB");
        if (audioTrack == null) {
            audioTrack = new AudioTrack(getAudioOutput(), 44100, AudioFormat.CHANNEL_OUT_MONO,
                    AudioFormat.ENCODING_PCM_16BIT, data.length * (Short.SIZE / 8), AudioTrack.MODE_STATIC);
        } else {
            try {
                audioTrack.pause();
                audioTrack.flush();
            } catch (IllegalStateException ex) {
                // Ignore
            }
        }
        audioTrack.setLoopPoints(0, audioTrack.write(data, 0, data.length), -1);
        audioTrack.play();
    }

    private double dbToRms(double db) {
        return (Math.pow(10, db / 20.) / (Math.pow(10, 90. / 20.))) * 2500;
    }

    private short[] makeWhiteNoiseSignal(int sampleRate, double powerRMS) {
        // Make signal
        double powerPeak = powerRMS * Math.sqrt(2);
        short[] signal = new short[sampleRate * 2];
        for (int s = 0; s < sampleRate * 2; s++) {
            signal[s] = (short) (powerPeak * ((Math.random() - 0.5) * 2));
        }
        return signal;
    }

    private short[] makeSignal(int sampleRate, int signalFrequency, double powerRMS) {
        // Make signal
        double powerPeak = powerRMS * Math.sqrt(2);
        short[] signal = new short[sampleRate * 2];
        for (int s = 0; s < sampleRate * 2; s++) {
            double t = s * (1 / (double) sampleRate);
            signal[s] = (short) (Math.sin(2 * Math.PI * signalFrequency * t) * (powerPeak));
        }
        return signal;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        switch (requestCode) {
        case PERMISSION_RECORD_AUDIO_AND_GPS: {
            // If request is cancelled, the result arrays are empty.
            if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
                doBindService();
            } else {
                // permission denied
                // Ask again
                checkAndAskPermissions();
            }
        }
        }
    }

    private void updateBarChart() {
        BarChart barChart = getBarChart();
        if (barChart == null) {
            return;
        }
        if (freqLeqStats.size() <= 2) {
            return;
        }
        double[] pearsons = computePearson();
        if (pearsons == null) {
            return;
        }

        float YMin = Float.MAX_VALUE;
        float YMax = Float.MIN_VALUE;

        ArrayList<IBarDataSet> dataSets = new ArrayList<IBarDataSet>();

        // Read all white noise values for indexing before usage
        ArrayList<BarEntry> yMeasure = new ArrayList<BarEntry>();
        int idfreq = 0;
        for (double value : pearsons) {
            YMax = Math.max(YMax, (float) value);
            YMin = Math.min(YMin, (float) value);
            yMeasure.add(new BarEntry((float) value, idfreq++));
        }
        BarDataSet freqSet = new BarDataSet(yMeasure, "Pearson's correlation");
        freqSet.setColor(ColorTemplate.COLORFUL_COLORS[0]);
        freqSet.setValueTextColor(Color.WHITE);
        freqSet.setDrawValues(true);
        dataSets.add(freqSet);

        ArrayList<String> xVals = new ArrayList<String>();
        double[] freqs = FFTSignalProcessing
                .computeFFTCenterFrequency(AudioProcess.REALTIME_SAMPLE_RATE_LIMITATION);
        for (double freqValue : freqs) {
            xVals.add(Spectrogram.formatFrequency((int) freqValue));
        }

        // create a data object with the datasets
        BarData data = new BarData(xVals, dataSets);
        barChart.setData(data);
        YAxis yl = barChart.getAxisLeft();
        yl.setAxisMinValue(YMin - 0.1f);
        yl.setAxisMaxValue(YMax + 0.1f);

        barChart.invalidate();
    }

    private void updateLineChart() {
        LineChart lineChart = getLineChart();
        if (lineChart == null) {
            return;
        }
        if (freqLeqStats.isEmpty()) {
            return;
        }
        float YMin = Float.MAX_VALUE;
        float YMax = Float.MIN_VALUE;

        ArrayList<ILineDataSet> dataSets = new ArrayList<ILineDataSet>();

        // Read all white noise values for indexing before usage
        int idStep = 0;
        double referenceLevel = freqLeqStats.get(0).whiteNoiseLevel.getGlobaldBaValue();
        for (LinearCalibrationResult result : freqLeqStats) {
            ArrayList<Entry> yMeasure = new ArrayList<Entry>();
            int idfreq = 0;
            for (LeqStats leqStat : result.measure) {
                float dbLevel = (float) leqStat.getLeqMean();
                YMax = Math.max(YMax, dbLevel);
                YMin = Math.min(YMin, dbLevel);
                yMeasure.add(new Entry(dbLevel, idfreq++));
            }
            LineDataSet freqSet = new LineDataSet(yMeasure, String.format(Locale.getDefault(), "%d dB",
                    (int) (result.whiteNoiseLevel.getGlobaldBaValue() - referenceLevel)));
            freqSet.setColor(ColorTemplate.COLORFUL_COLORS[idStep % ColorTemplate.COLORFUL_COLORS.length]);
            freqSet.setFillColor(ColorTemplate.COLORFUL_COLORS[idStep % ColorTemplate.COLORFUL_COLORS.length]);
            freqSet.setValueTextColor(Color.WHITE);
            freqSet.setCircleColorHole(
                    ColorTemplate.COLORFUL_COLORS[idStep % ColorTemplate.COLORFUL_COLORS.length]);
            freqSet.setDrawValues(false);
            freqSet.setDrawFilled(true);
            freqSet.setFillAlpha(255);
            freqSet.setDrawCircles(true);
            freqSet.setMode(LineDataSet.Mode.LINEAR);
            dataSets.add(freqSet);
            idStep++;
        }

        ArrayList<String> xVals = new ArrayList<String>();
        double[] freqs = FFTSignalProcessing
                .computeFFTCenterFrequency(AudioProcess.REALTIME_SAMPLE_RATE_LIMITATION);
        for (double freqValue : freqs) {
            xVals.add(Spectrogram.formatFrequency((int) freqValue));
        }

        // create a data object with the datasets
        LineData data = new LineData(xVals, dataSets);
        lineChart.setData(data);
        YAxis yl = lineChart.getAxisLeft();
        yl.setAxisMinValue(YMin - 3);
        yl.setAxisMaxValue(YMax + 3);
        lineChart.invalidate();
    }

    private void updateScatterChart() {
        ScatterChart scatterChart = getScatterChart();
        if (scatterChart == null) {
            return;
        }
        if (freqLeqStats.isEmpty()) {
            return;
        }
        float YMin = Float.MAX_VALUE;
        float YMax = Float.MIN_VALUE;
        float XMin = Float.MAX_VALUE;
        float XMax = Float.MIN_VALUE;

        ArrayList<IScatterDataSet> dataSets = new ArrayList<IScatterDataSet>();

        // Frequency count, one dataset by frequency
        int dataSetCount = freqLeqStats.get(freqLeqStats.size() - 1).whiteNoiseLevel.getdBaLevels().length;

        Set<Integer> whiteNoiseValuesSet = new TreeSet<Integer>();

        // Read all white noise values for indexing before usage
        for (LinearCalibrationResult result : freqLeqStats) {
            for (int idFreq = 0; idFreq < result.whiteNoiseLevel.getdBaLevels().length; idFreq++) {
                float dbLevel = result.whiteNoiseLevel.getdBaLevels()[idFreq];
                float referenceDbLevel = freqLeqStats.get(0).whiteNoiseLevel.getdBaLevels()[idFreq];
                whiteNoiseValuesSet.add((int) (dbLevel - referenceDbLevel));
            }
        }
        // Convert into ordered list
        int[] whiteNoiseValues = new int[whiteNoiseValuesSet.size()];

        ArrayList<String> xVals = new ArrayList<String>();
        int i = 0;
        for (int dbValue : whiteNoiseValuesSet) {
            whiteNoiseValues[i++] = dbValue;
            xVals.add(String.valueOf(dbValue));
            XMax = Math.max(XMax, dbValue);
            XMin = Math.min(XMin, dbValue);
        }

        double[] freqs = FFTSignalProcessing
                .computeFFTCenterFrequency(AudioProcess.REALTIME_SAMPLE_RATE_LIMITATION);
        for (int freqId = 0; freqId < dataSetCount; freqId += 1) {
            int freqValue = (int) freqs[freqId];
            if (selectedFrequencies.contains(freqValue)) {
                ArrayList<Entry> yMeasure = new ArrayList<Entry>();
                for (LinearCalibrationResult result : freqLeqStats) {
                    float dbLevel = (float) result.measure[freqId].getLeqMean();
                    float referenceDbLevel = freqLeqStats.get(0).whiteNoiseLevel.getdBaLevels()[freqId];
                    int whiteNoise = (int) (result.whiteNoiseLevel.getdBaLevels()[freqId] - referenceDbLevel);
                    YMax = Math.max(YMax, dbLevel);
                    YMin = Math.min(YMin, dbLevel);
                    yMeasure.add(new Entry(dbLevel, Arrays.binarySearch(whiteNoiseValues, whiteNoise)));
                }
                ScatterDataSet freqSet = new ScatterDataSet(yMeasure, Spectrogram
                        .formatFrequency((int) ThirdOctaveBandsFiltering.STANDARD_FREQUENCIES_REDUCED[freqId]));
                freqSet.setScatterShape(ScatterChart.ScatterShape.CIRCLE);
                freqSet.setColor(ColorTemplate.COLORFUL_COLORS[freqId % ColorTemplate.COLORFUL_COLORS.length]);
                freqSet.setScatterShapeSize(8f);
                freqSet.setValueTextColor(Color.WHITE);
                freqSet.setDrawValues(false);
                dataSets.add(freqSet);
            }
        }

        // create a data object with the datasets
        ScatterData data = new ScatterData(xVals, dataSets);
        scatterChart.setData(data);
        YAxis yl = scatterChart.getAxisLeft();
        yl.setAxisMinValue(YMin - 3);
        yl.setAxisMaxValue(YMax + 3);
        scatterChart.invalidate();
    }

    /**
     * @return Pearson's product-moment correlation coefficients for the measured data
     */
    private double[] computePearson() {
        if (freqLeqStats.size() < 3) {
            return null;
        }
        // Frequency count, one dataset by frequency
        int dataSetCount = freqLeqStats.get(freqLeqStats.size() - 1).whiteNoiseLevel.getdBaLevels().length;
        double[] pearsonCoefficient = new double[dataSetCount];

        StringBuilder log = new StringBuilder();
        for (int freqId = 0; freqId < dataSetCount; freqId++) {
            double[] xValues = new double[freqLeqStats.size()];
            double[] yValues = new double[freqLeqStats.size()];
            int idStep = 0;
            for (LinearCalibrationResult result : freqLeqStats) {
                double dbLevel = result.measure[freqId].getLeqMean();
                double whiteNoise = result.whiteNoiseLevel.getdBaLevels()[freqId];
                xValues[idStep] = whiteNoise;
                yValues[idStep] = dbLevel;
                if (freqId == 0) {
                    LOGGER.info("100 hZ white noise " + whiteNoise + " dB spl: " + dbLevel + " dB");
                }
                idStep++;
            }
            pearsonCoefficient[freqId] = new PearsonsCorrelation().correlation(xValues, yValues);
            if (log.length() == 0) {
                log.append("[");
            } else {
                log.append(", ");
            }
            log.append(String.format(Locale.getDefault(), "%.2f %%", pearsonCoefficient[freqId] * 100));
        }
        log.append("]");
        LOGGER.info("Pearson's values : " + log.toString());
        return pearsonCoefficient;
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        if ((calibration_step == CALIBRATION_STEP.CALIBRATION || calibration_step == CALIBRATION_STEP.WARMUP)
                && AudioProcess.PROP_DELAYED_STANDART_PROCESSING.equals(event.getPropertyName())) {
            // New leq
            AudioProcess.AudioMeasureResult measure = (AudioProcess.AudioMeasureResult) event.getNewValue();
            final double leq;
            // Use global dB value or only the selected frequency band
            leq = measure.getGlobaldBaValue();
            if (calibration_step == CALIBRATION_STEP.CALIBRATION) {
                leqStats.addLeq(leq);
                if (!freqLeqStats.isEmpty() && splBackroundNoise != 0) {
                    freqLeqStats.get(freqLeqStats.size() - 1).pushMeasure(measure.getResult());
                }
            }
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    double leqToShow;
                    if (calibration_step == CALIBRATION_STEP.CALIBRATION) {
                        leqToShow = leqStats.getLeqMean();
                    } else {
                        leqToShow = leq;
                    }
                    textDeviceLevel.setText(String.format(Locale.getDefault(), "%.1f", leqToShow));
                }
            });
        }
    }

    private ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder service) {
            measurementService = ((MeasurementService.LocalBinder) service).getService();
            SharedPreferences sharedPref = PreferenceManager
                    .getDefaultSharedPreferences(CalibrationLinearityActivity.this);
            measurementService.setdBGain(getDouble(sharedPref, "settings_recording_gain", 0));
            measurementService.addPropertyChangeListener(CalibrationLinearityActivity.this);
            if (!measurementService.isRecording()) {
                measurementService.startRecording();
            }
            measurementService.getAudioProcess().setDoFastLeq(false);
            measurementService.getAudioProcess().setDoOneSecondLeq(true);
            measurementService.getAudioProcess().setWeightingA(false);
            measurementService.getAudioProcess().setHannWindowOneSecond(true);
        }

        public void onServiceDisconnected(ComponentName className) {
            // This is called when the connection with the service has been
            // unexpectedly disconnected -- that is, its process crashed.
            // Because it is running in our same process, we should never
            // see this happen.
            measurementService.removePropertyChangeListener(CalibrationLinearityActivity.this);
            measurementService = null;
        }
    };

    void doBindService() {
        // Establish a connection with the service.  We use an explicit
        // class name because we want a specific service implementation that
        // we know will be running in our own process (and thus won't be
        // supporting component replacement by other applications).
        if (!bindService(new Intent(this, MeasurementService.class), mConnection, Context.BIND_AUTO_CREATE)) {
            Toast.makeText(CalibrationLinearityActivity.this, R.string.measurement_service_disconnected,
                    Toast.LENGTH_SHORT).show();
        } else {
            mIsBound = true;
        }
    }

    public void onReset() {
        initCalibration();
    }

    void doUnbindService() {
        if (mIsBound) {
            measurementService.removePropertyChangeListener(this);
            // Detach our existing connection.
            unbindService(mConnection);
            mIsBound = false;
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
        if (audioTrack != null) {
            audioTrack.stop();
        }
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.menu_main, menu);
        return true;
    }

    private void onTimerEnd() {
        if (calibration_step == CALIBRATION_STEP.WARMUP) {
            runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    if (splBackroundNoise == 0) {
                        textStatus.setText(R.string.calibration_status_background_noise);
                    } else {
                        textStatus.setText(getString(R.string.calibration_linear_status_on, whiteNoisedB));
                    }
                }
            });
            // Start calibration
            leqStats = new LeqStats();
            if (!freqLeqStats.isEmpty()) {
                freqLeqStats.get(freqLeqStats.size() - 1).setGlobalMeasure(leqStats);
            }
            progressHandler.start(defaultCalibrationTime * 1000);
            calibration_step = CALIBRATION_STEP.CALIBRATION;
        } else if (calibration_step == CALIBRATION_STEP.CALIBRATION) {
            if (splBackroundNoise != 0) {
                double previousLeq = Double.MAX_VALUE;
                if (freqLeqStats.size() > 1) {
                    previousLeq = freqLeqStats.get(freqLeqStats.size() - 2).globalMeasure.getLeqMean();
                }
                // If the powered calibration reach the background noise or
                // if the last leq is superior than the previous leq
                if (leqStats.getLeqMean() < splBackroundNoise + 3 || leqStats.getLeqMean() > previousLeq) {
                    // Almost reach the background noise, stop calibration
                    calibration_step = CALIBRATION_STEP.END;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            textDeviceLevel.setText(R.string.no_valid_dba_value);
                            textStatus.setText(R.string.calibration_status_end);
                            updateSelectedGraph();
                        }
                    });
                    measurementService.stopRecording();
                    // Remove measurement service
                    doUnbindService();
                    // Stop playing sound
                    audioTrack.stop();
                    // Activate user input
                    if (!testGainCheckBox.isChecked()) {
                        applyButton.setEnabled(true);
                    }
                    resetButton.setEnabled(true);
                } else {
                    calibration_step = CALIBRATION_STEP.WARMUP;
                    runOnUiThread(new Runnable() {
                        @Override
                        public void run() {
                            textDeviceLevel.setText(R.string.no_valid_dba_value);
                            textStatus.setText(getString(R.string.calibration_status_waiting_for_start_timer));
                            updateSelectedGraph();
                        }
                    });
                    playNewTrack();
                    progressHandler.start(defaultWarmupTime * 1000);
                }
            } else {
                // End of calibration of background noise
                calibration_step = CALIBRATION_STEP.WARMUP;
                runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        textDeviceLevel.setText(R.string.no_valid_dba_value);
                        textStatus.setText(getString(R.string.calibration_status_waiting_for_start_timer));
                        updateSelectedGraph();
                    }
                });
                playNewTrack();
                splBackroundNoise = leqStats.getLeqMean();
                progressHandler.start(defaultWarmupTime * 1000);
            }
        }
    }

    /**
     * Manage progress timer
     */
    private static final class ProgressHandler implements Handler.Callback {
        private CalibrationLinearityActivity activity;
        private int delay;
        private long beginTime;

        public ProgressHandler(CalibrationLinearityActivity activity) {
            this.activity = activity;
        }

        public void start(int delayMilliseconds) {
            delay = delayMilliseconds;
            beginTime = SystemClock.elapsedRealtime();
            activity.timeHandler.sendEmptyMessageDelayed(0, COUNTDOWN_STEP_MILLISECOND);
        }

        @Override
        public boolean handleMessage(Message msg) {
            long currentTime = SystemClock.elapsedRealtime();
            int newProg = (int) ((((beginTime + delay) - currentTime) / (float) delay)
                    * activity.progressBar_wait_calibration_recording.getMax());
            activity.progressBar_wait_calibration_recording.setProgress(newProg);
            if (currentTime < beginTime + delay) {
                activity.timeHandler.sendEmptyMessageDelayed(0, COUNTDOWN_STEP_MILLISECOND);
            } else {
                activity.onTimerEnd();
            }
            return true;
        }
    }

    private static final class LinearCalibrationResult {
        private LeqStats[] measure;
        private FFTSignalProcessing.ProcessingResult whiteNoiseLevel;
        private LeqStats globalMeasure;

        public LinearCalibrationResult(FFTSignalProcessing.ProcessingResult whiteNoiseLevel) {
            this.whiteNoiseLevel = whiteNoiseLevel;

        }

        public void setGlobalMeasure(LeqStats globalMeasure) {
            this.globalMeasure = globalMeasure;
        }

        public void pushMeasure(FFTSignalProcessing.ProcessingResult measure) {
            float[] measureLevels = measure.getdBaLevels();
            if (this.measure == null) {
                this.measure = new LeqStats[measure.getdBaLevels().length];
                for (int idFreq = 0; idFreq < this.measure.length; idFreq++) {
                    this.measure[idFreq] = new LeqStats();
                }
            }
            for (int idFreq = 0; idFreq < this.measure.length; idFreq++) {
                this.measure[idFreq].addLeq(measureLevels[idFreq]);
            }
        }
    }
}