Android Studio - Measure Decibels / Sound Level from a Fragment

Viewed 278

I apologize if my code seems messy, I am fairly new to the whole app development game. I am trying to build an app as a university project, where one of its many features is detecting the surrounding noise level of the user in decibels, in order to determine if he / she is socializing with other people (silly, I know). I am working with a Bottom Navigation Menu, and 5 different Fragments, one of which contains a button which will start the sound measurement process.

This is the aforementioned Fragment's code:

package com.example.ssresilience;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Intent;
import android.media.MediaRecorder;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentResultListener;

import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;

import org.w3c.dom.Text;

import java.io.IOException;

public class MeasureFragment extends Fragment {

    // TODO: Rename parameter arguments, choose names that match
    // the fragment initialization parameters, e.g. ARG_ITEM_NUMBER
    private static final String ARG_PARAM1 = "param1";
    private static final String ARG_PARAM2 = "param2";

    // TODO: Rename and change types of parameters
    private String mParam1;
    private String mParam2;
    private String goal;
    private Button fg_measure_button_go;
    private MediaRecorder mRecorder = null;

    public MeasureFragment() {
        // Required empty public constructor
    }

    /**
     * Use this factory method to create a new instance of
     * this fragment using the provided parameters.
     *
     * @param param1 Parameter 1.
     * @param param2 Parameter 2.
     * @return A new instance of fragment MeasureFragment.
     */
    // TODO: Rename and change types and number of parameters
    public static MeasureFragment newInstance(String param1, String param2) {
        MeasureFragment fragment = new MeasureFragment();
        Bundle args = new Bundle();
        args.putString(ARG_PARAM1, param1);
        args.putString(ARG_PARAM2, param2);
        fragment.setArguments(args);
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        if (getArguments() != null) {
            mParam1 = getArguments().getString(ARG_PARAM1);
            mParam2 = getArguments().getString(ARG_PARAM2);
        }
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        // Inflate the layout for this fragment
        View rootView = inflater.inflate(R.layout.fragment_measure, container, false);

        // Retrieve the selected Goal from the DataSite Class
        goal = ((DataSite)getActivity().getApplication()).getGoal();

        // Initialize the Fragment's TextViews
        TextView fg_measure_title = (TextView)rootView.findViewById(R.id.fg_measure_title);
        TextView fg_measure_text = (TextView)rootView.findViewById(R.id.fg_measure_text);
        Button fg_measure_button_go = (Button)rootView.findViewById(R.id.fg_measure_button_go);
        fg_measure_button_go.setOnClickListener(this::onClick);

        // Fill the Fragment's TextViews according to the selected Goal
        if (goal != null) {
            if (goal.equals("socialize")) {
                fg_measure_title.setText("Socialize More");
                fg_measure_text.setText("Measure the level of noise of your surrounding environment, in order to let the app know you're socializing with people.");
            }
            if (goal.equals("study")) {
                fg_measure_title.setText("Enhance Study Motives");
                fg_measure_text.setText("Allow the app to calculate and determine your stress level, by taking the Stress Test. Remember, a higher stress level leads to lower study motives.");
            }
            if (goal.equals("exercise")) {
                fg_measure_title.setText("Physical Exercise");
                fg_measure_text.setText("Measure the level of your current physical activity state by utilizing your device's accelerometer sensor.");
            }
        }

        return rootView;
    }

    @SuppressLint({"UseCompatLoadingForDrawables", "ResourceAsColor", "UseCompatLoadingForColorStateLists", "NonConstantResourceId", "SetTextI18n"})
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.fg_measure_button_go:
                ((SoundMeter) getActivity().getApplication()).getAmplitude();
                break;
        }
    }
}

At the bottom of the file, there's the line of code ((SoundMeter) getActivity().getApplication()).getAmplitude();. This calls the getAmplitude() method from the SoundMeter activity.

Below is the code for SoundMeter.java. I found this while searching online, and apparently it works for many users.

package com.example.ssresilience;

import android.app.Application;
import android.media.MediaRecorder;

import java.io.IOException;

public class SoundMeter extends Application {
    static final private double EMA_FILTER = 0.6;

    private MediaRecorder mRecorder = null;
    private double mEMA = 0.0;

    public void start()  {
        if (mRecorder == null) {
            mRecorder = new MediaRecorder();
            mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
            mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
            mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
            mRecorder.setOutputFile("/dev/null/");

            try {
                mRecorder.prepare();
            } catch (IllegalStateException | IOException e) {
                e.printStackTrace();
            }
            mRecorder.start();
            mEMA = 0.0;
        }
    }

    public void stop() {
        if (mRecorder != null) {
            mRecorder.stop();
            mRecorder.release();
            mRecorder = null;
        }
    }

    public double getTheAmplitude(){
        if(mRecorder != null)
            return (mRecorder.getMaxAmplitude());
        else
            return 1;
    }
    public double getAmplitude() {
        if (mRecorder != null)
            return  (mRecorder.getMaxAmplitude()/2700.0);
        else
            return 0;

    }

    public double getAmplitudeEMA() {
        double amp = getAmplitude();
        mEMA = EMA_FILTER * amp + (1.0 - EMA_FILTER) * mEMA;
        return mEMA;
    }
}

Lastly, in my AndroidManifest.xml file, I have included these 2 lines of code:

<uses-permission android:name="android.permission.RECORD_AUDIO" />

and

<activity 
    android:name=".SoundMeter"
    tools:ignore="Instantiatable" />
<activity

The problem is: as soon as I press the button in MeasureFragment, which calls the getAmplitude() method, the app instantly crashes, without any output in the Logcat. I can confirm that the technique I use to call the Activity method from the current Fragment (((SoundMeter) getActivity().getApplication()).getAmplitude();) is not the issue, since I use it in other occasions as well, and it works flawlessly.

I am aware I need to store the retrieved value to a variable, but in order to do that I need to get the process going first, and I cannot do that when the app crashes.

Any idea as to what I am doing wrong, or something that can be changed in the code?

Thank you in advance!

0 Answers
Related