maoni/src/main/java/org/rm3l/maoni/ui/MaoniActivity.java

Summary

Maintainability
F
5 days
Test Coverage
/*
 * Copyright (c) 2016-2022 Armel Soro
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */
package org.rm3l.maoni.ui;

import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ProviderInfo;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.net.Uri;
import android.os.Bundle;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.ContextCompat;
import androidx.core.content.FileProvider;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.Toolbar;
import android.text.TextUtils;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.google.android.material.appbar.AppBarLayout;
import com.google.android.material.textfield.TextInputLayout;

import java.io.File;
import java.util.HashMap;
import java.util.UUID;
import me.panavtec.drawableview.DrawableView;
import me.panavtec.drawableview.DrawableViewConfig;
import org.rm3l.maoni.Maoni.CallbacksConfiguration;
import org.rm3l.maoni.R;
import org.rm3l.maoni.common.contract.Listener;
import org.rm3l.maoni.common.contract.UiListener;
import org.rm3l.maoni.common.contract.Validator;
import org.rm3l.maoni.common.model.Feedback;
import org.rm3l.maoni.utils.LogcatUtils;
import org.rm3l.maoni.utils.ViewUtils;

/**
 * Maoni Activity
 */
public class MaoniActivity extends AppCompatActivity {

    private static final String TAG = MaoniActivity.class.getSimpleName();

    public static final String APPLICATION_INFO_VERSION_CODE = "APPLICATION_INFO_VERSION_CODE";

    public static final String APPLICATION_INFO_VERSION_NAME = "APPLICATION_INFO_VERSION_NAME";

    public static final String APPLICATION_INFO_PACKAGE_NAME = "APPLICATION_INFO_PACKAGE_NAME";

    public static final String APPLICATION_INFO_BUILD_CONFIG_DEBUG =
            "APPLICATION_INFO_BUILD_CONFIG_DEBUG";

    public static final String APPLICATION_INFO_BUILD_CONFIG_FLAVOR =
            "APPLICATION_INFO_BUILD_CONFIG_FLAVOR";

    public static final String APPLICATION_INFO_BUILD_CONFIG_BUILD_TYPE =
            "APPLICATION_INFO_BUILD_CONFIG_BUILD_TYPE";

    public static final String WORKING_DIR = "WORKING_DIR";

    public static final String FILE_PROVIDER_AUTHORITY = "FILE_PROVIDER_AUTHORITY";

    public static final String THEME = "THEME";

    public static final String TOOLBAR_TITLE_TEXT_COLOR = "TOOLBAR_TITLE_TEXT_COLOR";

    public static final String TOOLBAR_SUBTITLE_TEXT_COLOR = "TOOLBAR_SUBTITLE_TEXT_COLOR";

    public static final String SCREENSHOT_FILE = "SCREENSHOT_FILE";

    public static final String CALLER_ACTIVITY = "CALLER_ACTIVITY";

    public static final String WINDOW_TITLE = "WINDOW_TITLE";

    public static final String WINDOW_SUBTITLE = "WINDOW_SUBTITLE";

    public static final String MESSAGE = "MESSAGE";

    public static final String HEADER = "HEADER";

    public static final String SCREENSHOT_HINT = "SCREENSHOT_HINT";

    public static final String CONTENT_HINT = "CONTENT_HINT";

    public static final String CONTENT_ERROR_TEXT = "CONTENT_ERROR_TEXT";

    public static final String SCREENSHOT_TOUCH_TO_PREVIEW_HINT = "SCREENSHOT_PREVIEW_HINT";

    public static final String INCLUDE_LOGS_TEXT = "INCLUDE_LOGS_TEXT";

    public static final String INCLUDE_SCREENSHOT_TEXT = "INCLUDE_SCREENSHOT_TEXT";

    public static final String EXTRA_LAYOUT = "EXTRA_LAYOUT";

    public static final String SHOW_KEYBOARD_ON_START = "SHOW_KEYBOARD_ON_START";

    public static final String SCREEN_CAPTURING_FEATURE_ENABLED = "SCREEN_CAPTURING_FEATURE_ENABLED";

