org.akvo.caddisfly.sensor.colorimetry.liquid.ColorimetryLiquidActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.akvo.caddisfly.sensor.colorimetry.liquid.ColorimetryLiquidActivity.java

Source

/*
 * Copyright (C) Stichting Akvo (Akvo Foundation)
 *
 * This file is part of Akvo Caddisfly.
 *
 * Akvo Caddisfly is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Akvo Caddisfly 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 Akvo Caddisfly. If not, see <http://www.gnu.org/licenses/>.
 */

package org.akvo.caddisfly.sensor.colorimetry.liquid;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.hardware.Sensor;
import android.hardware.SensorManager;
import android.os.AsyncTask;
import android.os.BatteryManager;
import android.os.Bundle;
import android.os.Handler;
import android.os.PowerManager;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.SparseArray;
import android.view.Display;
import android.view.MenuItem;
import android.view.Surface;
import android.view.WindowManager;
import android.widget.TextView;

import org.akvo.caddisfly.R;
import org.akvo.caddisfly.app.CaddisflyApp;
import org.akvo.caddisfly.helper.ShakeDetector;
import org.akvo.caddisfly.helper.SoundPoolPlayer;
import org.akvo.caddisfly.helper.SwatchHelper;
import org.akvo.caddisfly.helper.TestConfigHelper;
import org.akvo.caddisfly.model.ColorInfo;
import org.akvo.caddisfly.model.Result;
import org.akvo.caddisfly.model.ResultDetail;
import org.akvo.caddisfly.model.Swatch;
import org.akvo.caddisfly.model.TestInfo;
import org.akvo.caddisfly.preference.AppPreferences;
import org.akvo.caddisfly.sensor.CameraDialog;
import org.akvo.caddisfly.sensor.CameraDialogFragment;
import org.akvo.caddisfly.sensor.SensorConstants;
import org.akvo.caddisfly.sensor.colorimetry.strip.util.Constant;
import org.akvo.caddisfly.ui.BaseActivity;
import org.akvo.caddisfly.util.AlertUtil;
import org.akvo.caddisfly.util.ApiUtil;
import org.akvo.caddisfly.util.ColorUtil;
import org.akvo.caddisfly.util.FileUtil;
import org.akvo.caddisfly.util.ImageUtil;
import org.akvo.caddisfly.util.PreferencesUtil;
import org.json.JSONObject;
import org.opencv.android.BaseLoaderCallback;
import org.opencv.android.LoaderCallbackInterface;
import org.opencv.android.OpenCVLoader;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.UUID;

import timber.log.Timber;

import static org.akvo.caddisfly.sensor.SensorConstants.DEGREES_180;
import static org.akvo.caddisfly.sensor.SensorConstants.DEGREES_270;
import static org.akvo.caddisfly.sensor.SensorConstants.DEGREES_90;

/**
 * The activity where the test chamber type test is conducted.
 */
