com.mycelium.wallet.activity.export.DecryptPrivateKeyActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.mycelium.wallet.activity.export.DecryptPrivateKeyActivity.java

Source

/*
 * Copyright 2013 Megion Research and Development GmbH
 *
 * Licensed under the Microsoft Reference Source License (MS-RSL)
 *
 * This license governs use of the accompanying software. If you use the software, you accept this license.
 * If you do not accept the license, do not use the software.
 *
 * 1. Definitions
 * The terms "reproduce," "reproduction," and "distribution" have the same meaning here as under U.S. copyright law.
 * "You" means the licensee of the software.
 * "Your company" means the company you worked for when you downloaded the software.
 * "Reference use" means use of the software within your company as a reference, in read only form, for the sole purposes
 * of debugging your products, maintaining your products, or enhancing the interoperability of your products with the
 * software, and specifically excludes the right to distribute the software outside of your company.
 * "Licensed patents" means any Licensor patent claims which read directly on the software as distributed by the Licensor
 * under this license.
 *
 * 2. Grant of Rights
 * (A) Copyright Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive,
 * worldwide, royalty-free copyright license to reproduce the software for reference use.
 * (B) Patent Grant- Subject to the terms of this license, the Licensor grants you a non-transferable, non-exclusive,
 * worldwide, royalty-free patent license under licensed patents for reference use.
 *
 * 3. Limitations
 * (A) No Trademark License- This license does not grant you any rights to use the Licensors name, logo, or trademarks.
 * (B) If you begin patent litigation against the Licensor over patents that you think may apply to the software
 * (including a cross-claim or counterclaim in a lawsuit), your license to the software ends automatically.
 * (C) The software is licensed "as-is." You bear the risk of using it. The Licensor gives no express warranties,
 * guarantees or conditions. You may have additional consumer rights under your local laws which this license cannot
 * change. To the extent permitted under your local laws, the Licensor excludes the implied warranties of merchantability,
 * fitness for a particular purpose and non-infringement.
 */

package com.mycelium.wallet.activity.export;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.v4.app.Fragment;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.WindowManager;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;

import com.google.common.base.CharMatcher;
import com.google.common.base.Function;
import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import com.mrd.bitlib.crypto.MrdExport;
import com.mrd.bitlib.crypto.MrdExport.DecodingException;
import com.mrd.bitlib.crypto.MrdExport.V1.EncryptionParameters;
import com.mrd.bitlib.crypto.MrdExport.V1.InvalidChecksumException;
import com.mrd.bitlib.crypto.MrdExport.V1.KdfParameters;
import com.mycelium.wallet.MbwManager;
import com.mycelium.wallet.R;
import com.mycelium.wallet.UserFacingException;
import com.mycelium.wallet.activity.TextNormalizer;
import com.mycelium.wallet.service.MrdKeyStretchingTask;
import com.mycelium.wallet.service.ServiceTask;
import com.mycelium.wallet.service.ServiceTaskStatusEx;
import com.mycelium.wallet.service.ServiceTaskStatusEx.State;
import com.mycelium.wallet.service.TaskExecutionServiceController;
import com.mycelium.wallet.service.TaskExecutionServiceController.TaskExecutionServiceCallback;

public class DecryptPrivateKeyActivity extends Activity implements TaskExecutionServiceCallback {

    public static final CharMatcher LETTERS = CharMatcher.inRange('a', 'z').or(CharMatcher.inRange('A', 'Z'));
    public static final Splitter SPLIT_3 = Splitter.fixedLength(3);
    public static final Joiner JOIN_SPACE = Joiner.on(' ');
    public static final Function<String, String> PASS_NORMALIZER = new Function<String, String>() {
        @Override
        public String apply(String input) {
            String onlyLetters = LETTERS.retainFrom(input);
            return JOIN_SPACE.join(SPLIT_3.split(onlyLetters)).toUpperCase();
        }
    };
    public static final int PASSLENGTH_WITHSPACES = 21;

    private EditText passwordEdit;

    public static void callMe(Activity currentActivity, String encryptedPrivateKey, int requestCode) {
        Intent intent = new Intent(currentActivity, DecryptPrivateKeyActivity.class);
        intent.putExtra("encryptedPrivateKey", encryptedPrivateKey);
        currentActivity.startActivityForResult(intent, requestCode);
    }