    public static final String LOGS_CAPTURING_FEATURE_ENABLED = "LOGS_CAPTURING_FEATURE_ENABLED";

    public static final String SHARED_PREFERENCES = "SHARED_PREFS";

    private static final String MAONI_LOGS_FILENAME = "maoni_logs.txt";

    protected View mRootView;

    private Feedback.App mAppInfo;

    private int mBlackoutColor;

    @Nullable
    private EditText mContent;

    @Nullable
    private CharSequence mContentErrorText;

    @Nullable
    private TextInputLayout mContentInputLayout;

    private String mFeedbackUniqueId;

    private int mHighlightColor;

    @Nullable
    private CheckBox mIncludeLogs;

    @Nullable
    private CheckBox mIncludeScreenshot;

    private Listener mListener;

    private boolean mLogsCapturingFeatureEnabled;

    private Menu mMenu;

    private boolean mScreenCapturingFeatureEnabled;

    @Nullable
    private CharSequence mScreenshotFilePath;

    private HashMap<String, Object> mSharedPreferences;

    private boolean mShowKeyboardOnStart;

    private Validator mValidator;

    private File mWorkingDir;

    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        final Intent intent = getIntent();

        setTheme(intent.getIntExtra(THEME, R.style.Maoni_AppTheme_Light));

        setContentView(R.layout.maoni_activity_feedback);

        mRootView = findViewById(R.id.maoni_container);
        if (mRootView == null) {
            throw new IllegalStateException(
                    "Layout must contain a root view with the following id: maoni_container");
        }

        mHighlightColor = ContextCompat.getColor(this, R.color.maoni_highlight_transparent_semi);
        mBlackoutColor = ContextCompat.getColor(this, R.color.maoni_black);

        if (intent.hasExtra(WORKING_DIR)) {
            mWorkingDir = new File(intent.getStringExtra(WORKING_DIR));
        } else {
            mWorkingDir = getCacheDir();
        }

        if (intent.hasExtra(SHARED_PREFERENCES)) {
            //noinspection unchecked
            mSharedPreferences = (HashMap<String, Object>) intent.getSerializableExtra(SHARED_PREFERENCES);
        }

        final ImageView headerImageView = findViewById(R.id.maoni_toolbar_header_image);
        if (headerImageView != null && intent.hasExtra(HEADER)) {
            final int headerLayoutId = intent.getIntExtra(HEADER, -1);
            if (headerLayoutId != -1) {
                headerImageView.setImageResource(headerLayoutId);
            }
        }

        if (intent.hasExtra(EXTRA_LAYOUT)) {
            final View extraContentView = findViewById(R.id.maoni_content_extra);
            if (extraContentView instanceof LinearLayout) {
                final int extraLayout = intent.getIntExtra(EXTRA_LAYOUT, -1);
                if (extraLayout != -1) {
                    final LinearLayout extraContent = (LinearLayout) extraContentView;
                    extraContent.setVisibility(View.VISIBLE);
                    extraContent
                            .addView(getLayoutInflater().inflate(extraLayout, extraContent, false));
                }
            }
        }

        final CallbacksConfiguration maoniConfiguration = CallbacksConfiguration.getInstance(this);
        mListener = maoniConfiguration.getListener();
        mValidator = maoniConfiguration.getValidator();

        final Toolbar toolbar = findViewById(R.id.maoni_toolbar);
        if (toolbar != null) {
            toolbar.setTitle(intent.hasExtra(WINDOW_TITLE) ?
                    intent.getCharSequenceExtra(WINDOW_TITLE) :
                    getString(R.string.maoni_send_feedback));
            if (intent.hasExtra(WINDOW_SUBTITLE)) {
                toolbar.setSubtitle(intent.getCharSequenceExtra(WINDOW_SUBTITLE));
            }
            if (intent.hasExtra(TOOLBAR_TITLE_TEXT_COLOR)) {
                toolbar.setTitleTextColor(
                        intent.getIntExtra(TOOLBAR_TITLE_TEXT_COLOR, R.color.maoni_white));
            }
            if (intent.hasExtra(TOOLBAR_SUBTITLE_TEXT_COLOR)) {
                toolbar.setSubtitleTextColor(
                        intent.getIntExtra(TOOLBAR_SUBTITLE_TEXT_COLOR, R.color.maoni_white));
            }
            setSupportActionBar(toolbar);
        }

