com.bullmobi.base.ui.fragments.dialogs.FeedbackDialog.java Source code

Java tutorial

Introduction

Here is the source code for com.bullmobi.base.ui.fragments.dialogs.FeedbackDialog.java

Source

/*
 * Copyright (C) 2014 AChep@xda <ynkr.wang@gmail.com>
 *
 * This program 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 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA  02110-1301, USA.
 */
package com.bullmobi.base.ui.fragments.dialogs;

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.net.Uri;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.ActionBarActivity;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewStub;
import android.widget.AdapterView;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;

import com.bullmobi.message.Config;
import com.bullmobi.message.R;
import com.bullmobi.message.providers.LogAttachmentProvider;
import com.bullmobi.message.ui.DialogHelper;
import com.bullmobi.base.Build;
import com.bullmobi.base.Device;
import com.bullmobi.base.content.ConfigBase;
import com.bullmobi.base.providers.LogsProviderBase;
import com.bullmobi.base.utils.FileUtils;
import com.bullmobi.base.utils.IntentUtils;
import com.bullmobi.base.utils.PackageUtils;
import com.bullmobi.base.utils.ResUtils;
import com.bullmobi.base.utils.ToastUtils;
import com.bullmobi.base.utils.ViewUtils;
import com.bullmobi.base.utils.logcat.Logcat;
import com.afollestad.materialdialogs.MaterialDialog;

import org.json.JSONException;
import org.json.JSONObject;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;

import static com.bullmobi.base.Build.DEBUG;

/**
 * Feedback dialog fragment.
 * <p/>
 * Provides an UI for sending bugs & suggestions on my email.
 */
public class FeedbackDialog extends DialogFragment implements ConfigBase.OnConfigChangedListener {

    private static final String TAG = "FeedbackDialog";

    private View mFaqContainer;

    private Spinner mSpinner;
    private EditText mEditText;
    private CheckBox mAttachLogCheckBox;

    private final AdapterView.OnItemSelectedListener mListener = new AdapterView.OnItemSelectedListener() {
        @Override
        public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
            // Show "Attach log" checkbox only if the type
            // of this message is "Issue".
            ViewUtils.setVisible(mAttachLogCheckBox, position == 0);
        }

