public interface Handler extends Validator, Listener, UiListener {
} public interface Listener {

    /**
     * Called when the user has dismissed the Maoni feedback form,
     * without having touched the "Send Feedback" button
     */
    void onDismiss();

    /**
     * Called upon a successful form validation.
     *
     * @param feedback the feedback object,
     *                 which you can manipulate to interact with other remote feedback systems.
     *
     * @return {@code true} if activity should be destroyed or {@code false} otherwise.
     */
    boolean onSendButtonClicked(final Feedback feedback);
} public interface UiListener {

    /**
     * Called when starting the initialization of Maoni activity.
     * <p>Can be used for example to perform some extra fields initialization.
     *
     * @param rootView           the root view of Maoni activity
     * @param savedInstanceState the saved instance state
     */
    void onCreate(final View rootView, Bundle savedInstanceState);
} public interface Validator {

    /**
     * Check and validate user input.
     * Note that the form validation is synchronous and executed in the UI main thread.
     * As such, it should not be blocking.
     *
     * @param rootView the root view of Maoni activity
     * @return the validation status. Returning {@code true} makes Maoni close
     * and call your callback {@link Listener#onSendButtonClicked(Feedback)} method.
     */
    boolean validateForm(final View rootView);
} public class MaoniEmailListener implements Listener {

    public static final String DEFAULT_EMAIL_SUBJECT = "Feedback";

    private final Context mContext;
    private final String mMimeType;
    private final String mSubject;
    private final String[] mToAddresses;
    private final String mBodyHeader;
    private final String mBodyFooter;
    private final String[] mCcAddresses;
    private final String[] mBccAddresses;

    public MaoniEmailListener(final Context context, final String... toAddresses) {
        this(context, DEFAULT_EMAIL_SUBJECT, toAddresses);
    }

    public MaoniEmailListener(final Context context, final String subject,
        final String[] toAddresses) {
        this(context, subject, toAddresses, null, null);
    }

    public MaoniEmailListener(final Context context, final String subject,
        final String[] toAddresses, final String[] ccAddresses, final String[] bccAddresses) {
        this(context, "text/html", subject, null, null, toAddresses, ccAddresses, bccAddresses);
    }

    public MaoniEmailListener(
            final Context context,
            final String mimeType,
            final String subject,
            final String bodyHeader,
            final String bodyFooter,
            final String[] toAddresses,
            final String[] ccAddresses,
            final String[] bccAddresses) {
        this.mContext = context;
        this.mMimeType = mimeType;
        this.mSubject = subject;
        this.mToAddresses = toAddresses;
        this.mBodyHeader = bodyHeader;
        this.mBodyFooter = bodyFooter;
        this.mCcAddresses = ccAddresses;
        this.mBccAddresses = bccAddresses;
    }

    @Override
    public void onDismiss() {
        //Nothing to do
    }

    @Override
    public boolean onSendButtonClicked(final Feedback feedback) {

        final Intent intent = new Intent(Intent.ACTION_SEND_MULTIPLE);
        intent.setData(Uri.parse("mailto:")); // only email apps should handle this
        intent.putExtra(Intent.EXTRA_SUBJECT,
                mSubject != null ? mSubject : DEFAULT_EMAIL_SUBJECT);
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
            if (mToAddresses != null) {
                intent.putExtra(Intent.EXTRA_EMAIL, mToAddresses);
            }
            if (mCcAddresses != null) {
                intent.putExtra(Intent.EXTRA_CC, mCcAddresses);
            }
            if (mBccAddresses != null) {
                intent.putExtra(Intent.EXTRA_BCC, mBccAddresses);
            }
        }
        if (mMimeType != null) {
            intent.setType(mMimeType);
        }

        final StringBuilder body = new StringBuilder();
        if (mBodyHeader != null) {
            body.append(mBodyHeader).append("\n\n");
        }

        body.append(feedback.userComment).append("\n\n");

        body.append("\n------------\n");
        body.append("- Feedback ID: ").append(feedback.id).append("\n");

        final Map<CharSequence, Object> additionalData = feedback.getAdditionalData();
        if (additionalData != null) {
            body.append("\n------ Extra-fields ------\n");
            for (final Map.Entry<CharSequence, Object> entry : additionalData.entrySet()) {
                body.append("- ")
                        .append(entry.getKey()).append(": ").
                        append(entry.getValue()).append("\n");
            }
        }

        body.append("\n------ Application ------\n");
        if (feedback.appInfo != null) {
            if (feedback.appInfo.applicationId != null) {
                body.append("- Application ID: ").append(feedback.appInfo.applicationId).append("\n");
            }
            if (feedback.appInfo.caller != null) {
                body.append("- Activity: ").append(feedback.appInfo.caller).append("\n");
            }
            if (feedback.appInfo.buildType != null) {
                body.append("- Build Type: ").append(feedback.appInfo.buildType).append("\n");
            }
            if (feedback.appInfo.flavor != null) {
                body.append("- Flavor: ").append(feedback.appInfo.flavor).append("\n");
            }
            if (feedback.appInfo.versionCode != null) {
                body.append("- Version Code: ").append(feedback.appInfo.versionCode).append("\n");
            }
            if (feedback.appInfo.versionName != null) {
                body.append("- Version Name: ").append(feedback.appInfo.versionName).append("\n");
            }
        }

        body.append("\n------ Device ------\n");
        if (feedback.deviceInfo != null) {
            body.append(feedback.deviceInfo.toString());
        }
        body.append("\n\n");

        if (mBodyFooter != null) {
            body.append("\n--").append(mBodyFooter);
        }

        intent.putExtra(Intent.EXTRA_TEXT, body.toString());

        final ComponentName componentName = intent.resolveActivity(mContext.getPackageManager());
        if (componentName != null) {
            //Add screenshot as attachment
            final ArrayList<Uri> attachmentsUris = new ArrayList<>();
            if (feedback.screenshotFileUri != null) {
                //Grant READ permission to the intent
                mContext.grantUriPermission(componentName.getPackageName(),
                        feedback.screenshotFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
                attachmentsUris.add(feedback.screenshotFileUri);
            }
            //Add logs file as attachment
            if (feedback.logsFileUri != null) {
                //Grant READ permission to the intent
                mContext.grantUriPermission(componentName.getPackageName(),
                        feedback.logsFileUri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
                attachmentsUris.add(feedback.logsFileUri);
            }
            if (!attachmentsUris.isEmpty()) {
                intent.putParcelableArrayListExtra(Intent.EXTRA_STREAM, attachmentsUris);
            }
            mContext.startActivity(intent);
        }

        return true;
    }
} 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);
    }

} public final class ContextUtils {

    private ContextUtils() {
    }

    /**
     * Gets a field from the project's BuildConfig. This is useful when, for example, flavors
     * are used at the project level to set custom fields.
     * <p/>
     * Workaround inspired from http://goo.gl/gKQqkC
     *
     * @param context   Used to find the correct file
     * @param fieldName The name of the field to access
     * @return The value of the field, or {@literal null} if the field is not found.
     */
    @Nullable
    public static Object getBuildConfigValue(@NonNull final Context context,
                                             @NonNull final String fieldName) {
        try {
            return Class
                    .forName(String.format("%s.BuildConfig", context.getPackageName()))
                    .getField(fieldName)
                    .get(null);
        } catch (final Exception e) {
            return null;
        }
    }

    @Nullable
    public static Activity scanForActivity(@NonNull final Context context) {
        if (context instanceof Activity) {
            return (Activity) context;
        } else if (context instanceof ContextWrapper) {
            return scanForActivity(((ContextWrapper) context).getBaseContext());
        } else {
            return null;
        }
    }
} public final class ViewUtils {

    public static final int COMPRESSION_QUALITY = 100;
    public static final int DEFAULT_BITMAP_WIDTH = 640;
    public static final int DEFAULT_BITMAP_HEIGHT = 480;

    private ViewUtils() {
    }

    /**
     * Convert the specified view to a drawable, if possible
     *
     * @param view the view to convert
     * @return the bitmap or {@code null} if the {@code view} is null
     */
    @Nullable
    public static Bitmap toBitmap(@Nullable final View view) {
        if (view == null) {
            return null;
        }
        final int width = view.getWidth();
        final int height = view.getHeight();
        final Bitmap bitmapToExport = Bitmap
                .createBitmap(width > 0 ? width : DEFAULT_BITMAP_WIDTH,
                        height > 0 ? height : DEFAULT_BITMAP_HEIGHT,
                        Bitmap.Config.ARGB_8888);
        final Canvas canvas = new Canvas(bitmapToExport);
        view.draw(canvas);
        return bitmapToExport;
    }

    /**
     * Export the given view to the specified {@code File}
     * @param context the context
     * @param view the view to export
     * @param file the file in which to save the view
     */
    public static void exportViewToFile(@NonNull final Context context,
                                        @NonNull final View view, @NonNull final File file) {
        final Bitmap bitmap = toBitmap(view);
        if (bitmap == null) {
            return;
        }
        exportBitmapToFile(context, bitmap, file);
    }

    /**
     * Export the given bitmap to the specified {@code File}
     * @param context the context
     * @param bitmap the bitmap to export
     * @param file the file in which to save the view
     */
    public static void exportBitmapToFile(@NonNull final Context context,
                                          @NonNull final Bitmap bitmap,
                                          @NonNull final File file) {
        OutputStream outputStream = null;
        try {
            outputStream = new BufferedOutputStream(new FileOutputStream(file, false));
            bitmap.compress(Bitmap.CompressFormat.PNG, COMPRESSION_QUALITY, outputStream);
            outputStream.flush();
        } catch (final IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (outputStream != null) {
                    outputStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
                //No Worries
            }
        }
    }

    private static int calculateInSampleSize(
            BitmapFactory.Options options, int reqWidth, int reqHeight) {
        // Raw height and width of image
        final int height = options.outHeight;
        final int width = options.outWidth;
        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            final int halfHeight = height / 2;
            final int halfWidth = width / 2;

            // Calculate the largest inSampleSize value that is a power of 2 and keeps both
            // height and width larger than the requested height and width.
            while ((halfHeight / inSampleSize) >= reqHeight
                    && (halfWidth / inSampleSize) >= reqWidth) {
                inSampleSize *= 2;
            }
        }

        return inSampleSize;
    }

    /**
     * Decode a given drawable resource with the specified dimensions
     *
     * @param res the app resources
     * @param resId the drawable resource ID
     * @param reqWidth the required width
     * @param reqHeight the required height
     * @return the bitmap
     */
    public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,
                                                         int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeResource(res, resId, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeResource(res, resId, options);
    }

    /**
     * Decode a given image file with the specified dimensions
     *
     * @param filePath the file resource path
     * @param reqWidth the required width
     * @param reqHeight the required height
     * @return the bitmap
     */
    public static Bitmap decodeSampledBitmapFromFilePath(String filePath,
                                                         int reqWidth, int reqHeight) {

        // First decode with inJustDecodeBounds=true to check dimensions
        final BitmapFactory.Options options = new BitmapFactory.Options();
        options.inJustDecodeBounds = true;
        BitmapFactory.decodeFile(filePath, options);

        // Calculate inSampleSize
        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        // Decode bitmap with inSampleSize set
        options.inJustDecodeBounds = false;
        return BitmapFactory.decodeFile(filePath, options);

    }
} @NonNull
public static CallbacksConfiguration getInstance(@Nullable final Context context) {
    if (SINGLETON == null) {
        SINGLETON = new CallbacksConfiguration(context);
    }
    return SINGLETON;
} public static final String APPLICATION_INFO_BUILD_CONFIG_BUILD_TYPE =
        "APPLICATION_INFO_BUILD_CONFIG_BUILD_TYPE"; 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_PACKAGE_NAME = "APPLICATION_INFO_PACKAGE_NAME"; 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 CALLER_ACTIVITY = "CALLER_ACTIVITY"; public static final String CONTENT_ERROR_TEXT = "CONTENT_ERROR_TEXT"; public static final String CONTENT_HINT = "CONTENT_HINT"; public static final String EXTRA_LAYOUT = "EXTRA_LAYOUT"; public static final String FILE_PROVIDER_AUTHORITY = "FILE_PROVIDER_AUTHORITY"; public static final String HEADER = "HEADER"; public static final String INCLUDE_LOGS_TEXT = "INCLUDE_LOGS_TEXT"; public static final String INCLUDE_SCREENSHOT_TEXT = "INCLUDE_SCREENSHOT_TEXT"; public static final String LOGS_CAPTURING_FEATURE_ENABLED = "LOGS_CAPTURING_FEATURE_ENABLED"; public static final String MESSAGE = "MESSAGE"; public static final String SCREENSHOT_FILE = "SCREENSHOT_FILE"; public static final String SCREENSHOT_HINT = "SCREENSHOT_HINT"; public static final String SCREENSHOT_TOUCH_TO_PREVIEW_HINT = "SCREENSHOT_PREVIEW_HINT"; public static final String SCREEN_CAPTURING_FEATURE_ENABLED = "SCREEN_CAPTURING_FEATURE_ENABLED"; public static final String SHARED_PREFERENCES = "SHARED_PREFS"; public static final String SHOW_KEYBOARD_ON_START = "SHOW_KEYBOARD_ON_START"; public static final String THEME = "THEME"; public static final String TOOLBAR_SUBTITLE_TEXT_COLOR = "TOOLBAR_SUBTITLE_TEXT_COLOR"; public static final String TOOLBAR_TITLE_TEXT_COLOR = "TOOLBAR_TITLE_TEXT_COLOR"; public static final String WINDOW_SUBTITLE = "WINDOW_SUBTITLE"; public static final String WINDOW_TITLE = "WINDOW_TITLE"; public static final String WORKING_DIR = "WORKING_DIR";
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;
import android.view.Window;
import androidx.annotation.ColorRes;
import androidx.annotation.DrawableRes;
import androidx.annotation.LayoutRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.StyleRes;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.rm3l.maoni.common.contract.Handler;
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.email.MaoniEmailListener;
import org.rm3l.maoni.ui.MaoniActivity;
import org.rm3l.maoni.utils.ContextUtils;
import org.rm3l.maoni.utils.ViewUtils;
import java.io.File;
import java.util.concurrent.atomic.AtomicBoolean;
import static org.rm3l.maoni.Maoni.CallbacksConfiguration.getInstance;
import static org.rm3l.maoni.ui.MaoniActivity.APPLICATION_INFO_BUILD_CONFIG_BUILD_TYPE;
import static org.rm3l.maoni.ui.MaoniActivity.APPLICATION_INFO_BUILD_CONFIG_DEBUG;
import static org.rm3l.maoni.ui.MaoniActivity.APPLICATION_INFO_BUILD_CONFIG_FLAVOR;
import static org.rm3l.maoni.ui.MaoniActivity.APPLICATION_INFO_PACKAGE_NAME;
import static org.rm3l.maoni.ui.MaoniActivity.APPLICATION_INFO_VERSION_CODE;
import static org.rm3l.maoni.ui.MaoniActivity.APPLICATION_INFO_VERSION_NAME;
import static org.rm3l.maoni.ui.MaoniActivity.CALLER_ACTIVITY;
import static org.rm3l.maoni.ui.MaoniActivity.CONTENT_ERROR_TEXT;
import static org.rm3l.maoni.ui.MaoniActivity.CONTENT_HINT;
import static org.rm3l.maoni.ui.MaoniActivity.EXTRA_LAYOUT;
import static org.rm3l.maoni.ui.MaoniActivity.FILE_PROVIDER_AUTHORITY;
import static org.rm3l.maoni.ui.MaoniActivity.HEADER;
import static org.rm3l.maoni.ui.MaoniActivity.INCLUDE_LOGS_TEXT;
import static org.rm3l.maoni.ui.MaoniActivity.INCLUDE_SCREENSHOT_TEXT;
import static org.rm3l.maoni.ui.MaoniActivity.LOGS_CAPTURING_FEATURE_ENABLED;
import static org.rm3l.maoni.ui.MaoniActivity.MESSAGE;
import static org.rm3l.maoni.ui.MaoniActivity.SCREENSHOT_FILE;
import static org.rm3l.maoni.ui.MaoniActivity.SCREENSHOT_HINT;
import static org.rm3l.maoni.ui.MaoniActivity.SCREENSHOT_TOUCH_TO_PREVIEW_HINT;
import static org.rm3l.maoni.ui.MaoniActivity.SCREEN_CAPTURING_FEATURE_ENABLED;
import static org.rm3l.maoni.ui.MaoniActivity.SHARED_PREFERENCES;
import static org.rm3l.maoni.ui.MaoniActivity.SHOW_KEYBOARD_ON_START;
import static org.rm3l.maoni.ui.MaoniActivity.THEME;
import static org.rm3l.maoni.ui.MaoniActivity.TOOLBAR_SUBTITLE_TEXT_COLOR;
import static org.rm3l.maoni.ui.MaoniActivity.TOOLBAR_TITLE_TEXT_COLOR;
import static org.rm3l.maoni.ui.MaoniActivity.WINDOW_SUBTITLE;
import static org.rm3l.maoni.ui.MaoniActivity.WINDOW_TITLE;
import static org.rm3l.maoni.ui.MaoniActivity.WORKING_DIR;
    /**
     * Start the Maoni Activity. If screenCapturingFeatureEnabled is true this will take a
     * screenshot of the dialog window
     *
     * @param dialog the caller dialog
     */
    public void start(@Nullable Dialog dialog) {
        Activity activity = ContextUtils.scanForActivity(dialog.getContext());
        start(activity, dialog.getWindow());
    }

    /**
     * Internal helper method used to start MaoniActivity
     *
     * @param callerActivity activity context
     * @param window window for screen (if enabled)
     */
    private void start(Activity callerActivity, Window window) {
        if (mUsed.getAndSet(true)) {
            this.clear();
            throw new UnsupportedOperationException(
                    "Maoni instance cannot be reused to start a new activity. " +
                            "Please build a new Maoni instance.");
        }

        if (callerActivity == null) {
            Log.d(LOG_TAG, "Target activity is undefined");
            return;
        }

        final Intent maoniIntent = new Intent(callerActivity, MaoniActivity.class);

        //Set app-related info
        final PackageManager packageManager = callerActivity.getPackageManager();
        try {
            if (packageManager != null) {
                final PackageInfo packageInfo = packageManager
                        .getPackageInfo(callerActivity.getPackageName(), 0);
                if (packageInfo != null) {
                    maoniIntent.putExtra(APPLICATION_INFO_VERSION_CODE, packageInfo.versionCode);
                    maoniIntent.putExtra(APPLICATION_INFO_VERSION_NAME, packageInfo.versionName);
                    maoniIntent.putExtra(APPLICATION_INFO_PACKAGE_NAME, packageInfo.packageName);
                }
            }
        } catch (final PackageManager.NameNotFoundException nnfe) {
            //No worries
            nnfe.printStackTrace();
        }
        final Object buildConfigDebugValue = ContextUtils.getBuildConfigValue(callerActivity,
                DEBUG);
        if (buildConfigDebugValue instanceof Boolean) {
            maoniIntent.putExtra(APPLICATION_INFO_BUILD_CONFIG_DEBUG,
                    (Boolean) buildConfigDebugValue);
        }
        final Object buildConfigFlavorValue = ContextUtils.getBuildConfigValue(callerActivity,
                FLAVOR);
        if (buildConfigFlavorValue != null) {
            maoniIntent.putExtra(APPLICATION_INFO_BUILD_CONFIG_FLAVOR,
                    buildConfigFlavorValue.toString());
        }
        final Object buildConfigBuildTypeValue = ContextUtils.getBuildConfigValue(callerActivity,
                BUILD_TYPE);
        if (buildConfigBuildTypeValue != null) {
            maoniIntent.putExtra(APPLICATION_INFO_BUILD_CONFIG_BUILD_TYPE,
                    buildConfigBuildTypeValue.toString());
        }

        maoniIntent.putExtra(FILE_PROVIDER_AUTHORITY, fileProviderAuthority);

        maoniIntent.putExtra(SHOW_KEYBOARD_ON_START, showKeyboardOnStart);

        maoniIntent.putExtra(WORKING_DIR,
                maoniWorkingDir != null ?
                        maoniWorkingDir.getAbsolutePath() : callerActivity.getCacheDir().getAbsolutePath());

        maoniIntent.putExtra(SCREEN_CAPTURING_FEATURE_ENABLED, screenCapturingFeatureEnabled);
        if (this.screenCapturingFeatureEnabled) {
            //Create screenshot file
            final File screenshotFile = new File(maoniWorkingDir != null ? maoniWorkingDir : callerActivity.getCacheDir(),
                MAONI_FEEDBACK_SCREENSHOT_FILENAME);
            ViewUtils.exportViewToFile(callerActivity, window.getDecorView(),
                screenshotFile);
            maoniIntent.putExtra(SCREENSHOT_FILE, screenshotFile.getAbsolutePath());

            if (screenshotHint != null) {
                maoniIntent.putExtra(SCREENSHOT_HINT, screenshotHint);
            }

            if (includeScreenshotText != null) {
                maoniIntent.putExtra(INCLUDE_SCREENSHOT_TEXT, includeScreenshotText);
            }

            if (touchToPreviewScreenshotText != null) {
                maoniIntent.putExtra(SCREENSHOT_TOUCH_TO_PREVIEW_HINT, touchToPreviewScreenshotText);
            }
        }

        maoniIntent.putExtra(CALLER_ACTIVITY, callerActivity.getClass().getCanonicalName());

        if (theme != null) {
            maoniIntent.putExtra(THEME, theme);
        }

        if (windowTitle != null) {
            maoniIntent.putExtra(WINDOW_TITLE, windowTitle);
        }

        if (windowSubTitle != null) {
            maoniIntent.putExtra(WINDOW_SUBTITLE, windowSubTitle);
        }

        if (windowTitleTextColor != null) {
            maoniIntent.putExtra(TOOLBAR_TITLE_TEXT_COLOR, windowTitleTextColor);
        }

        if (windowSubTitleTextColor != null) {
            maoniIntent.putExtra(TOOLBAR_SUBTITLE_TEXT_COLOR, windowSubTitleTextColor);
        }

        if (message != null) {
            maoniIntent.putExtra(MESSAGE, message);