        final ActionBar actionBar = getSupportActionBar();
        if (actionBar != null) {
            actionBar.setDisplayHomeAsUpEnabled(true);
            actionBar.setHomeButtonEnabled(true);
            actionBar.setHomeButtonEnabled(true);
        }

        if (intent.hasExtra(MESSAGE)) {
            final CharSequence message = intent.getCharSequenceExtra(MESSAGE);
            final TextView activityMessageTv = findViewById(R.id.maoni_feedback_message);
            if (activityMessageTv != null) {
                activityMessageTv.setText(message);
            }
        }

        if (intent.hasExtra(SCREENSHOT_HINT)) {
            final CharSequence screenshotInformationalHint = intent.getCharSequenceExtra(SCREENSHOT_HINT);
            final TextView screenshotInformationalHintTv = findViewById(R.id.maoni_screenshot_informational_text);
            if (screenshotInformationalHintTv != null) {
                screenshotInformationalHintTv.setText(screenshotInformationalHint);
            }
        }

        mContentInputLayout = findViewById(R.id.maoni_content_input_layout);
        mContent = findViewById(R.id.maoni_content);

        if (intent.hasExtra(CONTENT_HINT)) {
            final CharSequence contentHint = intent.getCharSequenceExtra(CONTENT_HINT);
            if (mContentInputLayout != null) {
                mContentInputLayout.setHint(contentHint);
            }
        }

        if (intent.hasExtra(CONTENT_ERROR_TEXT)) {
            mContentErrorText = intent.getCharSequenceExtra(CONTENT_ERROR_TEXT);
        } else {
            mContentErrorText = getString(R.string.maoni_validate_must_not_be_blank);
        }

        mIncludeLogs = findViewById(R.id.maoni_include_logs);
        if (mIncludeLogs != null && intent.hasExtra(INCLUDE_LOGS_TEXT)) {
            mIncludeLogs.setText(intent.getCharSequenceExtra(INCLUDE_LOGS_TEXT));
        }

        mIncludeScreenshot = findViewById(R.id.maoni_include_screenshot);
        if (mIncludeScreenshot != null && intent.hasExtra(INCLUDE_SCREENSHOT_TEXT)) {
            mIncludeScreenshot.setText(intent.getCharSequenceExtra(INCLUDE_SCREENSHOT_TEXT));
        }

        mScreenshotFilePath = intent.getCharSequenceExtra(SCREENSHOT_FILE);

        mShowKeyboardOnStart = intent.getBooleanExtra(SHOW_KEYBOARD_ON_START, false);

        mScreenCapturingFeatureEnabled = intent.getBooleanExtra(SCREEN_CAPTURING_FEATURE_ENABLED, true);
        final Integer[] screenCapturingRelatedFields = new Integer[]{
                R.id.maoni_include_screenshot,
                R.id.maoni_include_screenshot_content
        };
        if (mScreenCapturingFeatureEnabled) {
            initScreenCaptureView(intent);
        }
        final int visibilityForScreenCapturingRelatedFields = (mScreenCapturingFeatureEnabled ?
                View.VISIBLE : View.GONE);
        for (final Integer screenCapturingRelatedField : screenCapturingRelatedFields) {
            final View view = findViewById(screenCapturingRelatedField);
            if (view == null) {
                continue;
            }
            view.setVisibility(visibilityForScreenCapturingRelatedFields);
        }

