/* * 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 [email protected] */ package org.noise_planet.noisecapture; import android.app.AlertDialog; import android.app.ProgressDialog; 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.content.res.Resources; import android.graphics.Color; import android.location.Location; import android.os.Bundle; import android.os.IBinder; import android.os.SystemClock; import android.preference.PreferenceManager; import android.support.design.widget.TabLayout; import android.view.MotionEvent; import android.view.View; import android.view.WindowManager; import android.view.animation.AnimationUtils; import android.webkit.WebView; import android.widget.Chronometer; import android.widget.CompoundButton; import android.widget.ImageButton; import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import com.github.mikephil.charting.charts.BarChart; import com.github.mikephil.charting.charts.HorizontalBarChart; import com.github.mikephil.charting.components.Legend; import com.github.mikephil.charting.components.XAxis; import com.github.mikephil.charting.components.XAxis.XAxisPosition; 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.formatter.YAxisValueFormatter; import com.github.mikephil.charting.interfaces.datasets.IBarDataSet; import org.orbisgis.sos.AcousticIndicators; import org.orbisgis.sos.LeqStats; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.text.DecimalFormat; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; public class MeasurementActivity extends MainActivity implements SharedPreferences.OnSharedPreferenceChangeListener, MapFragment.MapFragmentAvailableListener { //public ImageButton buttonrecord; //public ImageButton buttoncancel; private AtomicBoolean isComputingMovingLeq = new AtomicBoolean(false); // For the Charts protected HorizontalBarChart mChart; // VUMETER representation private DoProcessing doProcessing; private ImageButton buttonrecord; private ImageButton buttonPause; private ViewPagerExt viewPager; private static final int PAGE_SPECTRUM = 0; private static final int PAGE_SPECTROGRAM = 1; private static final int PAGE_MAP = 2; // From this accuracy the location hint color is orange private static final float APROXIMATE_LOCATION_ACCURACY = 10.f; private static final double MINIMAL_DISTANCE_RESTORE_MAP = 3.f; private static final int MAX_LOCATIONS_RESTORE_MAP = 500; // Other resources private boolean mIsBound = false; private long lastMapLocationRefresh = 0; // Map user location refresh rate in milliseconds private static final long REFRESH_MAP_LOCATION_RATE = 1000; private AtomicBoolean chronometerWaitingToStart = new AtomicBoolean(false); public final static double MIN_SHOWN_DBA_VALUE = 20; public final static double MAX_SHOWN_DBA_VALUE = 120; private static final int DEFAULT_MINIMAL_LEQ = 1; private static final int DEFAULT_DELETE_LEQ_ON_PAUSE = 0; private boolean hasMaximalMeasurementTime; private int maximalMeasurementTime = 0; // NoiseCapture will switch from Hann window to Rectangular window if the measurement delay // is superior than this value in ms private final static long SWITCH_TO_FAST_RECTANGULAR_DELAY = 1500; private static final String LOG_SCALE_SETTING = "settings_spectrogram_logscalemode"; private static final String DELETE_LEQ_ON_PAUSE_SETTING = "settings_delete_leq_on_pause"; private static final String HAS_MAXIMAL_MEASURE_TIME_SETTING = "settings_recording"; private static final String MAXIMAL_MEASURE_TIME_SETTING = "settings_recording_duration"; private static final String SETTINGS_MEASUREMENT_DISPLAY_WINDOW = "settings_measurement_display_window"; private static final int DEFAULT_MAXIMAL_MEASURE_TIME_SETTING = 10; public int getRecordId() { return measurementService.getRecordId(); } public void initComponents() { Spectrogram spectrogram = getSpectrogram(); if(spectrogram != null) { spectrogram.setTimeStep(measurementService.getAudioProcess().getFFTDelay()); } setData(0); updateSpectrumGUI(); } private Spectrogram getSpectrogram() { View view = ((ViewPagerAdapter)viewPager.getAdapter()).getItem(PAGE_SPECTROGRAM).getView(); if(view != null) { return (Spectrogram) view.findViewById(R.id.spectrogram_view); } else { return null; } } private BarChart getSpectrum() { View view = ((ViewPagerAdapter)viewPager.getAdapter()).getItem(PAGE_SPECTRUM).getView(); if(view != null) { return (BarChart) view.findViewById(R.id.spectrumChart); } else { return null; } } private WebView getMap() { View view = ((ViewPagerAdapter)viewPager.getAdapter()).getItem(PAGE_MAP).getView(); if(view != null) { return (WebView) view.findViewById(R.id.measurement_webmapview); } else { return null; } } private MapFragment getMapControler() { return (MapFragment) (((ViewPagerAdapter)viewPager.getAdapter()).getItem(PAGE_MAP)); } private void setupViewPager(ViewPagerExt viewPager) { ViewPagerAdapter adapter = new ViewPagerAdapter(getSupportFragmentManager()); adapter.addFragment(new MeasurementSpectrumFragment(), getString(R.string.measurement_tab_spectrum)); adapter.addFragment(new MeasurementSpectrogramFragment(), getString(R.string.measurement_tab_spectrogram)); MapFragment mapFragment = new MapFragment(); mapFragment.setMapFragmentAvailableListener(this); adapter.addFragment(mapFragment, getString(R.string.measurement_tab_map)); // Give full control of swipe to the map instead of the tabs controller. viewPager.addIgnoredTab(2); viewPager.setAdapter(adapter); } @Override public void onMapFragmentAvailable(MapFragment mapFragment) { mapFragment.loadUrl("file:///android_asset/html/map_measurement.html"); } @Override public void onPageLoaded(MapFragment mapFragment) { // Nothing to do } @Override public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) { if(LOG_SCALE_SETTING.equals(key)) { Spectrogram spectrogram = getSpectrogram(); if(spectrogram != null) { spectrogram.setScaleMode(sharedPreferences.getBoolean(key, true) ? Spectrogram.SCALE_MODE.SCALE_LOG : Spectrogram.SCALE_MODE.SCALE_LINEAR); } } else if(DELETE_LEQ_ON_PAUSE_SETTING.equals(key)) { measurementService.setDeletedLeqOnPause(getInteger(sharedPreferences,key, DEFAULT_DELETE_LEQ_ON_PAUSE)); } else if(HAS_MAXIMAL_MEASURE_TIME_SETTING.equals(key)) { hasMaximalMeasurementTime = sharedPreferences.getBoolean(HAS_MAXIMAL_MEASURE_TIME_SETTING, false); } else if(MAXIMAL_MEASURE_TIME_SETTING.equals(key)) { maximalMeasurementTime = getInteger(sharedPreferences,MAXIMAL_MEASURE_TIME_SETTING, DEFAULT_MAXIMAL_MEASURE_TIME_SETTING); } else if("settings_recording_gain".equals(key) && measurementService != null) { measurementService.setdBGain(getDouble(sharedPreferences, key, 0)); } else if(SETTINGS_MEASUREMENT_DISPLAY_WINDOW.equals(key) && measurementService != null) { measurementService.getAudioProcess().setHannWindowFast(sharedPreferences.getString(SETTINGS_MEASUREMENT_DISPLAY_WINDOW, "RECTANGULAR").equals("HANN")); if(BuildConfig.DEBUG) { System.out.println("Switch to Rectangular window"); } } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); if(checkAndAskPermissions()) { // Application have right now all permissions doBindService(); } setContentView(R.layout.activity_measurement); initDrawer(); // Check if the dialog box (for caution) must be displayed // Depending of the settings SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); sharedPref.registerOnSharedPreferenceChangeListener(this); Boolean CheckNbRunSettings = sharedPref.getBoolean("settings_caution", true); hasMaximalMeasurementTime = sharedPref.getBoolean(HAS_MAXIMAL_MEASURE_TIME_SETTING, false); maximalMeasurementTime = getInteger(sharedPref, MAXIMAL_MEASURE_TIME_SETTING, DEFAULT_MAXIMAL_MEASURE_TIME_SETTING); if (CheckNbRunSettings && CheckNbRun("NbRunMaxCaution", getResources().getInteger(R.integer .NbRunMaxCaution))) { new AlertDialog.Builder(this).setTitle(R.string.title_caution) .setMessage(R.string.text_caution) .setNeutralButton(R.string.text_OK, null) .setIcon(android.R.drawable.ic_dialog_alert) .show(); } // Enabled/disabled buttons buttonPause = (ImageButton) findViewById(R.id.pauseBtn); buttonPause.setEnabled(false); // To start a record (test mode) buttonrecord = (ImageButton) findViewById(R.id.recordBtn); buttonrecord.setImageResource(R.drawable.button_record_normal); buttonrecord.setEnabled(true); // Actions on record button doProcessing = new DoProcessing(this); buttonrecord.setOnClickListener(doProcessing); // Action on cancel button (during recording) buttonPause.setOnClickListener(onButtonPause); buttonPause.setOnTouchListener(new ToggleButtonTouch(this)); // Init tabs (Spectrum, Spectrogram, Map) viewPager = (ViewPagerExt) findViewById(R.id.measurement_viewpager); setupViewPager(viewPager); TabLayout tabLayout = (TabLayout) findViewById(R.id.measurement_tabs); tabLayout.setupWithViewPager(viewPager); // Select map by default viewPager.setCurrentItem(2); // Instantaneous sound level VUMETER // Stacked bars are used for represented Min, Current and Max values // Horizontal barchart LinearLayout graphLayouts = (LinearLayout) findViewById(R.id.graph_components_layout); mChart = (HorizontalBarChart) findViewById(R.id.vumeter); mChart.setTouchEnabled(false); initVueMeter(); setData(0); // Legend: hide all Legend lv = mChart.getLegend(); lv.setEnabled(false); // Hide legend } @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(); } } } } @Override protected void onStart() { super.onStart(); checkTransferResults(); } private View.OnClickListener onButtonPause = new View.OnClickListener() { @Override public void onClick(View view) { // Stop measurement without waiting for the end of processing measurementService.setPause(!measurementService.isPaused()); chronometerWaitingToStart.set(true); MeasurementActivity.this.runOnUiThread(new UpdateText(MeasurementActivity.this)); } }; private static class ToggleButtonTouch implements View.OnTouchListener { MeasurementActivity measurement; public ToggleButtonTouch(MeasurementActivity measurement) { this.measurement = measurement; } @Override public boolean onTouch(View v, MotionEvent event) { if(event.getAction() == MotionEvent.ACTION_DOWN) { v.setPressed(!measurement.measurementService.isPaused()); v.performClick(); } return true; } } // Init RNE Pie Chart public void initVueMeter(){ mChart.setDrawBarShadow(false); mChart.setDescription(""); mChart.setPinchZoom(false); mChart.setDrawGridBackground(false); mChart.setMaxVisibleValueCount(0); mChart.setScaleXEnabled(false); // Disable scaling on the X-axis // XAxis parameters: hide all XAxis xlv = mChart.getXAxis(); xlv.setPosition(XAxisPosition.BOTTOM); xlv.setDrawAxisLine(false); xlv.setDrawGridLines(false); xlv.setDrawLabels(false); // YAxis parameters (left): main axis for dB values representation YAxis ylv = mChart.getAxisLeft(); ylv.setDrawAxisLine(false); ylv.setDrawGridLines(true); ylv.setAxisMaxValue(110.f); ylv.setStartAtZero(true); ylv.setTextColor(Color.WHITE); ylv.setGridColor(Color.WHITE); ylv.setValueFormatter(new dBValueFormatter()); // YAxis parameters (right): no axis, hide all YAxis yrv = mChart.getAxisRight(); yrv.setEnabled(false); //return true; } // Fix the format of the dB Axis of the vumeter public class dBValueFormatter implements YAxisValueFormatter { private DecimalFormat mFormat; public dBValueFormatter() { mFormat = new DecimalFormat("###,###,##0"); // use one decimal } @Override public String getFormattedValue(float value, YAxis yAxis) { return mFormat.format(value); } } // Generate artificial 1 data (sound level) for vumeter representation private void setData(double val) { ArrayList<String> xVals = new ArrayList<String>(); xVals.add(""); ArrayList<BarEntry> yVals1 = new ArrayList<BarEntry>(); yVals1.add(new BarEntry((float)val, 0)); BarDataSet set1 = new BarDataSet(yVals1, "DataSet"); //set1.setBarSpacePercent(35f); //set1.setColor(Color.rgb(0, 153, 204)); int nc=getNEcatColors(val); // Choose the color category in function of the sound level set1.setColor(NE_COLORS[nc]); ArrayList<IBarDataSet> dataSets = new ArrayList<IBarDataSet>(); dataSets.add(set1); BarData data = new BarData(xVals, dataSets); data.setValueTextSize(10f); mChart.setData(data); mChart.invalidate(); // refresh } private void updateSpectrumGUI() { ArrayList<String> xVals = new ArrayList<String>(); ArrayList<BarEntry> yVals1 = new ArrayList<BarEntry>(); double[] freqLabels = measurementService.getAudioProcess().getRealtimeCenterFrequency(); float[] freqValues = measurementService.getAudioProcess().getThirdOctaveFrequencySPL(); for(int idfreq =0; idfreq < freqLabels.length; idfreq++) { xVals.add(Spectrogram.formatFrequency((int)freqLabels[idfreq])); // Sum values // Compute frequency range covered by frequency yVals1.add(new BarEntry(new float[] {freqValues[idfreq]}, idfreq)); } BarDataSet set1 = new BarDataSet(yVals1, "DataSet"); set1.setColor(Color.rgb(102, 178, 255)); set1.setStackLabels(new String[]{ "SL" }); ArrayList<IBarDataSet> dataSets = new ArrayList<IBarDataSet>(); dataSets.add(set1); BarData data = new BarData(xVals, dataSets); data.setValueTextSize(10f); BarChart sChart = getSpectrum(); if(sChart != null){ sChart.setData(data); sChart.setPadding(0, 0, 0, 0); sChart.setViewPortOffsets(0,0,0,0); sChart.invalidate(); // refresh } } private static class WaitEndOfProcessing implements Runnable { private MeasurementActivity activity; private ProgressDialog processingDialog; public WaitEndOfProcessing(MeasurementActivity activity, ProgressDialog processingDialog) { this.activity = activity; this.processingDialog = processingDialog; } @Override public void run() { int lastShownProgress = 0; while(activity.measurementService.getAudioProcess().getCurrentState() != AudioProcess.STATE.CLOSED && !activity.measurementService.isCanceled()) { try { Thread.sleep(200); int progress = activity.measurementService.getAudioProcess().getRemainingNotProcessSamples(); if(progress != lastShownProgress) { lastShownProgress = progress; activity.runOnUiThread(new SetDialogMessage(processingDialog, activity.getResources().getString(R.string.measurement_processlastsamples, lastShownProgress))); } } catch (InterruptedException ex) { return; } } // If canceled or ended before 1s if(!activity.measurementService.isCanceled() && activity.measurementService.getLeqAdded() != 0) { processingDialog.dismiss(); // Goto the Results activity activity.runOnUiThread(new Runnable() { @Override public void run() { Intent ir = new Intent(activity.getApplicationContext(), CommentActivity.class); ir.putExtra(MainActivity.RESULTS_RECORD_ID, activity.measurementService.getRecordId()); ir.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); activity.startActivity(ir); activity.finish(); } }); } else { // No recordId available, restart measurement activity activity.runOnUiThread(new Runnable() { @Override public void run() { processingDialog.dismiss(); Intent im = new Intent(activity, MeasurementActivity.class); im.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); activity.startActivity(im); activity.finish(); }}); } } } private static class SetDialogMessage implements Runnable { private ProgressDialog dialog; private String message; public SetDialogMessage(ProgressDialog dialog, String message) { this.dialog = dialog; this.message = message; } @Override public void run() { dialog.setMessage(message); } } private void initGuiState() { if(measurementService == null) { // measurementService is required return; } // Update buttons: cancel enabled; record button to stop; // Show start measure hint TextView overlayMessage = (TextView) findViewById(R.id.textView_message_overlay); initComponents(); if (measurementService.isStoring()) { overlayMessage.setVisibility(View.INVISIBLE); buttonPause.setEnabled(true); buttonrecord.setImageResource(R.drawable.button_record_pressed); getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // Start chronometer chronometerWaitingToStart.set(true); MeasurementManager measurementManager = new MeasurementManager(this); MapFragment mapFragment = getMapControler(); if(mapFragment != null) { List<MeasurementManager.LeqBatch> locations = measurementManager .getRecordLocations(measurementService.getRecordId(), true, MAX_LOCATIONS_RESTORE_MAP, null, MINIMAL_DISTANCE_RESTORE_MAP); mapFragment.cleanMeasurementPoints(); for(MeasurementManager.LeqBatch location : locations) { Storage.Leq leq = location.getLeq(); String htmlColor = MeasurementExport.getColorFromLevel (location.computeGlobalLeq()); mapFragment.addMeasurement(new MapFragment.LatLng(leq.getLatitude(), leq .getLongitude()), htmlColor); } } } else { getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); // Enabled/disabled buttons after measurement buttonPause.setEnabled(false); buttonrecord.setImageResource(R.drawable.button_record); buttonrecord.setEnabled(true); // Stop and reset chronometer Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer_recording_time); chronometer.stop(); overlayMessage.setText(R.string.no_data_text_description); overlayMessage.setVisibility(View.VISIBLE); } } private static class DoProcessing implements CompoundButton.OnClickListener, PropertyChangeListener { private MeasurementActivity activity; public DoProcessing(MeasurementActivity activity) { this.activity = activity; } @Override public void propertyChange(PropertyChangeEvent event) { if(AudioProcess.PROP_MOVING_SPECTRUM.equals(event.getPropertyName())) { AudioProcess.AudioMeasureResult measure = (AudioProcess.AudioMeasureResult) event.getNewValue(); // Realtime audio processing Spectrogram spectrogram = activity.getSpectrogram(); if(spectrogram != null) { spectrogram.addTimeStep(measure.getResult().getFftResult(), activity.measurementService.getAudioProcess().getFFTFreqArrayStep()); } if(activity.isComputingMovingLeq.compareAndSet(false, true) && activity .measurementService.isRecording()) { activity.runOnUiThread(new UpdateText(activity)); } if(activity.measurementService.getAudioProcess().isHannWindowFast() && activity.measurementService.getAudioProcess().getFastNotProcessedMilliseconds() > SWITCH_TO_FAST_RECTANGULAR_DELAY) { activity.runOnUiThread(new Runnable() { @Override public void run() { SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(activity.getApplicationContext()); SharedPreferences.Editor editor = sharedPref.edit(); editor.putString(MeasurementActivity.SETTINGS_MEASUREMENT_DISPLAY_WINDOW, "RECTANGULAR"); editor.apply(); } }); } } else if(AudioProcess.PROP_STATE_CHANGED.equals(event.getPropertyName())) { if (AudioProcess.STATE.CLOSED.equals(event.getNewValue())) { activity.runOnUiThread(new UpdateText(activity)); } } else if(AudioProcess.PROP_DELAYED_STANDART_PROCESSING.equals(event.getPropertyName())) { if(activity.hasMaximalMeasurementTime && activity.measurementService.isStoring() && activity.maximalMeasurementTime <= activity.measurementService.getLeqAdded()) { activity.runOnUiThread(new Runnable() { @Override public void run() { activity.buttonrecord.performClick(); } }); } } else if(MeasurementService.PROP_NEW_MEASUREMENT.equals(event.getPropertyName())) { if(BuildConfig.DEBUG) { System.out.println("Measure offset "+activity.measurementService.getAudioProcess().getFastNotProcessedMilliseconds()+" ms"); } MapFragment mapFragment = activity.getMapControler(); if(mapFragment != null) { final MeasurementService.MeasurementEventObject measurement = (MeasurementService.MeasurementEventObject) event.getNewValue(); if(!(Double.compare(measurement.leq.getLatitude(), 0) == 0 && Double.compare(measurement.leq.getLongitude(), 0) == 0)) { activity.runOnUiThread(new Runnable() { @Override public void run() { String htmlColor = MeasurementExport.getColorFromLevel (measurement.measure.getGlobaldBaValue()); activity.getMapControler().addMeasurement(new MapFragment.LatLng(measurement.leq.getLatitude(), measurement.leq.getLongitude()), htmlColor); } }); } } } } @Override public void onClick(View v) { Resources resources = activity.getResources(); ImageButton buttonPause= (ImageButton) activity.findViewById(R.id.pauseBtn); buttonPause.setEnabled(true); ImageButton buttonrecord= (ImageButton) activity.findViewById(R.id.recordBtn); if (!activity.measurementService.isStoring()) { // Start recording buttonrecord.setImageResource(R.drawable.button_record_pressed); buttonrecord.setEnabled(false); activity.measurementService.startStorage(); // Force service to stay alive even if this activity is killed (Foreground service) activity.startService(new Intent(activity, MeasurementService.class)); } else { // Stop measurement activity.measurementService.stopRecording(); // Show computing progress dialog ProgressDialog myDialog = new ProgressDialog(activity); if (!activity.measurementService.isCanceled()) { myDialog.setMessage(resources.getString(R.string.measurement_processlastsamples, activity.measurementService.getAudioProcess().getRemainingNotProcessSamples())); myDialog.setCancelable(false); myDialog.setButton(DialogInterface.BUTTON_NEGATIVE, resources.getText(R.string.text_CANCEL_data_transfer), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); activity.measurementService.cancel(); } }); myDialog.show(); } // Launch processing end activity new Thread(new WaitEndOfProcessing(activity, myDialog)).start(); } activity.initGuiState(); } } private final static class UpdateText implements Runnable { MeasurementActivity activity; private static void formatdBA(double dbAValue, TextView textView) { if(dbAValue > MIN_SHOWN_DBA_VALUE && dbAValue < MAX_SHOWN_DBA_VALUE) { textView.setText(String.format(" %.1f", dbAValue)); } else { textView.setText(R.string.no_valid_dba_value); } } private UpdateText(MeasurementActivity activity) { this.activity = activity; } @Override public void run() { try { if(activity.measurementService.isRecording()) { int seconds = activity.measurementService.getLeqAdded(); if(seconds >= MeasurementActivity.DEFAULT_MINIMAL_LEQ && !activity.buttonrecord.isEnabled()) { activity.buttonrecord.setEnabled(true); } Chronometer chronometer = (Chronometer) activity .findViewById(R.id.chronometer_recording_time); if (activity.chronometerWaitingToStart.getAndSet(false)) { chronometer.setBase(SystemClock.elapsedRealtime() - seconds * 1000); TextView overlayMessage = (TextView) activity.findViewById(R.id.textView_message_overlay); if(activity.measurementService.isPaused()) { chronometer.stop(); chronometer.startAnimation(AnimationUtils.loadAnimation(activity, R.anim.pause_anim)); overlayMessage.setText(R.string.measurement_pause); } else { chronometer.clearAnimation(); chronometer.start(); overlayMessage.setText(""); } } //Update accuracy hint final TextView accuracyText = (TextView) activity.findViewById(R.id.textView_value_gps_precision); final ImageView accuracyImageHint = (ImageView) activity.findViewById(R.id.imageView_value_gps_precision); Location location = activity.measurementService.getLastLocation(); if(location != null) { float lastPrecision = location.getAccuracy(); if (lastPrecision < APROXIMATE_LOCATION_ACCURACY) { accuracyImageHint.setImageResource(R.drawable.gps_fixed); accuracyText.setText(activity.getString(R.string.gps_hint_precision, (int)lastPrecision)); } else { accuracyImageHint.setImageResource(R.drawable.gps_not_fixed); accuracyText.setText(activity.getString(R.string.gps_hint_precision, (int)lastPrecision)); } if (accuracyImageHint.getVisibility() == View.INVISIBLE) { accuracyImageHint.setVisibility(View.VISIBLE); } long now = System.currentTimeMillis(); if(now - activity.lastMapLocationRefresh >= REFRESH_MAP_LOCATION_RATE) { activity.getMapControler().updateLocationMarker(new MapFragment.LatLng(location.getLatitude(), location.getLongitude()), location.getAccuracy()); activity.lastMapLocationRefresh = now; } } else { accuracyImageHint.setImageResource(R.drawable.gps_off); accuracyText.setText(R.string.no_gps_hint); } // Update current location of user final double leq = activity.measurementService.getAudioProcess().getLeq(false); activity.setData(activity.measurementService.getAudioProcess().getLeq(false)); // Change the text and the textcolor in the corresponding textview // for the Leqi value LeqStats leqStats = activity.measurementService.getFastLeqStats(); final TextView mTextView = (TextView) activity.findViewById(R.id.textView_value_SL_i); formatdBA(leq, mTextView); if(activity.measurementService.getLeqAdded() != 0) { // Stats are only available if the recording of previous leq are activated final TextView valueMin = (TextView) activity.findViewById(R.id .textView_value_Min_i); formatdBA(leqStats.getLeqMin(), valueMin); final TextView valueMax = (TextView) activity.findViewById(R.id .textView_value_Max_i); formatdBA(leqStats.getLeqMax(), valueMax); final TextView valueMean = (TextView) activity.findViewById(R.id .textView_value_Mean_i); formatdBA(leqStats.getLeqMean(), valueMean); } int nc = MeasurementActivity.getNEcatColors(leq); // Choose the color category in // function of the sound level mTextView.setTextColor(activity.NE_COLORS[nc]); // Spectrum data activity.updateSpectrumGUI(); } else { activity.initGuiState(); } // Debug processing time } finally { activity.isComputingMovingLeq.set(false); } } } private MeasurementService measurementService; private ServiceConnection mConnection = new ServiceConnection() { public void onServiceConnected(ComponentName className, IBinder service) { // This is called when the connection with the service has been // established, giving us the service object we can use to // interact with the service. Because we have bound to a explicit // service that we know is running in our own process, we can // cast its IBinder to a concrete class and directly access it. measurementService = ((MeasurementService.LocalBinder)service).getService(); measurementService.setMinimalLeqCount(MeasurementActivity.DEFAULT_MINIMAL_LEQ); SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(MeasurementActivity.this); measurementService.setDeletedLeqOnPause(getInteger(sharedPref,MeasurementActivity.DELETE_LEQ_ON_PAUSE_SETTING, MeasurementActivity.DEFAULT_DELETE_LEQ_ON_PAUSE)); measurementService.setdBGain( getDouble(sharedPref,"settings_recording_gain", 0)); // Init gui if recording is ongoing measurementService.addPropertyChangeListener(doProcessing); if(!measurementService.isRecording()) { measurementService.startRecording(); } measurementService.getAudioProcess().setDoFastLeq(true); measurementService.getAudioProcess().setDoOneSecondLeq(true); measurementService.getAudioProcess().setWeightingA(true); measurementService.getAudioProcess().setHannWindowOneSecond(true); measurementService.getAudioProcess().setHannWindowFast(sharedPref.getString(SETTINGS_MEASUREMENT_DISPLAY_WINDOW, "RECTANGULAR").equals("HANN")); initGuiState(); } 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(doProcessing); 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(MeasurementActivity.this, R.string.measurement_service_disconnected, Toast.LENGTH_SHORT).show(); } else { mIsBound = true; } } void doUnbindService() { if (mIsBound && measurementService != null) { measurementService.removePropertyChangeListener(doProcessing); // Detach our existing connection. unbindService(mConnection); mIsBound = false; } } @Override protected void onRestart() { super.onRestart(); doBindService(); if(measurementService != null) { initGuiState(); measurementService.getAudioProcess().setDoFastLeq(true); } } @Override protected void onStop() { super.onStop(); if(measurementService != null) { // Disconnect listener from measurement if(measurementService.isStoring()) { // Disable 125ms processing as it is only used for display measurementService.getAudioProcess().setDoFastLeq(false); } doUnbindService(); } } @Override protected void onDestroy() { super.onDestroy(); doUnbindService(); } }