        @Override
        public void onNothingSelected(AdapterView<?> parent) {
            ViewUtils.setVisible(mAttachLogCheckBox, false);
        }
    };

    @Override
    public void onResume() {
        super.onResume();

        Config config = Config.getInstance();
        config.registerListener(this);

        updateFaqPanel(config.getTriggers().isHelpRead());
    }

    @Override
    public void onPause() {
        Config config = Config.getInstance();
        config.unregisterListener(this);

        super.onPause();
    }

    @Override
    public void onConfigChanged(@NonNull ConfigBase config, @NonNull String key, @NonNull Object value) {
        switch (key) {
        case Config.KEY_TRIG_HELP_READ:
            boolean read = (boolean) value;
            updateFaqPanel(read);
            break;
        }
    }

    @NonNull
    @Override
    public Dialog onCreateDialog(Bundle savedInstanceState) {
        Activity activity = getActivity();
        assert activity != null;

        MaterialDialog md = new MaterialDialog.Builder(activity).iconRes(R.drawable.ic_action_feedback_white)
                .title(R.string.feedback_dialog_title).customView(R.layout.feedback_dialog, true)
                .negativeText(android.R.string.cancel).positiveText(R.string.send)
                .callback(new MaterialDialog.ButtonCallback() {
                    @Override
                    public void onPositive(MaterialDialog dialog) {
                        Context context = getActivity();
                        CharSequence message = mEditText.getText();

                        if (isMessageLongEnough(message)) {
                            boolean attachLog = mAttachLogCheckBox.isChecked()
                                    && mAttachLogCheckBox.getVisibility() == View.VISIBLE;

                            int type = mSpinner.getSelectedItemPosition();
                            CharSequence title = createTitle(context, type);
                            CharSequence body = createBody(context, message);
                            send(title, body, attachLog);
                        } else {
                            String toastText = getString(R.string.feedback_error_msg_too_short,
                                    getMinMessageLength());
                            ToastUtils.showShort(context, toastText);
                        }
                    }

                    @Override
                    public void onNegative(MaterialDialog dialog) {
                        dismiss();
                    }
                }).autoDismiss(false).build();

        View view = md.getCustomView();
        assert view != null;
        mSpinner = (Spinner) view.findViewById(R.id.type);
        mSpinner.setOnItemSelectedListener(mListener);
        mEditText = (EditText) view.findViewById(R.id.message);
        mAttachLogCheckBox = (CheckBox) view.findViewById(R.id.checkbox);

        // Frequently asked questions panel
        Config.Triggers triggers = Config.getInstance().getTriggers();
        if (!triggers.isHelpRead())
            initFaqPanel((ViewGroup) view);

        return md;
    }

    /**
     * Initialize Frequently asked questions panel. This panel is here to reduce
     * the number of already answered questions.
     */
    private void initFaqPanel(@NonNull ViewGroup root) {
        mFaqContainer = ((ViewStub) root.findViewById(R.id.faq)).inflate();
        mFaqContainer.findViewById(R.id.faq).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ActionBarActivity activity = (ActionBarActivity) getActivity();
                DialogHelper.showHelpDialog(activity);
            }
        });
    }

    /**
     * Removes Frequently asked questions panel from the view
     * and sets {@link #mFaqContainer} to null.
     * After calling this method you no longer able to get panel back.
     */
    private void recycleFaqPanel() {
        ViewGroup viewGroup = (ViewGroup) mFaqContainer.getParent();
        viewGroup.removeView(mFaqContainer);
        mFaqContainer = null;
    }

    /**
     * {@link #recycleFaqPanel() Recycles} Frequently asked questions panel when it's not needed
     * anymore.
     *
     * @param isHelpRead {@code true} to recycle panel, {@code false} to do nothing.
     */
    private void updateFaqPanel(boolean isHelpRead) {
        if (mFaqContainer != null && isHelpRead) {
            recycleFaqPanel();
        }
    }

    private void send(@NonNull CharSequence title, @NonNull CharSequence body, boolean attachLog) {
        Activity context = getActivity();
        String[] recipients = { Build.SUPPORT_EMAIL };
        Intent intent = new Intent().putExtra(Intent.EXTRA_EMAIL, recipients).putExtra(Intent.EXTRA_SUBJECT, title)
                .putExtra(Intent.EXTRA_TEXT, body);

        if (attachLog) {
            attachLog(intent);
            intent.setAction(Intent.ACTION_SEND);
            intent.setType("message/rfc822");
        } else {
            intent.setAction(Intent.ACTION_SENDTO);
            intent.setData(Uri.parse("mailto:")); // only email apps should handle it
        }

        if (IntentUtils.hasActivityForThat(context, intent)) {
            startActivity(intent);
            dismiss();
        } else {
            ToastUtils.showLong(context, R.string.feedback_error_no_app);
        }
    }

    /**
     * Creates the title of the email.
     *
     * @param type one of the following types:
     *             0 - issue
     *             1 - suggestion
     *             2 - other
     * @return the title of the email.
     */
    @NonNull
    private CharSequence createTitle(@NonNull Context context, int type) {
        CharSequence osVersion = Device.API_VERSION_NAME_SHORT;
        CharSequence[] types = new CharSequence[] { "issue", "suggestion", "other" };
        return AboutDialog.getVersionName(context) + ": " + osVersion + ", " + types[type];
    }

    /**
     * Creates the body of the email. It automatically adds some
     * info about the device.
     *
     * @param msg the message that been typed by user.
     * @return the body of the email
     */
    @NonNull
    private CharSequence createBody(@NonNull Context context, @NonNull CharSequence msg) {
        final String extra;

        do {
            PackageInfo pi;
            try {
                pi = context.getPackageManager().getPackageInfo(PackageUtils.getName(context), 0);
            } catch (PackageManager.NameNotFoundException e) {
                extra = "There was an exception while getting my own package info.";
                break;
            }

            JSONObject obj = new JSONObject();
            try {
                Config config = Config.getInstance();

                // App related stuff
                obj.put("app_version_code", pi.versionCode);
                obj.put("app_version_name", pi.versionName);
                obj.put("app_timestamp", Build.TIME_STAMP);
                obj.put("app_is_debug", DEBUG);
                obj.put("app_is_help_read", config.getTriggers().isHelpRead());
                obj.put("app_launch_count", config.getTriggers().getLaunchCount());

                // Device related stuff
                obj.put("language", Locale.getDefault().getLanguage());
                obj.put("android_version_release", android.os.Build.VERSION.RELEASE);
                obj.put("android_version_sdk_int", android.os.Build.VERSION.SDK_INT);
                obj.put("android_build_display", android.os.Build.DISPLAY);
                obj.put("android_build_brand", android.os.Build.BRAND);
                obj.put("android_build_model", android.os.Build.MODEL);
            } catch (JSONException ignored) {
                extra = "There was an exception while building JSON.";
                break;
            }

            extra = obj.toString().replaceAll(",\"", ", \"");
        } while (false);

        return msg + "\n\nExtras (added automatically & do not change):\n" + extra;
    }

    private boolean isMessageLongEnough(@Nullable CharSequence message) {
        return message != null && message.length() >= getMinMessageLength();
    }

    private int getMinMessageLength() {
        return getResources().getInteger(R.integer.config_feedback_minMessageLength);
    }

    private void attachLog(@NonNull Intent intent) {
        Context context = getActivity();

        try {
            String log = Logcat.capture();
            if (log == null)
                throw new Exception("Failed to capture the logcat.");

            // Prepare cache directory.
            File cacheDir = context.getCacheDir();
            if (cacheDir == null)
                throw new Exception("Cache directory is inaccessible");
            File directory = new File(cacheDir, LogsProviderBase.DIRECTORY);
            FileUtils.deleteRecursive(directory); // Clean-up cache folder
            if (!directory.mkdirs())
                throw new Exception("Failed to create cache directory.");

            // Create log file.
            SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
            sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
            String fileName = "EasyNotification_log_" + sdf.format(new Date()) + ".txt";
            File file = new File(directory, fileName);

            // Write to the file.
            if (!FileUtils.writeToFile(file, log))
                throw new Exception("Failed to write log to the file.");

            // Put extra stream to the intent.
            Uri uri = Uri.parse("content://" + LogAttachmentProvider.AUTHORITY + "/" + fileName);
            intent.putExtra(Intent.EXTRA_STREAM, uri);
        } catch (Exception e) {
            String message = ResUtils.getString(getResources(), R.string.feedback_error_accessing_log,
                    e.getMessage());
            ToastUtils.showLong(context, message);
        }
    }

}