@SuppressWarnings("deprecation")
public class ColorimetryLiquidActivity extends BaseActivity implements ResultDialogFragment.ResultDialogListener,
        DiagnosticResultDialog.DiagnosticResultDialogListener, CameraDialog.Cancelled {

    private static final String EMPTY_STRING = "";
    private static final String TWO_SENTENCE_FORMAT = "%s%n%n%s";
    private static final String RESULT_DIALOG_TAG = "resultDialog";
    private static final int RESULT_RESTART_TEST = 3;
    private static final int MAX_SHAKE_DURATION = 2000;
    private static final int MAX_SHAKE_DURATION_2 = 3000;
    private static final int DELAY_MILLIS = 500;
    private final Handler delayHandler = new Handler();
    private final BaseLoaderCallback mLoaderCallback = new BaseLoaderCallback(this) {
        @Override
        public void onManagerConnected(int status) {
            switch (status) {
            case LoaderCallbackInterface.SUCCESS:
                break;
            default:
                super.onManagerConnected(status);
                break;
            }
        }
    };
    private double mResult;
    private Bitmap mCroppedBitmap;
    private boolean mIsCalibration;
    private double mSwatchValue;
    private int mDilutionLevel = 0;
    private DiagnosticResultDialog mResultFragment;
    private TextView textDilution;
    private SoundPoolPlayer sound;
    private ShakeDetector mShakeDetector;
    private SensorManager mSensorManager;
    private Sensor mAccelerometer;
    private boolean mWaitingForStillness = false;
    private CameraDialog mCameraFragment;
    @Nullable
    private Runnable delayRunnable;
    private PowerManager.WakeLock wakeLock;
    private List<Result> mResults;
    private boolean mTestCompleted;
    private boolean mHighLevelsFound;
    private boolean mIgnoreShake;
    //reference to last dialog opened so it can be dismissed on activity getting destroyed
    private AlertDialog alertDialogToBeDestroyed;
    private boolean mIsFirstResult;

    @Override
    protected void onResume() {
        super.onResume();
        OpenCVLoader.initAsync(OpenCVLoader.OPENCV_VERSION_3_1_0, this, mLoaderCallback);
    }

    @Override
    protected void onPostCreate(Bundle savedInstanceState) {
        super.onPostCreate(savedInstanceState);
        if (mIsCalibration && getSupportActionBar() != null) {
            String subTitle = String.format(Locale.getDefault(), "%s %.2f %s", getString(R.string.calibrate),
                    mSwatchValue, CaddisflyApp.getApp().getCurrentTestInfo().getUnit());
            textDilution.setText(subTitle);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        setContentView(R.layout.activity_colorimetry_liquid);

        setTitle("Analysis");

        Intent intent = getIntent();
        mIsCalibration = intent.getBooleanExtra("isCalibration", false);
        mSwatchValue = intent.getDoubleExtra("swatchValue", 0);

        sound = new SoundPoolPlayer(this);

        textDilution = (TextView) findViewById(R.id.textDilution);
        TextView textSubtitle = (TextView) findViewById(R.id.textSubtitle);

        //Set up the shake detector
        mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE);
        mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);

        mShakeDetector = new ShakeDetector(new ShakeDetector.OnShakeListener() {

            @Override
            public void onShake() {
                if ((mIgnoreShake) || mWaitingForStillness || mCameraFragment == null) {
                    return;
                }

                if (isDestroyed()) {
                    return;
                }

                mWaitingForStillness = true;

                showError(String.format(TWO_SENTENCE_FORMAT, getString(R.string.errorTestInterrupted),
                        getString(R.string.doNotMoveDeviceDuringTest)), null);
            }
        }, new ShakeDetector.OnNoShakeListener() {
            @Override
            public void onNoShake() {
                if (mWaitingForStillness) {
                    mWaitingForStillness = false;
                    dismissShakeAndStartTest();
                }
            }
        });

        mSensorManager.unregisterListener(mShakeDetector);

        mShakeDetector.setMinShakeAcceleration(5);
        mShakeDetector.setMaxShakeDuration(MAX_SHAKE_DURATION);

        textSubtitle.setText(R.string.placeDevice);

    }

    private void initializeTest() {

        mSensorManager.unregisterListener(mShakeDetector);

        mIgnoreShake = false;
        mTestCompleted = false;
        mHighLevelsFound = false;

        mWaitingForStillness = true;

        if (ApiUtil.isCameraInUse(this, null)) {
            releaseResources();
            finish();
        }

        mSensorManager.registerListener(mShakeDetector, mAccelerometer, SensorManager.SENSOR_DELAY_UI);
    }

    /**
     * Acquire a wake lock to prevent the screen from turning off during the analysis process.
     */
    private void acquireWakeLock() {
        if (wakeLock == null || !wakeLock.isHeld()) {
            PowerManager pm = (PowerManager) getApplicationContext().getSystemService(Context.POWER_SERVICE);
            //noinspection deprecation
            wakeLock = pm.newWakeLock(PowerManager.FULL_WAKE_LOCK | PowerManager.ACQUIRE_CAUSES_WAKEUP
                    | PowerManager.ON_AFTER_RELEASE, "CameraSensorWakeLock");
            wakeLock.acquire();
        }
    }

    /**
     * Start the test by displaying the partial_progress bar.
     */
    private void dismissShakeAndStartTest() {
        mSensorManager.unregisterListener(mShakeDetector);

        startTest();
    }

    /**
     * Show an error message dialog.
     *
     * @param message the message to be displayed
     * @param bitmap  any bitmap image to displayed along with error message
     */
    private void showError(String message, final Bitmap bitmap) {

        releaseResources();

        sound.playShortResource(R.raw.err);

        alertDialogToBeDestroyed = AlertUtil.showError(this, R.string.error, message, bitmap, R.string.retry,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        initializeTest();
                    }
                }, new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        dialogInterface.dismiss();
                        releaseResources();
                        setResult(Activity.RESULT_CANCELED);
                        finish();
                    }
                }, null);
    }

    /**
     * In diagnostic mode show the diagnostic results dialog.
     *
     * @param testFailed    if test has failed then dialog knows to show the retry button
     * @param result        the result shown to the user
     * @param color         the color that was used to get above result
     * @param isCalibration is it in calibration mode, then show only colors, no results
     */
    private void showDiagnosticResultDialog(boolean testFailed, String result, int color, boolean isCalibration) {
        mResultFragment = DiagnosticResultDialog.newInstance(mResults, testFailed, result, color, isCalibration);
        final FragmentTransaction ft = getFragmentManager().beginTransaction();

        Fragment prev = getFragmentManager().findFragmentByTag("gridDialog");
        if (prev != null) {
            ft.remove(prev);
        }
        mResultFragment.setCancelable(false);
        mResultFragment.show(ft, "gridDialog");
    }

    @Override
    protected void onStart() {
        super.onStart();
        mIsCalibration = getIntent().getBooleanExtra("isCalibration", false);
        mSwatchValue = getIntent().getDoubleExtra("swatchValue", 0);
        mDilutionLevel = getIntent().getIntExtra("dilution", 0);

        switch (mDilutionLevel) {
        case 1:
            textDilution.setText(String.format(getString(R.string.timesDilution), 2));
            break;
        case 2:
            textDilution.setText(String.format(getString(R.string.timesDilution), 5));
            break;
        default:
            textDilution.setText(R.string.noDilution);
        }

        TestInfo testInfo = CaddisflyApp.getApp().getCurrentTestInfo();

        //        TextView textResult = ((TextView) findViewById(R.id.textResult));
        if (!mIsCalibration) {
            //      textResult.setVisibility(View.GONE);

            if (getSupportActionBar() != null) {
                getSupportActionBar().setTitle(R.string.appName);
            }

            // disable the key guard when device wakes up and shake alert is displayed
            getWindow().setFlags(
                    WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON,
                    WindowManager.LayoutParams.FLAG_FULLSCREEN | WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD
                            | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                            | WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
        }

        //set the title to the test contaminant name
        ((TextView) findViewById(R.id.textTitle)).setText(testInfo.getName());

        if (testInfo.getId().isEmpty()) {
            alertCouldNotLoadConfig();
        } else if (!mTestCompleted) {
            initializeTest();
        }
    }

    /**
     * Display error message for configuration not loading correctly.
     */
    private void alertCouldNotLoadConfig() {
        String message = String.format(TWO_SENTENCE_FORMAT, getString(R.string.errorLoadingConfiguration),
                getString(R.string.pleaseContactSupport));
        AlertUtil.showError(this, R.string.error, message, null, R.string.ok,
                new DialogInterface.OnClickListener() {
                    @Override
                    public void onClick(DialogInterface dialogInterface, int i) {
                        dialogInterface.dismiss();
                        finish();
                    }
                }, null, null);
    }

    /**
     * Get the test result by analyzing the bitmap.
     *
     * @param bitmap the bitmap of the photo taken during analysis
     */
    private void getAnalyzedResult(@NonNull Bitmap bitmap) {

        //Extract the color from the photo which will be used for comparison
        ColorInfo photoColor = ColorUtil.getColorFromBitmap(bitmap,
                ColorimetryLiquidConfig.SAMPLE_CROP_LENGTH_DEFAULT);

        //Quality too low reject this result
        //        if (photoColor.getQuality() < 20) {
        //            return;
        //        }

        TestInfo testInfo = CaddisflyApp.getApp().getCurrentTestInfo();

        ArrayList<ResultDetail> results = new ArrayList<>();

        //In diagnostic mode show results based on other color models / number of calibration steps
        if (AppPreferences.isDiagnosticMode()) {
            ArrayList<Swatch> swatches = new ArrayList<>();

            //1 step analysis
            try {
                swatches.add((Swatch) testInfo.getSwatch(0).clone());
                SwatchHelper.generateSwatches(swatches, testInfo.getSwatches());
                results.add(SwatchHelper.analyzeColor(1, photoColor, swatches, ColorUtil.ColorModel.LAB));
                results.add(SwatchHelper.analyzeColor(1, photoColor, swatches, ColorUtil.ColorModel.RGB));

                swatches.clear();

                //add only the first and last swatch for a 2 step analysis
                swatches.add((Swatch) testInfo.getSwatch(0).clone());
                swatches.add((Swatch) testInfo.getSwatch(testInfo.getSwatches().size() - 1).clone());
                SwatchHelper.generateSwatches(swatches, testInfo.getSwatches());
                results.add(SwatchHelper.analyzeColor(2, photoColor, swatches, ColorUtil.ColorModel.LAB));
                results.add(SwatchHelper.analyzeColor(2, photoColor, swatches, ColorUtil.ColorModel.RGB));

                swatches.clear();

                //add the middle swatch for a 3 step analysis
                swatches.add((Swatch) testInfo.getSwatch(0).clone());
                swatches.add((Swatch) testInfo.getSwatch(testInfo.getSwatches().size() - 1).clone());
                swatches.add(1, (Swatch) testInfo.getSwatch(testInfo.getSwatches().size() / 2).clone());
                SwatchHelper.generateSwatches(swatches, testInfo.getSwatches());
                results.add(SwatchHelper.analyzeColor(3, photoColor, swatches, ColorUtil.ColorModel.LAB));
                results.add(SwatchHelper.analyzeColor(3, photoColor, swatches, ColorUtil.ColorModel.RGB));

            } catch (CloneNotSupportedException e) {
                Timber.e(e);
            }

            //use all the swatches for an all steps analysis
            results.add(SwatchHelper.analyzeColor(testInfo.getSwatches().size(), photoColor,
                    CaddisflyApp.getApp().getCurrentTestInfo().getSwatches(), ColorUtil.ColorModel.RGB));

            results.add(SwatchHelper.analyzeColor(testInfo.getSwatches().size(), photoColor,
                    CaddisflyApp.getApp().getCurrentTestInfo().getSwatches(), ColorUtil.ColorModel.LAB));
        }

        results.add(0, SwatchHelper.analyzeColor(testInfo.getSwatches().size(), photoColor,
                CaddisflyApp.getApp().getCurrentTestInfo().getSwatches(), ColorUtil.DEFAULT_COLOR_MODEL));

        Result result = new Result(bitmap, results);

        mResults.add(result);
    }

    private void startTest() {

        mResults = new ArrayList<>();

        mWaitingForStillness = false;
        mIsFirstResult = true;

        mShakeDetector.setMinShakeAcceleration(1);
        mShakeDetector.setMaxShakeDuration(MAX_SHAKE_DURATION_2);
        mSensorManager.registerListener(mShakeDetector, mAccelerometer, SensorManager.SENSOR_DELAY_UI);

        (new AsyncTask<Void, Void, Void>() {

            @Nullable
            @Override
            protected Void doInBackground(Void... params) {
                return null;
            }

            @Override
            protected void onPostExecute(Void result) {
                super.onPostExecute(result);

                mCameraFragment = CameraDialogFragment.newInstance();

                mCameraFragment.setPictureTakenObserver(new CameraDialogFragment.PictureTaken() {
                    @Override
                    public void onPictureTaken(@NonNull byte[] bytes, boolean completed) {
                        Bitmap bitmap = ImageUtil.getBitmap(bytes);

                        Display display = getWindowManager().getDefaultDisplay();
                        int rotation;
                        switch (display.getRotation()) {
                        case Surface.ROTATION_0:
                            rotation = DEGREES_90;
                            break;
                        case Surface.ROTATION_180:
                            rotation = DEGREES_270;
                            break;
                        case Surface.ROTATION_270:
                            rotation = DEGREES_180;
                            break;
                        case Surface.ROTATION_90:
                        default:
                            rotation = 0;
                            break;
                        }

                        bitmap = ImageUtil.rotateImage(bitmap, rotation);

                        TestInfo testInfo = CaddisflyApp.getApp().getCurrentTestInfo();

                        Bitmap croppedBitmap;

                        if (testInfo.isUseGrayScale()) {
                            croppedBitmap = ImageUtil.getCroppedBitmap(bitmap,
                                    ColorimetryLiquidConfig.SAMPLE_CROP_LENGTH_DEFAULT, false);

                            if (croppedBitmap != null) {
                                croppedBitmap = ImageUtil.getGrayscale(croppedBitmap);
                            }
                        } else {
                            croppedBitmap = ImageUtil.getCroppedBitmap(bitmap,
                                    ColorimetryLiquidConfig.SAMPLE_CROP_LENGTH_DEFAULT, true);
                        }

                        //Ignore the first result as camera may not have focused correctly
                        if (!mIsFirstResult) {
                            if (croppedBitmap != null) {
                                getAnalyzedResult(croppedBitmap);
                            } else {
                                showError(
                                        String.format(TWO_SENTENCE_FORMAT, getString(R.string.chamberNotFound),
                                                getString(R.string.checkChamberPlacement)),
                                        ImageUtil.getBitmap(bytes));
                                mCameraFragment.stopCamera();
                                mCameraFragment.dismiss();
                                return;
                            }
                        }
                        mIsFirstResult = false;

                        if (completed) {
                            analyzeFinalResult(bytes, croppedBitmap);
                            mCameraFragment.dismiss();
                        } else {
                            sound.playShortResource(R.raw.beep);
                        }
                    }
                });

                acquireWakeLock();

                delayRunnable = new Runnable() {
                    @Override
                    public void run() {
                        final FragmentTransaction ft = getFragmentManager().beginTransaction();
                        Fragment prev = getFragmentManager().findFragmentByTag("cameraDialog");
                        if (prev != null) {
                            ft.remove(prev);
                        }
                        ft.addToBackStack(null);
                        try {
                            mCameraFragment.show(ft, "cameraDialog");
                            mCameraFragment.takePictures(AppPreferences.getSamplingTimes(),
                                    ColorimetryLiquidConfig.DELAY_BETWEEN_SAMPLING);
                        } catch (Exception e) {
                            Timber.e(e);
                            finish();
                        }
                    }
                };

                delayHandler.postDelayed(delayRunnable, ColorimetryLiquidConfig.DELAY_BETWEEN_SAMPLING);

            }
        }).execute();
    }

    /**
     * Analyze the result and display the appropriate success/fail message.
     * <p/>
     * If the result value is too high then display the high contamination level message
     *
     * @param data          image data to be displayed if error in analysis
     * @param croppedBitmap cropped image used for analysis
     */
    private void analyzeFinalResult(@NonNull byte[] data, Bitmap croppedBitmap) {

        releaseResources();

        if (mResults.size() == 0) {
            // Analysis failed. Display error
            showError(String.format(TWO_SENTENCE_FORMAT, getString(R.string.chamberNotFound),
                    getString(R.string.checkChamberPlacement)), ImageUtil.getBitmap(data));
        } else {

            mTestCompleted = true;

            // Get the result
            mResult = SwatchHelper.getAverageResult(mResults);

            TestInfo testInfo = CaddisflyApp.getApp().getCurrentTestInfo();

            // Check if contamination level is too high
            if (mResult >= testInfo.getDilutionRequiredLevel() && testInfo.getCanUseDilution()) {
                mHighLevelsFound = true;
            }

            // Calculate final result based on dilution
            switch (mDilutionLevel) {
            case 1:
                mResult = mResult * 2;
                break;
            case 2:
                mResult = mResult * 5;
                break;
            default:
                break;
            }

            // Format the result
            String resultText = String.format(Locale.getDefault(), "%.2f", mResult);

            // Add 'greater than' symbol if result could be an unknown high value
            if (mHighLevelsFound) {
                resultText = "> " + resultText;
            }
            mCroppedBitmap = croppedBitmap;
            // Show the result dialog
            showResult(data, mResult, resultText);
        }
    }

    private void showResult(@NonNull byte[] data, double result, String resultText) {

        // Get the average color across the results
        int color = SwatchHelper.getAverageColor(mResults);

        // If calibrating then finish if successful
        if (mIsCalibration && color != Color.TRANSPARENT) {

            Intent resultIntent = new Intent(getIntent());
            setResult(Activity.RESULT_OK, resultIntent);
            resultIntent.putExtra(SensorConstants.COLOR, color);

            sound.playShortResource(R.raw.done);
            PreferencesUtil.setInt(this, R.string.totalSuccessfulCalibrationsKey,
                    PreferencesUtil.getInt(this, R.string.totalSuccessfulCalibrationsKey, 0) + 1);

            if (AppPreferences.isSaveImagesOn()) {
                saveImageForDiagnostics(data, resultText);
            }

            if (AppPreferences.isDiagnosticMode()) {
                showDiagnosticResultDialog(false, resultText, color, true);
            } else {
                (new Handler()).postDelayed(new Runnable() {
                    public void run() {
                        finish();
                    }
                }, DELAY_MILLIS);
            }
        } else {

            // If no result then display error message
            if (result < 0 || color == Color.TRANSPARENT) {
                if (AppPreferences.isSaveImagesOn()) {
                    saveImageForDiagnostics(data, resultText);
                }

                if (AppPreferences.isDiagnosticMode()) {
                    sound.playShortResource(R.raw.err);

                    PreferencesUtil.setInt(this, R.string.totalFailedTestsKey,
                            PreferencesUtil.getInt(this, R.string.totalFailedTestsKey, 0) + 1);

                    showDiagnosticResultDialog(true, EMPTY_STRING, color, mIsCalibration);
                } else {
                    if (mIsCalibration) {
                        showError(String.format(TWO_SENTENCE_FORMAT, getString(R.string.chamberNotFound),
                                getString(R.string.checkChamberPlacement)), ImageUtil.getBitmap(data));
                        PreferencesUtil.setInt(this, R.string.totalFailedCalibrationsKey,
                                PreferencesUtil.getInt(this, R.string.totalFailedCalibrationsKey, 0) + 1);
                    } else {
                        PreferencesUtil.setInt(this, R.string.totalFailedTestsKey,
                                PreferencesUtil.getInt(this, R.string.totalFailedTestsKey, 0) + 1);

                        showError(String.format(TWO_SENTENCE_FORMAT, getString(R.string.errorTestFailed),
                                getString(R.string.checkChamberPlacement)), ImageUtil.getBitmap(data));
                    }
                }
            } else {

                if (AppPreferences.isSaveImagesOn()) {
                    saveImageForDiagnostics(data, resultText);
                }

                if (AppPreferences.isDiagnosticMode()) {
                    sound.playShortResource(R.raw.done);
                    showDiagnosticResultDialog(false, resultText, color, false);

                    PreferencesUtil.setInt(this, R.string.totalSuccessfulTestsKey,
                            PreferencesUtil.getInt(this, R.string.totalSuccessfulTestsKey, 0) + 1);

                } else {
                    String title = CaddisflyApp.getApp().getCurrentTestInfo().getName();

                    String message = EMPTY_STRING;
                    if (mHighLevelsFound && mDilutionLevel < 2) {
                        sound.playShortResource(R.raw.beep_long);
                        switch (mDilutionLevel) {
                        case 0:
                            message = String.format(getString(R.string.tryWithDilutedSample), 2);
                            break;
                        case 1:
                            message = String.format(getString(R.string.tryWithDilutedSample), 5);
                            break;
                        default:
                            message = EMPTY_STRING;
                        }
                    } else {
                        sound.playShortResource(R.raw.done);
                    }

                    PreferencesUtil.setInt(this, R.string.totalSuccessfulTestsKey,
                            PreferencesUtil.getInt(this, R.string.totalSuccessfulTestsKey, 0) + 1);

                    ResultDialogFragment mResultDialogFragment = ResultDialogFragment.newInstance(title, resultText,
                            mDilutionLevel, message, CaddisflyApp.getApp().getCurrentTestInfo().getUnit());
                    final FragmentTransaction ft = getFragmentManager().beginTransaction();

                    Fragment fragment = getFragmentManager().findFragmentByTag(RESULT_DIALOG_TAG);
                    if (fragment != null) {
                        ft.remove(fragment);
                    }

                    mResultDialogFragment.setCancelable(false);
                    mResultDialogFragment.show(ft, RESULT_DIALOG_TAG);
                }
            }
        }
    }

    private void saveImageForDiagnostics(@NonNull byte[] data, String result) {
        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        Intent batteryStatus = registerReceiver(null, intentFilter);
        int batteryPercent = -1;

        if (batteryStatus != null) {
            int level = batteryStatus.getIntExtra(BatteryManager.EXTRA_LEVEL, -1);
            int scale = batteryStatus.getIntExtra(BatteryManager.EXTRA_SCALE, -1);
            batteryPercent = (int) ((level / (float) scale) * 100);
        }

        if (mIsCalibration) {
            result = String.format(Locale.US, "%.2f", mSwatchValue);
        }

        String date = new SimpleDateFormat("yyyy-MM-dd_HH-mm", Locale.US).format(new Date());
        ImageUtil.saveImage(data, CaddisflyApp.getApp().getCurrentTestInfo().getId(),
                String.format(Locale.US, "%s_%s_%s_%d_%s", date, (mIsCalibration ? "C" : "T"), result,
                        batteryPercent, ApiUtil.getInstallationId(this)));
    }

    @Override
    public void onSuccessFinishDialog(boolean resultOk) {
        if (resultOk) {
            setResultIntent();
        } else {
            setResult(RESULT_RESTART_TEST);
        }

        finish();

    }

    private void setResultIntent() {
        // Get the average color across the results
        int color = SwatchHelper.getAverageColor(mResults);

        // If this is a test and it was successful then build the result to return
        if (!mIsCalibration && (mResult > -1 || color != Color.TRANSPARENT)) {
            Intent intent = getIntent();

            String resultText = String.format(Locale.getDefault(), "%.2f", mResult);

            // Add 'greater than' symbol if result could be an unknown high value
            if (mHighLevelsFound) {
                resultText = "> " + resultText;
            }

            Intent resultIntent = new Intent(intent);
            resultIntent.putExtra(SensorConstants.RESULT, resultText);
            resultIntent.putExtra(SensorConstants.COLOR, color);

            String resultImageUrl = EMPTY_STRING;
            if (getIntent().getBooleanExtra(Constant.SEND_IMAGE_IN_RESULT, false)) {
                // Save photo taken during the test
                resultImageUrl = UUID.randomUUID().toString() + ".png";
                String path = FileUtil.writeBitmapToExternalStorage(mCroppedBitmap, "/result-images",
                        resultImageUrl);
                resultIntent.putExtra(SensorConstants.IMAGE, path);
            }

            TestInfo testInfo = CaddisflyApp.getApp().getCurrentTestInfo();

            SparseArray<String> results = new SparseArray<>();
            results.put(testInfo.getSubTests().get(0).getId(), resultText);

            JSONObject resultJson = TestConfigHelper.getJsonResult(testInfo, results, color, resultImageUrl, null);

            resultIntent.putExtra(SensorConstants.RESPONSE, resultJson.toString());

            // TODO: Remove this when obsolete
            // Backward compatibility. Return plain text result
            resultIntent.putExtra(SensorConstants.RESPONSE_COMPAT, resultText);

            setResult(Activity.RESULT_OK, resultIntent);

        }
    }

    private void releaseResources() {
        mSensorManager.unregisterListener(mShakeDetector);
        delayHandler.removeCallbacks(delayRunnable);
        if (mCameraFragment != null) {
            try {
                mCameraFragment.dismiss();
            } catch (Exception ignored) {

            }
            mCameraFragment.stopCamera();
        }
        if (wakeLock != null && wakeLock.isHeld()) {
            wakeLock.release();
        }
    }

    @Override
    public boolean onOptionsItemSelected(@NonNull MenuItem item) {
        if (item.getItemId() == android.R.id.home) {
            onBackPressed();
            return true;
        }
        return super.onOptionsItemSelected(item);
    }

    @Override
    protected void onUserLeaveHint() {
        super.onUserLeaveHint();
        releaseResources();
        if (alertDialogToBeDestroyed != null) {
            alertDialogToBeDestroyed.dismiss();
        }
        setResult(Activity.RESULT_CANCELED);
        finish();
    }

    @Override
    public void onBackPressed() {
        super.onBackPressed();
        releaseResources();
    }

    @Override
    public void onFinishDiagnosticResultDialog(boolean retry, boolean cancelled, String result,
            boolean isCalibration) {
        mResultFragment.dismiss();
        if (mHighLevelsFound && !isCalibration) {
            mCameraFragment.dismiss();
            sound.playShortResource(R.raw.beep_long);
            String title = CaddisflyApp.getApp().getCurrentTestInfo().getName();

            String message;
            //todo: remove hard coding of dilution levels
            switch (mDilutionLevel) {
            case 0:
                message = String.format(getString(R.string.tryWithDilutedSample), 2);
                break;
            case 1:
                message = String.format(getString(R.string.tryWithDilutedSample), 5);
                break;
            default:
                message = EMPTY_STRING;
            }

            ResultDialogFragment mResultDialogFragment = ResultDialogFragment.newInstance(title, result,
                    mDilutionLevel, message, CaddisflyApp.getApp().getCurrentTestInfo().getUnit());
            final FragmentTransaction ft = getFragmentManager().beginTransaction();

            Fragment fragment = getFragmentManager().findFragmentByTag(RESULT_DIALOG_TAG);
            if (fragment != null) {
                ft.remove(fragment);
            }

            mResultDialogFragment.setCancelable(false);
            mResultDialogFragment.show(ft, RESULT_DIALOG_TAG);

        } else if (retry) {
            mCameraFragment.dismiss();
            initializeTest();
        } else {
            releaseResources();
            if (cancelled) {
                setResult(Activity.RESULT_CANCELED);
            } else {
                setResultIntent();
            }
            finish();
        }
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        sound.release();
    }

    @Override
    public void dialogCancelled() {
        Intent intent = new Intent(getIntent());
        intent.putExtra(SensorConstants.RESPONSE, String.valueOf(EMPTY_STRING));
        this.setResult(Activity.RESULT_CANCELED, intent);
        releaseResources();
        finish();

    }
}