de.schildbach.wallet.ui.BackupWalletToSeedDialogFragment.java Source code

Java tutorial

Introduction

Here is the source code for de.schildbach.wallet.ui.BackupWalletToSeedDialogFragment.java

Source

/*
 * Copyright 2015 the original author or authors.
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

package de.schildbach.wallet.ui;

import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.SharedPreferences;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.Process;
import android.support.annotation.RequiresApi;
import android.support.v4.os.CancellationSignal;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.FragmentManager;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.TextView;

import org.bitcoinj.wallet.DeterministicSeed;
import org.bitcoinj.wallet.Wallet;
import org.dash.wallet.common.ui.DialogBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.params.KeyParameter;

import java.util.List;

import javax.annotation.Nullable;

import org.dash.wallet.common.Configuration;
import de.schildbach.wallet.WalletApplication;
import de.schildbach.wallet.ui.preference.PinRetryController;
import de.schildbach.wallet.ui.send.DecryptSeedTask;
import de.schildbach.wallet.ui.send.DeriveKeyTask;
import de.schildbach.wallet.ui.widget.FingerprintView;
import de.schildbach.wallet.ui.widget.UpgradeWalletDisclaimerDialog;
import de.schildbach.wallet.util.FingerprintHelper;
import de.schildbach.wallet.util.KeyboardUtil;
import de.schildbach.wallet_test.R;

/**
 * @author Andreas Schildbach
 */