    public static void callMe(Fragment fragment, String encryptedPrivateKey, int requestCode) {
        Intent intent = new Intent(fragment.getActivity(), DecryptPrivateKeyActivity.class);
        intent.putExtra("encryptedPrivateKey", encryptedPrivateKey);
        fragment.startActivityForResult(intent, requestCode);
    }

    private TaskExecutionServiceController _taskExecutionServiceController;
    private ServiceTaskStatusEx _taskStatus;
    private boolean _oomDetected;
    private ProgressUpdater _progressUpdater;
    private String _encryptedPrivateKey;
    private MbwManager _mbwManager;
    private MrdExport.V1.Header _header;
    private EncryptionParameters _encryptionParameters;

    /**
     * Called when the activity is first created.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        this.getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
                WindowManager.LayoutParams.FLAG_FULLSCREEN);
        super.onCreate(savedInstanceState);
        setContentView(R.layout.export_decrypt_private_key_activity);

        _mbwManager = MbwManager.getInstance(this);
        // Get parameters
        _encryptedPrivateKey = getIntent().getStringExtra("encryptedPrivateKey");

        // Decode the header of the encrypted private key
        try {
            _header = MrdExport.V1.extractHeader(_encryptedPrivateKey);
        } catch (DecodingException e) {
            Toast.makeText(this, R.string.unrecognized_format, Toast.LENGTH_SHORT).show();
            finish();
            return;
        }

        _taskExecutionServiceController = new TaskExecutionServiceController();
        _taskExecutionServiceController.bind(this, this);

        _progressUpdater = new ProgressUpdater();

        passwordEdit = (EditText) findViewById(R.id.password);
        passwordEdit.addTextChangedListener(new TextNormalizer(PASS_NORMALIZER, passwordEdit));

        passwordEdit.addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {

            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {

            }

            @Override
            public void afterTextChanged(Editable s) {
                if (isValidPassword(s)) {
                    startKeyStretching();
                }
                updatePasswordText();
            }
        });

        if (savedInstanceState != null) {
            setPassword(savedInstanceState.getString("password"));
        }

        updatePasswordText();

        showKeyboardOrStartStretching();
    }

    private void updatePasswordText() {

        if (getPassword().length() != MrdExport.V1.V1_PASSPHRASE_LENGTH + 1) {
            ((TextView) findViewById(R.id.tvStatus)).setText(R.string.import_decrypt_key_enter_password);
            ((TextView) findViewById(R.id.tvStatus))
                    .setBackgroundColor(getResources().getColor(R.color.transparent));
        } else if (MrdExport.isChecksumValid(getPassword())) {
            // Leave the status at what it is, it is updated by the progress
        } else {
            ((TextView) findViewById(R.id.tvStatus)).setText(R.string.import_decrypt_key_invalid_checksum);
            ((TextView) findViewById(R.id.tvStatus)).setBackgroundColor(getResources().getColor(R.color.red));
        }

    }

    private void showKeyboardOrStartStretching() {
        _encryptionParameters = null;

        if (this.isFinishing()) {
            return;
        }
        if (isValidPassword(passwordEdit.getText())) {
            startKeyStretching();
        } else {
            showKeyboard();
        }
    }

    private boolean isValidPassword(CharSequence s) {
        return allCharsEntered(s) && MrdExport.isChecksumValid(getPassword());
    }

    private boolean allCharsEntered(CharSequence s) {
        return s.length() == PASSLENGTH_WITHSPACES;
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        outState.putString("password", getPassword());
        super.onSaveInstanceState(outState);
    }

    @Override
    protected void onResume() {
        _progressUpdater.start();
        super.onResume();
    }

    @Override
    protected void onPause() {
        _progressUpdater.stop();
        super.onPause();
    }

    @Override
    protected void onDestroy() {
        if (_taskExecutionServiceController != null) {
            _taskExecutionServiceController.terminate();
            _taskExecutionServiceController.unbind(this);
        }
        super.onDestroy();
    }

    public String getPassword() {
        Editable text = passwordEdit.getText();
        if (text == null)
            return "";
        return LETTERS.retainFrom(text);
    }

    public void setPassword(String _enteredText) {
        passwordEdit.setText(_enteredText);
    }

    class ProgressUpdater implements Runnable {
        Handler _handler;

        ProgressUpdater() {
            _handler = new Handler();
        }

        public void start() {
            _handler.post(this);
        }

        public void stop() {
            _handler.removeCallbacks(this);
        }

        /**
         * Update the percentage of work completed for key stretching and pdf
         * generation
         */
        @Override
        public void run() {

            if (_oomDetected) {
                ((TextView) findViewById(R.id.tvProgress)).setText("");
                ((TextView) findViewById(R.id.tvStatus)).setText(R.string.out_of_memory_error);
                return;
            }

            if (_taskStatus == null || _taskStatus.state == State.NOTRUNNING) {
                ((TextView) findViewById(R.id.tvProgress)).setText("");
            } else if (_taskStatus.state == State.RUNNING) {
                ((TextView) findViewById(R.id.tvProgress)).setText("" + (int) (_taskStatus.progress * 100) + "%");
                ((TextView) findViewById(R.id.tvStatus)).setText(_taskStatus.statusMessage);

            }

            _taskExecutionServiceController.requestStatus();

            // Reschedule
            _handler.postDelayed(this, 300);
        }

    }

    private void startKeyStretching() {
        hideKeyboard();

        ((TextView) findViewById(R.id.tvStatus)).setBackgroundColor(getResources().getColor(R.color.transparent));
        String password = getPassword().substring(0, MrdExport.V1.V1_PASSPHRASE_LENGTH);
        KdfParameters kdfParameters = KdfParameters.fromPassphraseAndHeader(password, _header);

        if (_taskExecutionServiceController != null) {
            _taskExecutionServiceController.terminate();
            _taskExecutionServiceController.unbind(this);
        }
        _taskExecutionServiceController = new TaskExecutionServiceController();
        _taskExecutionServiceController.bind(this, this);
        MrdKeyStretchingTask task = new MrdKeyStretchingTask(kdfParameters, this);
        _taskExecutionServiceController.start(task);
    }

    private void showKeyboard() {
        passwordEdit.requestFocus();
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.showSoftInputFromInputMethod(passwordEdit.getWindowToken(), 0);
    }

    private void hideKeyboard() {
        InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
        imm.hideSoftInputFromWindow(passwordEdit.getWindowToken(), 0);
    }

    private void tryDecrypt(EncryptionParameters parameters) {
        try {
            String key = MrdExport.V1.decrypt(parameters, _encryptedPrivateKey, _mbwManager.getNetwork());
            // Success, return result
            Intent result = new Intent();
            result.putExtra("base58Key", key);
            result.putExtra("encryptionParameters", parameters);
            setResult(RESULT_OK, result);
            this.finish();
        } catch (InvalidChecksumException e) {
            // Invalid password, ask the user if he wishes to retry
            showRetryDialog();
        } catch (DecodingException e) {
            Toast.makeText(this, R.string.unrecognized_format, Toast.LENGTH_SHORT).show();
            finish();
        }
    }

    private void showRetryDialog() {
        AlertDialog.Builder builder = new AlertDialog.Builder(this);

        builder.setTitle(getString(R.string.invalid_password));
        builder.setMessage(getString(R.string.retry_password_question));

        builder.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {

            public void onClick(DialogInterface dialog, int which) {
                setPassword("");
                updatePasswordText();
                showKeyboardOrStartStretching();
                dialog.dismiss();
            }

        });

        builder.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {

            @Override
            public void onClick(DialogInterface dialog, int which) {
                dialog.dismiss();
                DecryptPrivateKeyActivity.this.finish();
            }
        });

        AlertDialog alert = builder.create();
        alert.show();
    }

    @Override
    public void onStatusReceived(ServiceTaskStatusEx status) {
        _taskStatus = status;
        if (_taskStatus != null && _taskStatus.state == State.FINISHED && _encryptionParameters == null) {
            _taskExecutionServiceController.requestResult();
        }
    }

    @Override
    public void onResultReceived(ServiceTask<?> result) {
        _taskExecutionServiceController.terminate();
        ((TextView) findViewById(R.id.tvProgress)).setText("");

        MrdKeyStretchingTask task = (MrdKeyStretchingTask) result;
        try {
            _encryptionParameters = task.getResult();
            tryDecrypt(_encryptionParameters);
        } catch (UserFacingException e) {
            _oomDetected = true;
            _mbwManager.reportIgnoredException(e);
        }
    }

}