        mLogsCapturingFeatureEnabled = intent.getBooleanExtra(LOGS_CAPTURING_FEATURE_ENABLED, true);
        final Integer[] logsCapturingRelatedFields = new Integer[]{
                R.id.maoni_include_logs
        };
        final int visibilityForLogsCapturingRelatedFields = (mLogsCapturingFeatureEnabled ?
                View.VISIBLE : View.GONE);
        for (final Integer logsCapturingRelatedField : logsCapturingRelatedFields) {
            final View view = findViewById(logsCapturingRelatedField);
            if (view == null) {
                continue;
            }
            view.setVisibility(visibilityForLogsCapturingRelatedFields);
        }

        mFeedbackUniqueId = UUID.randomUUID().toString();

        final View fab = findViewById(R.id.maoni_fab);
        if (fab != null) {
            fab.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    validateAndSubmitForm();
                }
            });
        }

        setAppRelatedInfo();

        final UiListener uiListener = maoniConfiguration.getUiListener();
        if (uiListener != null) {
            uiListener.onCreate(mRootView, savedInstanceState);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        if (!mShowKeyboardOnStart) {
            getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
        }
    }

    @Override
    protected void onDestroy() {
        CallbacksConfiguration.getInstance(this).reset();
        super.onDestroy();
    }

    @Override
    public void onBackPressed() {
        if (mListener != null) {
            mListener.onDismiss();
        }
        super.onBackPressed();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        getMenuInflater().inflate(R.menu.maoni_activity_menu, menu);
        this.mMenu = menu;
        final AppBarLayout appBarLayout = findViewById(R.id.maoni_app_bar);
        appBarLayout.addOnOffsetChangedListener(new AppBarStateChangeListener() {
            @Override
            public void onStateChanged(AppBarLayout appBarLayout, State state) {
                final MenuItem item = mMenu.findItem(R.id.maoni_feedback_send);
                if (item != null) {
                    item.setVisible(state != State.EXPANDED);
                }
            }
        });
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public final boolean onOptionsItemSelected(@NonNull MenuItem item) {
        final int itemId = item.getItemId();
        if (itemId == android.R.id.home) {
            onBackPressed();
        } else if (itemId == R.id.maoni_feedback_send) {
            validateAndSubmitForm();
        }
        return true;
    }

    private void initScreenCaptureView(@NonNull final Intent intent) {
        final ImageButton screenshotThumb = findViewById(R.id.maoni_screenshot);

        final TextView touchToPreviewTextView = findViewById(R.id.maoni_screenshot_touch_to_preview);
        if (touchToPreviewTextView != null && intent.hasExtra(SCREENSHOT_TOUCH_TO_PREVIEW_HINT)) {
            touchToPreviewTextView.setText(
                    intent.getCharSequenceExtra(SCREENSHOT_TOUCH_TO_PREVIEW_HINT));
        }

        final View screenshotContentView = findViewById(R.id.maoni_include_screenshot_content);
        if (!TextUtils.isEmpty(mScreenshotFilePath)) {
            final File file = new File(mScreenshotFilePath.toString());
            if (file.exists()) {
                if (mIncludeScreenshot != null) {
                    mIncludeScreenshot.setVisibility(View.VISIBLE);
                }
                if (screenshotContentView != null) {
                    screenshotContentView.setVisibility(View.VISIBLE);
                }
                if (screenshotThumb != null) {
                    //Thumbnail - load with smaller resolution so as to reduce memory footprint
                    screenshotThumb.setImageBitmap(
                            ViewUtils.decodeSampledBitmapFromFilePath(
                                    file.getAbsolutePath(), 100, 100));
                }

                // Hook up clicks on the thumbnail views.
                if (screenshotThumb != null) {
                    screenshotThumb.setOnClickListener(new View.OnClickListener() {
                        @Override
                        public void onClick(View view) {

                            final Dialog imagePreviewDialog = new Dialog(MaoniActivity.this);

                            imagePreviewDialog.requestWindowFeature(Window.FEATURE_NO_TITLE);
                            imagePreviewDialog.getWindow().setBackgroundDrawable(
                                    new ColorDrawable(Color.TRANSPARENT));

                            imagePreviewDialog.setOnDismissListener(new DialogInterface.OnDismissListener() {
                                @Override
                                public void onDismiss(DialogInterface dialogInterface) {
                                    //nothing;
                                }
                            });

                            imagePreviewDialog.setContentView(R.layout.maoni_screenshot_preview);

                            final View.OnClickListener clickListener = new View.OnClickListener() {
                                @Override
                                public void onClick(View v) {
                                    imagePreviewDialog.dismiss();
                                }
                            };

                            final ImageView imageView =
                                    imagePreviewDialog.findViewById(R.id.maoni_screenshot_preview_image);
                            imageView.setImageURI(Uri.fromFile(file));

                            final DrawableView drawableView = imagePreviewDialog
                                    .findViewById(R.id.maoni_screenshot_preview_image_drawable_view);
                            final DrawableViewConfig config = new DrawableViewConfig();
                            // If the view is bigger than canvas, with this the user will see the bounds
                            config.setShowCanvasBounds(true);
                            config.setStrokeWidth(57.0f);
                            config.setMinZoom(1.0f);
                            config.setMaxZoom(1.0f);
                            config.setStrokeColor(mHighlightColor);
                            final View decorView = getWindow().getDecorView();
                            config.setCanvasWidth(decorView.getWidth());
                            config.setCanvasHeight(decorView.getHeight());
                            drawableView.setConfig(config);
                            drawableView.bringToFront();

                            imagePreviewDialog.findViewById(R.id.maoni_screenshot_preview_pick_highlight_color)
                                    .setOnClickListener(new View.OnClickListener() {
                                        @Override
                                        public void onClick(View view) {
                                            config.setStrokeColor(mHighlightColor);
                                        }
                                    });
                            imagePreviewDialog.findViewById(R.id.maoni_screenshot_preview_pick_blackout_color)
                                    .setOnClickListener(new View.OnClickListener() {
                                        @Override
                                        public void onClick(View view) {
                                            config.setStrokeColor(mBlackoutColor);
                                        }
                                    });
                            imagePreviewDialog.findViewById(R.id.maoni_screenshot_preview_close)
                                    .setOnClickListener(clickListener);

                            imagePreviewDialog.findViewById(R.id.maoni_screenshot_preview_undo)
                                    .setOnClickListener(new View.OnClickListener() {
                                        @Override
                                        public void onClick(View view) {
                                            drawableView.undo();
                                        }
                                    });

                            imagePreviewDialog.findViewById(R.id.maoni_screenshot_preview_save)
                                    .setOnClickListener(new View.OnClickListener() {
                                        @Override
                                        public void onClick(View view) {
                                            ViewUtils.exportViewToFile(MaoniActivity.this,
                                                    imagePreviewDialog.findViewById(
                                                            R.id.maoni_screenshot_preview_image_view_updated),
                                                    new File(mScreenshotFilePath.toString()));
                                            initScreenCaptureView(intent);
                                            imagePreviewDialog.dismiss();
                                        }
                                    });

                            imagePreviewDialog.setCancelable(true);
                            imagePreviewDialog.setCanceledOnTouchOutside(false);

                            imagePreviewDialog.show();
                        }
                    });
                }
            } else {
                if (mIncludeScreenshot != null) {
                    mIncludeScreenshot.setVisibility(View.GONE);
                }
                if (screenshotContentView != null) {
                    screenshotContentView.setVisibility(View.GONE);
                }
            }
        } else {
            if (mIncludeScreenshot != null) {
                mIncludeScreenshot.setVisibility(View.GONE);
            }
            if (screenshotContentView != null) {
                screenshotContentView.setVisibility(View.GONE);
            }
        }
    }

    private void setAppRelatedInfo() {

        final Intent intent = getIntent();
        final CharSequence callerActivity = intent.getCharSequenceExtra(CALLER_ACTIVITY);
        mAppInfo = new Feedback.App(
                callerActivity != null ? callerActivity : getClass().getSimpleName(),
                intent.hasExtra(APPLICATION_INFO_BUILD_CONFIG_DEBUG) ?
                        intent.getBooleanExtra(APPLICATION_INFO_BUILD_CONFIG_DEBUG, false) : null,
                intent.getStringExtra(APPLICATION_INFO_PACKAGE_NAME),
                intent.getIntExtra(APPLICATION_INFO_VERSION_CODE, -1),
                intent.getStringExtra(APPLICATION_INFO_BUILD_CONFIG_FLAVOR),
                intent.getStringExtra(APPLICATION_INFO_BUILD_CONFIG_BUILD_TYPE),
                intent.hasExtra(APPLICATION_INFO_VERSION_NAME) ?
                        intent.getStringExtra(APPLICATION_INFO_VERSION_NAME) : null);
    }

    private void validateAndSubmitForm() {
        //Validate form
        if (this.validateForm(mRootView)) {
            //TODO Check that device is actually connected to the internet prior to going any further
            boolean includeScreenshot = false;
            if (mScreenCapturingFeatureEnabled && mIncludeScreenshot != null) {
                includeScreenshot = mIncludeScreenshot.isChecked();
            }
            String contentText = "";
            if (mContent != null) {
                contentText = mContent.getText().toString();
            }

            final Intent intent = getIntent();

            Uri screenshotUri = null;
            File screenshotFile = null;
            Uri logsUri = null;
            File logsFile = null;

            final boolean includeLogs = mLogsCapturingFeatureEnabled &&
                    mIncludeLogs != null && mIncludeLogs.isChecked();
            if (includeLogs) {
                logsFile = new File(
                        mWorkingDir,
                        MAONI_LOGS_FILENAME);
                LogcatUtils.getLogsToFile(logsFile);
            }

            if (intent.hasExtra(FILE_PROVIDER_AUTHORITY)) {
                final String fileProviderAuthority = intent.getStringExtra(FILE_PROVIDER_AUTHORITY);
                //#11 : Potential NPE in Android's FileProvider if Provider Authority couldn't be resolved
                final ProviderInfo providerAuthorityInfo = this.getPackageManager()
                        .resolveContentProvider(fileProviderAuthority, PackageManager.GET_META_DATA);
                if (providerAuthorityInfo == null) {
                    Log.w(TAG, "Could not resolve file provider authority : "
                            + fileProviderAuthority + ". Sharing of files captured not supported then. "
                            + "See http://maoni.rm3l.org/ for setup instructions.");
                } else {
                    if (mScreenshotFilePath != null) {
                        screenshotFile = new File(mScreenshotFilePath.toString());
                        screenshotUri =
                                FileProvider.getUriForFile(this, fileProviderAuthority, screenshotFile);
                        grantUriPermission(intent.getComponent().getPackageName(), screenshotUri,
                                Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    }
                    if (logsFile != null) {
                        logsUri = FileProvider.getUriForFile(this, fileProviderAuthority, logsFile);
                        grantUriPermission(intent.getComponent().getPackageName(), logsUri,
                                Intent.FLAG_GRANT_READ_URI_PERMISSION);
                    }
                }
            }

            //Construct the feedback object and call the actual implementation
            final Feedback feedback =
                    new Feedback(mFeedbackUniqueId,
                            this,
                            mAppInfo,
                            contentText,
                            includeScreenshot,
                            screenshotUri,
                            screenshotFile,
                            includeLogs,
                            logsUri,
                            logsFile,
                            mSharedPreferences);
            if (mListener != null) {
                if (mListener.onSendButtonClicked(feedback)) {
                    finish();
                } // else do *not* finish the activity
            } else {
                finish();
            }
        } //else do nothing - this is up to the callback implementation
    }

    private boolean validateForm(@NonNull View rootView) {
        if (mContent != null) {
            if (TextUtils.isEmpty(mContent.getText())) {
                if (mContentInputLayout != null) {
                    mContentInputLayout.setErrorEnabled(true);
                    mContentInputLayout.setError(mContentErrorText);
                }
                return false;
            } else {
                if (mContentInputLayout != null) {
                    mContentInputLayout.setErrorEnabled(false);
                }
            }
        }
        //Call the validator implementation instead
        return mValidator == null || mValidator.validateForm(rootView);
    }

}