public class BackupWalletToSeedDialogFragment extends DialogFragment
        implements SharedPreferences.OnSharedPreferenceChangeListener {

    private static final String FRAGMENT_TAG = BackupWalletToSeedDialogFragment.class.getName();
    private static final String ARGS_IS_UPGRADING = "is_upgrading";

    private FingerprintView fingerprintView;

    private CancellationSignal fingerprintCancellationSignal;

    public static void show(final FragmentManager fm) {
        final BackupWalletToSeedDialogFragment newFragment = new BackupWalletToSeedDialogFragment();
        Bundle args = new Bundle();
        args.putBoolean(ARGS_IS_UPGRADING, false);
        newFragment.setArguments(args);
        newFragment.show(fm, FRAGMENT_TAG);
    }

    public static void show(final FragmentManager fm, boolean isUpgrading) {
        final BackupWalletToSeedDialogFragment newFragment = new BackupWalletToSeedDialogFragment();

        Bundle args = new Bundle();
        args.putBoolean(ARGS_IS_UPGRADING, isUpgrading);
        newFragment.setArguments(args);
        newFragment.show(fm, FRAGMENT_TAG);
    }

    private AbstractWalletActivity activity;
    private WalletApplication application;
    private Wallet wallet;
    private PinRetryController pinRetryController;
    private Configuration config;

    @Nullable
    private AlertDialog dialog;

    private TextView seedView;
    private View privateKeyPasswordViewGroup;
    private EditText privateKeyPasswordView;
    private TextView privateKeyBadPasswordView;
    private View seedViewGroup;
    private Handler backgroundHandler;
    private HandlerThread backgroundThread;
    private CheckBox writtenDown;
    private Button showMnemonicSeedButton;

    private static final Logger log = LoggerFactory.getLogger(BackupWalletToSeedDialogFragment.class);

    @Override
    public void onAttach(final Activity activity) {
        super.onAttach(activity);

        this.activity = (AbstractWalletActivity) activity;
        this.application = (WalletApplication) activity.getApplication();
        this.wallet = application.getWallet();
        this.pinRetryController = new PinRetryController(activity);
        this.config = application.getConfiguration();
    }

    @Override
    public Dialog onCreateDialog(final Bundle savedInstanceState) {
        final View view = LayoutInflater.from(activity).inflate(R.layout.backup_wallet_to_seed_dialog, null);

        seedView = (TextView) view.findViewById(R.id.backup_wallet_dialog_seed);
        privateKeyPasswordViewGroup = view.findViewById(R.id.backup_wallet_seed_private_key_password_group);
        privateKeyPasswordView = (EditText) view.findViewById(R.id.backup_wallet_seed_private_key_password);
        privateKeyBadPasswordView = (TextView) view.findViewById(R.id.backup_wallet_seed_private_key_bad_password);
        showMnemonicSeedButton = (Button) view.findViewById(R.id.backup_wallet_seed_private_key_enter);
        seedViewGroup = view.findViewById(R.id.backup_wallet_seed_group);
        writtenDown = (CheckBox) view.findViewById(R.id.backup_wallet_seed_private_key_written_down);

        fingerprintView = view.findViewById(R.id.fingerprint_view);
        privateKeyPasswordViewGroup.setVisibility(wallet.isEncrypted() ? View.VISIBLE : View.GONE);

        privateKeyPasswordView.addTextChangedListener(privateKeyPasswordListener);
        showMnemonicSeedButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View v) {
                handleDecryptPIN();
            }
        });
        if (wallet.isEncrypted()) {
            backgroundThread = new HandlerThread("backgroundThread", Process.THREAD_PRIORITY_BACKGROUND);
            backgroundThread.start();
            backgroundHandler = new Handler(backgroundThread.getLooper());
            showPasswordViewGroup(true);
            updateView(false);
        } else {
            showMnemonicSeed(wallet.getActiveKeyChain().getSeed());
            updateView(true);
        }

        final DialogBuilder builder = new DialogBuilder(activity);
        builder.setTitle(R.string.export_keys_dialog_title);
        builder.setView(view);
        builder.setCancelable(false);
        builder.setPositiveButton(R.string.button_dismiss, new DialogInterface.OnClickListener() {
            @Override
            public void onClick(DialogInterface dialog, int which) {
                if (writtenDown.isChecked()) {
                    config.disarmBackupSeedReminder();
                }
                privateKeyPasswordView.removeTextChangedListener(privateKeyPasswordListener);
                if (getArguments().getBoolean(ARGS_IS_UPGRADING, false)
                        && activity instanceof UpgradeWalletDisclaimerDialog.OnUpgradeConfirmedListener) {
                    ((UpgradeWalletDisclaimerDialog.OnUpgradeConfirmedListener) activity).onUpgradeConfirmed();
                }
            }
        });

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            initFingerprintHelper();
        }

        return builder.create();
    }

    @Override
    public void onResume() {
        super.onResume();
        config.registerOnSharedPreferenceChangeListener(this);
    }

    @Override
    public void onPause() {
        config.unregisterOnSharedPreferenceChangeListener(this);
        super.onPause();
    }

    @Override
    public void onDismiss(final DialogInterface dialog) {
        this.dialog = null;
        if (fingerprintCancellationSignal != null) {
            fingerprintCancellationSignal.cancel();
        }
        super.onDismiss(dialog);
    }

    private final TextWatcher privateKeyPasswordListener = new TextWatcher() {
        @Override
        public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
            privateKeyBadPasswordView.setVisibility(View.GONE);
            showMnemonicSeedButton.setEnabled(true);
        }

        @Override
        public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {
        }

        @Override
        public void afterTextChanged(final Editable s) {
        }
    };

    @RequiresApi(api = Build.VERSION_CODES.M)
    private void initFingerprintHelper() {
        FingerprintHelper fingerprintHelper = new FingerprintHelper(getActivity());
        if (fingerprintHelper.init() && fingerprintHelper.isFingerprintEnabled()) {
            fingerprintView.setVisibility(View.VISIBLE);
            fingerprintCancellationSignal = new CancellationSignal();
            fingerprintHelper.getPassword(fingerprintCancellationSignal, new FingerprintHelper.Callback() {
                @Override
                public void onSuccess(String savedPass) {
                    privateKeyPasswordView.setText(savedPass);
                    handleDecryptPIN();
                }

                @Override
                public void onFailure(String message, boolean canceled, boolean exceededMaxAttempts) {
                    if (!canceled) {
                        fingerprintView.showError(exceededMaxAttempts);
                    }
                }

                @Override
                public void onHelp(int helpCode, String helpString) {
                    fingerprintView.showError(false);
                }
            });
        }
    }

    private void showMnemonicSeed(final DeterministicSeed seed) {
        StringBuilder wordlist = new StringBuilder(255);

        List<String> code = seed.getMnemonicCode();

        for (String word : code) {
            wordlist.append(word + " ");
        }
        seedView.setText(wordlist);
    }

    private void handleDecryptPIN() {
        if (wallet.isEncrypted()) {

            if (pinRetryController.isLocked()) {
                return;
            }

            showMnemonicSeedButton.setEnabled(false);
            showMnemonicSeedButton.setText(getText(R.string.encrypt_keys_dialog_state_decrypting));
            privateKeyPasswordView.setEnabled(false);

            final String pin = privateKeyPasswordView.getText().toString().trim();

            new DeriveKeyTask(backgroundHandler, application.scryptIterationsTarget()) {
                @Override
                protected void onSuccess(final KeyParameter encryptionKey, final boolean wasChanged) {
                    privateKeyBadPasswordView.setVisibility(View.GONE);
                    handleDecryptSeed(encryptionKey, pin);
                }
            }.deriveKey(wallet, pin);

        } else {

        }
    }

    private void handleDecryptSeed(final KeyParameter encryptionKey, final String pin) {
        if (wallet.isEncrypted()) {
            if (pinRetryController.isLocked()) {
                return;
            }
            showMnemonicSeedButton.setEnabled(false);
            new DecryptSeedTask(backgroundHandler) {
                @Override
                protected void onSuccess(final DeterministicSeed seed) {
                    pinRetryController.successfulAttempt();
                    showPasswordViewGroup(false);
                    showMnemonicSeed(seed);
                    updateView(true);
                }

                protected void onBadPassphrase() {
                    pinRetryController.failedAttempt(pin);
                    privateKeyBadPasswordView.setVisibility(View.VISIBLE);
                    privateKeyBadPasswordView.setText(getString(R.string.wallet_lock_wrong_pin,
                            pinRetryController.getRemainingAttemptsMessage()));
                    privateKeyPasswordView.setEnabled(true);
                    privateKeyPasswordView.requestFocus();
                    showMnemonicSeedButton.setText(getText(R.string.backup_wallet_to_seed_show_recovery_phrase));
                    updateView(false);
                }
            }.decryptSeed(wallet.getActiveKeyChain().getSeed(), wallet.getKeyCrypter(), encryptionKey);

        } else {

        }
    }

    private void showPasswordViewGroup(boolean show) {
        if (show) {
            seedViewGroup.setVisibility(View.GONE);
            privateKeyPasswordView.post(new Runnable() {
                @Override
                public void run() {
                    if (isAdded()) {
                        KeyboardUtil.showSoftKeyboard(getActivity(), privateKeyPasswordView);
                    }
                }
            });
        } else {
            seedViewGroup.setVisibility(View.VISIBLE);
            privateKeyPasswordViewGroup.setVisibility(View.GONE);
            KeyboardUtil.hideKeyboard(getActivity(), privateKeyPasswordView);
        }
    }

    @Override
    public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
        if (Configuration.PREFS_KEY_REMIND_BACKUP_SEED.equals(key))
            updateView(false);
    }

    private void updateView(boolean isUnlocked) {
        if (!config.remindBackupSeed() || getArguments().getBoolean(ARGS_IS_UPGRADING, false))
            writtenDown.setVisibility(View.GONE);
        else
            writtenDown.setVisibility(isUnlocked ? View.VISIBLE : View.GONE);
    }
}