package com.greenaddress.greenbits.ui.preferences;
import android.app.Activity;
import android.app.Dialog;
import android.content.Intent;
import android.os.Bundle;
import android.preference.CheckBoxPreference;
import android.preference.EditTextPreference;
import android.preference.Preference;
import android.text.InputType;
import android.text.TextUtils;
import android.view.View;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
import com.afollestad.materialdialogs.DialogAction;
import com.afollestad.materialdialogs.MaterialDialog;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.greenaddress.greenbits.GaService;
import com.greenaddress.greenbits.ui.CB;
import com.greenaddress.greenbits.ui.UI;
import com.greenaddress.greenbits.ui.R;
import com.greenaddress.greenbits.ui.TwoFactorActivity;
import java.util.concurrent.Callable;
import java.util.HashMap;
import java.util.Map;
public class TwoFactorPreferenceFragment extends GAPreferenceFragment {
private static final int REQUEST_ENABLE_2FA = 0;
private static final String NLOCKTIME_EMAILS = "NLocktimeEmails";
private String mMethod; // Current 2FA Method
private Map<String, String> mLocalizedMap; // 2FA method to localized description
private EditTextPreference mLimitsPref;
private CheckBoxPreference getPref(final String method) {
return find("twoFac" + method);
}
private boolean isEnabled(final Map<?, ?> twoFacConfig, final String method) {
return twoFacConfig.get(method.toLowerCase()).equals(true);
}
private boolean haveAny2FA(final Map<?, ?> twoFacConfig) {
return twoFacConfig != null && (Boolean) twoFacConfig.get("any");
}
private CheckBoxPreference setupCheckbox(final Map<?, ?> twoFacConfig, final String method) {
final CheckBoxPreference c = getPref(method);
if (method.equals(NLOCKTIME_EMAILS))
c.setChecked(isNlocktimeConfig(true));
else
c.setChecked(isEnabled(twoFacConfig, method));
c.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(final Preference p, final Object newValue) {
if (method.equals(NLOCKTIME_EMAILS))
setNlocktimeConfig((Boolean) newValue);
else
prompt2FAChange(method, (Boolean) newValue);
return false;
}
});
return c;
}
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mLocalizedMap = UI.getTwoFactorLookup(getResources());
addPreferencesFromResource(R.xml.preference_twofactor);
setHasOptionsMenu(true);
final Map<?, ?> twoFacConfig = mService.getTwoFactorConfig();
if (twoFacConfig == null || twoFacConfig.isEmpty()) {
final GaPreferenceActivity activity = (GaPreferenceActivity) getActivity();
activity.toast(R.string.err_send_not_connected_will_resume);
activity.finish();
return;
}
final CheckBoxPreference emailCB = setupCheckbox(twoFacConfig, "Email");
setupCheckbox(twoFacConfig, "Gauth");
setupCheckbox(twoFacConfig, "SMS");
setupCheckbox(twoFacConfig, "Phone");
mLimitsPref = find("twoFacLimits");
if (!GaService.IS_ELEMENTS) {
// Value -> satoshi conversion needs implementation & testing for
// non-Elements (currently it's simply float(str) * 100)
removePreference(mLimitsPref);
} else {
mLimitsPref.getEditText().setInputType(InputType.TYPE_CLASS_NUMBER | InputType.TYPE_NUMBER_FLAG_DECIMAL);
mLimitsPref.setOnPreferenceChangeListener(new Preference.OnPreferenceChangeListener() {
@Override
public boolean onPreferenceChange(final Preference preference, final Object newValue) {
return onLimitsPreferenceChange(preference, newValue);
}
});
// Can only set limits if at least one 2FA method is available
final boolean haveAny = haveAny2FA(twoFacConfig);
mLimitsPref.setEnabled(haveAny);
mLimitsPref.setSummary(haveAny ? R.string.twoFacLimitEnabled : R.string.twoFacLimitDisabled);
}
if (GaService.IS_ELEMENTS)
removePreference(getPref(NLOCKTIME_EMAILS));
else {
final CheckBoxPreference nlockCB = setupCheckbox(twoFacConfig, NLOCKTIME_EMAILS);
nlockCB.setEnabled(emailCB.isChecked());
}
}
private boolean isNlocktimeConfig(final Boolean enabled) {
Boolean b = false;
final Map<String, Object> outer;
outer = (Map) mService.getUserConfig("notifications_settings");
if (outer != null)
b = Boolean.TRUE.equals(outer.get("email_incoming")) &&
Boolean.TRUE.equals(outer.get("email_outgoing"));
return b.equals(enabled);
}
private void setNlocktimeConfig(final Boolean enabled) {
if (GaService.IS_ELEMENTS || isNlocktimeConfig(enabled))
return; // Nothing to do
final Map<String, Object> inner, outer;
inner = ImmutableMap.of("email_incoming", (Object) enabled,
"email_outgoing", (Object) enabled);
outer = ImmutableMap.of("notifications_settings", (Object) inner);
mService.setUserConfig(Maps.newHashMap(outer), true /* Immediate */);
getPref(NLOCKTIME_EMAILS).setChecked(enabled);
}
private void prompt2FAChange(final String method, final Boolean newValue) {
if (newValue) {
final Intent intent = new Intent(getActivity(), TwoFactorActivity.class);
intent.putExtra("method", method.toLowerCase());
mMethod = method;
startActivityForResult(intent, REQUEST_ENABLE_2FA);
return;
}
final boolean skipChoice = false;
final Dialog dlg = UI.popupTwoFactorChoice(getActivity(), mService, skipChoice,
new CB.Runnable1T<String>() {
@Override
public void run(final String withMethod) {
disable2FA(method, withMethod);
}
});
if (dlg != null)
dlg.show();
}
private void setLimit(final Long newValue, final Map<String, String> twoFacData) {
try {
mService.changeTxLimits(newValue, twoFacData);
} catch (final Exception e) {
e.printStackTrace();
UI.toast(getActivity(), "Failed", Toast.LENGTH_SHORT);
return;
}
mLimitsPref.setText(String.valueOf(((float) newValue) / 100));
}
private void disable2FA(final String method, final String withMethod) {
if (!withMethod.equals("gauth")) {
final Map<String, String> data = new HashMap<>();
data.put("method", method.toLowerCase());
mService.requestTwoFacCode(withMethod, "disable_2fa", data);
}
final View v = inflatePinDialog(withMethod);
final EditText codeText = UI.find(v, R.id.btchipPINValue);
UI.popup(this.getActivity(), "2FA")
.customView(v, true)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(final MaterialDialog dialog, final DialogAction which) {
mService.getExecutor().submit(new Callable<Void>() {
@Override
public Void call() {
disable2FAImpl(method, withMethod, UI.getText(codeText));
return null;
}
});
}
}).build().show();
}
private void disable2FAImpl(final String method, final String withMethod, final String code) {
try {
final Map<String, String> twoFacData = new HashMap<>();
twoFacData.put("method", withMethod);
twoFacData.put("code", code);
if (mService.disableTwoFactor(method.toLowerCase(), twoFacData)) {
getActivity().runOnUiThread(new Runnable() {
public void run() {
change2FA(method, false);
}
});
return;
}
} catch (final Exception e) {
// Toast below
}
UI.toast(getActivity(), "Error disabling 2FA", Toast.LENGTH_SHORT);
}
@Override
public void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (resultCode == Activity.RESULT_OK)
change2FA(mMethod, true);
else
super.onActivityResult(requestCode, resultCode, data);
}
private void change2FA(final String method, final Boolean checked) {
getPref(method).setChecked(checked);
if (method.equals("Email")) {
// Reset nlocktime prefs when the user changes email 2FA
setNlocktimeConfig(checked);
if (!GaService.IS_ELEMENTS)
getPref(NLOCKTIME_EMAILS).setEnabled(checked);
}
final boolean haveAny;
if (checked) {
// Simple case when enabling a new 2FA method
haveAny = true;
} else {
// If disabled, we know the services cached 2FA info is current
haveAny = haveAny2FA(mService.getTwoFactorConfig());
}
mLimitsPref.setEnabled(haveAny);
mLimitsPref.setSummary(haveAny ? R.string.twoFacLimitEnabled : R.string.twoFacLimitDisabled);
}
private View inflatePinDialog(final String withMethod) {
final View v = getActivity().getLayoutInflater().inflate(R.layout.dialog_btchip_pin, null, false);
final TextView promptText = UI.find(v, R.id.btchipPinPrompt);
promptText.setText(getString(R.string.twoFacProvideConfirmationCode,
mLocalizedMap.get(withMethod)));
return v;
}
private boolean onLimitsPreferenceChange(final Preference preference, final Object newValue) {
final Float limitF = Float.valueOf((String) newValue);
final long limit = (long) (limitF.floatValue() * 100);
final String existingLimit = mLimitsPref.getText();
if (!TextUtils.isEmpty(existingLimit) && limitF <= Float.valueOf(existingLimit)) {
setLimit(limit, null); // Don't require 2FA to lower the limit
return true;
}
final boolean skipChoice = false;
final Dialog dlg = UI.popupTwoFactorChoice(getActivity(), mService, skipChoice,
new CB.Runnable1T<String>() {
@Override
public void run(final String withMethod) {
final View v = inflatePinDialog(withMethod);
final EditText codeText = UI.find(v, R.id.btchipPINValue);
if (!withMethod.equals("gauth")) {
final Map<String, Object> data = new HashMap<>();
data.put("is_fiat", false);
data.put("per_tx", 0);
data.put("total", limit);
mService.requestTwoFacCode(withMethod, "change_tx_limits", data);
}
UI.popup(getActivity(), "2FA")
.customView(v, true)
.onPositive(new MaterialDialog.SingleButtonCallback() {
@Override
public void onClick(final MaterialDialog dialog, final DialogAction which) {
final Map<String, String> twoFacData = new HashMap<>();
twoFacData.put("method", withMethod);
twoFacData.put("code", UI.getText(codeText));
setLimit(limit, twoFacData);
}
}).build().show();
}
});
if (dlg != null)
dlg.show();
return false;
}
}