com.bonsai.wallet32.PasscodeActivity.java Source code

Java tutorial

Introduction

Here is the source code for com.bonsai.wallet32.PasscodeActivity.java

Source

// Copyright (C) 2013-2014  Bonsai Software, Inc.
// 
// 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 com.bonsai.wallet32;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import android.annotation.SuppressLint;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.SharedPreferences;
import android.content.res.Resources;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.IBinder;
import android.preference.PreferenceManager;
import android.support.v4.app.DialogFragment;
import android.support.v7.app.ActionBarActivity;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.TextView;
import android.widget.Toast;
import hashengineering.groestlcoin.wallet32.R;

public class PasscodeActivity extends ActionBarActivity {

    private static final int MAX_PASSCODE_LENGTH = 32;

    private static Logger mLogger = LoggerFactory.getLogger(PasscodeActivity.class);

    private WalletApplication mApp;

    private enum State {
        PASSCODE_CREATE, PASSCODE_CONFIRM, PASSCODE_ENTER
    }

    private enum Action {
        ACTION_CREATE, ACTION_RESTORE, ACTION_PAIR, ACTION_LOGIN, ACTION_CHANGE, ACTION_VIEWSEED, ACTION_SHOWPAIRING
    }

    private Resources mRes;
    SharedPreferences mPrefs;

    private boolean mChangePasscode;
    private Action mAction;

    private boolean mShowPasscode;
    private State mState;
    private String mPasscode;
    private String mLastPasscode;

    private WalletService mWalletService;

    private boolean mIsPaused = false;
    private boolean mPasscodeWasInvalid = false;

    protected ServiceConnection mConnection = new ServiceConnection() {
        public void onServiceConnected(ComponentName className, IBinder binder) {
            mWalletService = ((WalletService.WalletServiceBinder) binder).getService();
            mLogger.info("WalletService bound");
        }

        public void onServiceDisconnected(ComponentName className) {
            mWalletService = null;
            mLogger.info("WalletService unbound");
        }

    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_passcode);

        mApp = (WalletApplication) getApplicationContext();

        mRes = getResources();

        // If we haven't entered through the lobby at some point
        // we've gotten here via the "recent activities" menu or
        // similar.  Go to the lobby ...
        //
        if (!mApp.hasEntered()) {
            mLogger.info("at passcode without entry; back to the lobby");

            // Go to the lobby and get logged in ...
            Intent intent = new Intent(this, LobbyActivity.class);
            startActivity(intent);
            finish();
        }

        Bundle bundle = getIntent().getExtras();

        String action = bundle.getString("action");
        if (action == null) {
            String msg = "missing action in PasscodeActivity";
            mLogger.error(msg);
            throw new RuntimeException(msg);
        } else if (action.equals("create")) {
            mAction = Action.ACTION_CREATE;
            mLogger.info("ACTION_CREATE");
        } else if (action.equals("restore")) {
            mAction = Action.ACTION_RESTORE;
            mLogger.info("ACTION_RESTORE");
        } else if (action.equals("pair")) {
            mAction = Action.ACTION_PAIR;
            mLogger.info("ACTION_PAIR");
        } else if (action.equals("login")) {
            mAction = Action.ACTION_LOGIN;
            mLogger.info("ACTION_LOGIN");
        } else if (action.equals("change")) {
            mAction = Action.ACTION_CHANGE;
            mLogger.info("ACTION_CHANGE");
        } else if (action.equals("viewseed")) {
            mAction = Action.ACTION_VIEWSEED;
            mLogger.info("ACTION_VIEWSEED");
        } else if (action.equals("showpairing")) {
            mAction = Action.ACTION_SHOWPAIRING;
            mLogger.info("ACTION_SHOWPAIRING");
        } else {
            String msg = "unknown action value " + action;
            mLogger.error(msg);
            throw new RuntimeException(msg);
        }

        TextView msgtv = (TextView) findViewById(R.id.message);

        switch (mAction) {
        // These actions verify the passcode.
        case ACTION_LOGIN:
        case ACTION_VIEWSEED:
        case ACTION_SHOWPAIRING:
            mState = State.PASSCODE_ENTER;
            mChangePasscode = false;
            msgtv.setText(R.string.passcode_enter);
            show_esthack(false);
            break;

        // These actions directly create a passcode.
        case ACTION_CREATE:
        case ACTION_RESTORE:
        case ACTION_PAIR:
            mState = State.PASSCODE_CREATE;
            mChangePasscode = false;
            msgtv.setText(R.string.passcode_create);
            show_esthack(true);
            break;

        // This action verifies the passcode and then creates a
        // new one.
        case ACTION_CHANGE:
            mState = State.PASSCODE_ENTER;
            mChangePasscode = true;
            msgtv.setText(R.string.passcode_current);
            show_esthack(false);
            break;
        }

        // Set the state of the show passcode checkbox.
        mPrefs = PreferenceManager.getDefaultSharedPreferences(this);
        mShowPasscode = mPrefs.getBoolean("pref_showPasscode", false);
        CheckBox chkbx = (CheckBox) findViewById(R.id.show_passcode);
        chkbx.setChecked(mShowPasscode);
        chkbx.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                mShowPasscode = isChecked;
                SharedPreferences.Editor editor = mPrefs.edit();
                editor.putBoolean("pref_showPasscode", mShowPasscode);
                editor.commit();
                setPasscode(mPasscode); // redisplay
            }
        });

        setPasscode("");

        mLogger.info("PasscodeActivity created");
    }

    @SuppressLint("InlinedApi")
    @Override
    protected void onResume() {
        super.onResume();

        mLogger.info("PasscodeActivity resumed");

        mApp.cancelBackgroundTimeout();

        // NOTE - this passcode activity can happen on initial create
        // and login and the WalletService will not be started at that
        // time.  This is ok.
        //
        // We need a WalletService binding for the case where we change
        // the passcode and in this case it will be running ...
        //
        bindService(new Intent(this, WalletService.class), mConnection, Context.BIND_ADJUST_WITH_ACTIVITY);

        mIsPaused = false;

        // Did we have an invalid passcode attempt complete while paused?
        if (mPasscodeWasInvalid) {
            mLogger.info("showing deferred passcode invalid dialog");
            mPasscodeWasInvalid = false;
            showPasscodeInvalidDialog();
        }
    }

    @Override
    protected void onPause() {
        mLogger.info("PasscodeActivity paused");

        mIsPaused = true;

        unbindService(mConnection);

        mApp.startBackgroundTimeout();

        super.onPause();
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        // Inflate the menu; this adds items to the action bar if it is present.
        getMenuInflater().inflate(R.menu.lobby_actions, menu);
        return super.onCreateOptionsMenu(menu);
    }

    @Override
    public boolean onOptionsItemSelected(MenuItem item) {
        // Handle presses on the action bar items
        Intent intent;
        switch (item.getItemId()) {
        case R.id.action_about:
            intent = new Intent(this, AboutActivity.class);
            startActivity(intent);
            return true;
        default:
            return super.onOptionsItemSelected(item);
        }
    }

    private void show_esthack(boolean show) {
        if (show) {
            findViewById(R.id.esthack_spacer).setVisibility(View.VISIBLE);
            findViewById(R.id.esthack_pair).setVisibility(View.VISIBLE);
        } else {
            findViewById(R.id.esthack_spacer).setVisibility(View.GONE);
            findViewById(R.id.esthack_pair).setVisibility(View.GONE);
        }
    }

    public void enterDigit(View view) {
        // Is the passcode at maximum length?
        if (mPasscode.length() == MAX_PASSCODE_LENGTH) {
            String msg = mRes.getString(R.string.passcode_error_maxlen);
            mLogger.warn(msg);
            Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
            return;
        }

        // Which button was clicked?
        String val;
        switch (view.getId()) {
        case R.id.button_1:
            val = "1";
            break;
        case R.id.button_2:
            val = "2";
            break;
        case R.id.button_3:
            val = "3";
            break;
        case R.id.button_4:
            val = "4";
            break;
        case R.id.button_5:
            val = "5";
            break;
        case R.id.button_6:
            val = "6";
            break;
        case R.id.button_7:
            val = "7";
            break;
        case R.id.button_8:
            val = "8";
            break;
        case R.id.button_9:
            val = "9";
            break;
        case R.id.button_0:
            val = "0";
            break;
        default:
            val = "?";
            break;
        }

        // Update the textview.
        setPasscode(mPasscode + val);
    }

    public void deleteDigit(View view) {
        int len = mPasscode.length();
        if (len == 0)
            return; // Nothing to do here.
        else
            setPasscode(mPasscode.substring(0, len - 1)); // Strip last.
    }

    public void clearPasscode(View view) {
        setPasscode(""); // Clear the string.
    }

    public void submitPasscode(View view) {
        // We don't currently allow empty passcodes.
        // If we do, we'll have to side-step the keyCrypter.deriveKey
        // step because it doesn't like empty passcodes ...
        if (mPasscode.length() == 0) {
            showErrorDialog(mRes.getString(R.string.passcode_errortitle), mRes.getString(R.string.passcode_empty));
            return;
        }

        switch (mState) {
        case PASSCODE_CREATE:
            confirmPasscode();
            break;
        case PASSCODE_CONFIRM:
            checkPasscode();
            break;
        case PASSCODE_ENTER:
            validatePasscode();
            break;
        }
    }

    // We're creating a passcode and it's been entered once.
    private void confirmPasscode() {
        // Stash the first version of the passcode.
        mLastPasscode = mPasscode;

        // Clear the passcode field.
        setPasscode(""); // Clear the string.

        // Ask the user to confirm it.
        TextView msgtv = (TextView) findViewById(R.id.message);
        msgtv.setText(R.string.passcode_confirm);

        mState = State.PASSCODE_CONFIRM;
    }

    // We're creating a passcode and it's been entered a second time.
    private void checkPasscode() {
        // Do they match?
        if (mPasscode.equals(mLastPasscode)) {
            // They matched ... setup async
            new SetupPasscodeTask().execute(mPasscode);
        }

        else {
            // Didn't match, try again ...

            showErrorDialog(mRes.getString(R.string.passcode_errortitle),
                    mRes.getString(R.string.passcode_mismatch));
            // Clear the passcode.
            setPasscode(""); // Clear the string.

            // Ask the user to create again.
            TextView msgtv = (TextView) findViewById(R.id.message);
            msgtv.setText(R.string.passcode_create);

            mState = State.PASSCODE_CREATE;
        }
    }

    private void setupComplete() {

        Intent intent;

        // In all cases we are effectively logged in now.
        mApp.setLoggedIn();

        switch (mAction) {
        case ACTION_CREATE:
            // Create the wallet.
            WalletUtil.createWallet(getApplicationContext());

            // Spin up the WalletService.
            Intent svcintent = new Intent(this, WalletService.class);
            Bundle bundle = new Bundle();
            bundle.putString("SyncState", "CREATED");
            svcintent.putExtras(bundle);
            startService(svcintent);

            intent = new Intent(this, ViewSeedActivity.class);
            Bundle bundle2 = new Bundle();
            bundle2.putBoolean("showDone", true);
            intent.putExtras(bundle2);
            startActivity(intent);
            break;

        case ACTION_RESTORE:
            intent = new Intent(this, RestoreWalletActivity.class);
            startActivity(intent);
            break;

        case ACTION_PAIR:
            intent = new Intent(this, PairWalletActivity.class);
            startActivity(intent);
            break;

        case ACTION_CHANGE:
            // We're all set, back to where we came from.
            break;

        default:
            // Shouldn't ever get here.
            String msg = "unexpected action in setupComplete";
            mLogger.error(msg);
            throw new RuntimeException(msg);
        }

        // And we're done here ...
        finish();
    }

    // We need to validate the passcode.
    private void validatePasscode() {
        new ValidatePasscodeTask().execute(mPasscode);
    }

    private void showPasscodeInvalidDialog() {
        showErrorDialog(mRes.getString(R.string.passcode_errortitle), mRes.getString(R.string.passcode_invalid));

        // Ask the user to create again.
        TextView msgtv = (TextView) findViewById(R.id.message);
        msgtv.setText(R.string.passcode_enter);
    }

    private void validateComplete(boolean isValid) {

        if (!isValid) {
            mLogger.info("passcode invalid");

            // Clear the passcode.
            setPasscode(""); // Clear the string.

            mState = State.PASSCODE_ENTER;

            // If we are paused we defer the dialog to when we
            // are resumed ...
            //
            if (!mIsPaused) {
                showPasscodeInvalidDialog();
            } else {
                mLogger.info("deferring passcode invalid dialog");
                mPasscodeWasInvalid = true;
            }

            return;
        }

        // The passcode was valid.
        mApp.setPasscodeValidTimestamp();

        switch (mAction) {
        case ACTION_LOGIN:
            // Spin up the WalletService.
            Intent svcintent = new Intent(this, WalletService.class);
            Bundle bundle = new Bundle();
            bundle.putString("SyncState", "STARTUP");
            svcintent.putExtras(bundle);
            startService(svcintent);

            mApp.setLoggedIn();

            // Off to the main activity.
            Intent intent = new Intent(this, MainActivity.class);
            startActivity(intent);

            // And we're done with this activity.
            finish();
            break;

        case ACTION_CHANGE:
            // Now we're ready to create a new passcode.
            mState = State.PASSCODE_CREATE;
            TextView msgtv = (TextView) findViewById(R.id.message);
            msgtv.setText(R.string.passcode_create);
            setPasscode("");
            show_esthack(true);
            return;

        case ACTION_VIEWSEED:
            // Off to view the seed.
            Intent intent2 = new Intent(this, ViewSeedActivity.class);
            intent2.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
            startActivity(intent2);

            // And we're done with this activity.
            finish();
            break;

        case ACTION_SHOWPAIRING:
            // Off to view the pairing code.
            Intent intent3 = new Intent(this, ShowPairingActivity.class);
            intent3.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);
            startActivity(intent3);

            // And we're done with this activity.
            finish();
            break;
        }
    }

    // Set the passcode, add decorations, optionally hide values.
    private void setPasscode(String val) {
        mPasscode = val;
        StringBuilder bldr = new StringBuilder();
        int len = val.length();
        for (int ii = 0; ii < len; ii += 4) {
            if (ii != 0)
                bldr.append("-");
            int end = (ii + 4 > len) ? len : ii + 4;
            if (mShowPasscode) {
                bldr.append(val.substring(ii, end));
            } else {
                for (int jj = ii; jj < end; ++jj)
                    bldr.append("*");
            }
        }
        TextView pctv = (TextView) findViewById(R.id.passcode);
        pctv.setText(bldr.toString());

        // Update the estimated hack cost.
        String esthackstr = "$" + String.format("%.2f", esthack(len));
        TextView ehtv = (TextView) findViewById(R.id.esthack_value);
        ehtv.setText(esthackstr);
    }

    private double esthack(int len) {
        // (1*10^len) * C / R

        double nscrypt = Math.pow(10.0, len);
        double cost_per_host_second = 2.314e-8;
        double scrypt_per_host_second = 20;

        return nscrypt * cost_per_host_second / scrypt_per_host_second;
    }

    public static class MyDialogFragment extends DialogFragment {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setRetainInstance(true);
        }

        @Override
        public Dialog onCreateDialog(Bundle savedInstanceState) {
            super.onCreateDialog(savedInstanceState);
            String msg = getArguments().getString("msg");
            String title = getArguments().getString("title");
            boolean hasOK = getArguments().getBoolean("hasOK");
            AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
            builder.setTitle(title);
            builder.setMessage(msg);
            if (hasOK) {
                builder.setPositiveButton(R.string.base_error_ok, new DialogInterface.OnClickListener() {
                    public void onClick(DialogInterface di, int id) {
                    }
                });
            }
            return builder.create();
        }
    }

    protected DialogFragment showErrorDialog(String title, String msg) {
        DialogFragment df = new MyDialogFragment();
        Bundle args = new Bundle();
        args.putString("title", title);
        args.putString("msg", msg);
        args.putBoolean("hasOK", true);
        df.setArguments(args);
        df.show(getSupportFragmentManager(), "error");
        return df;
    }

    protected DialogFragment showModalDialog(String title, String msg) {
        DialogFragment df = new MyDialogFragment();
        Bundle args = new Bundle();
        args.putString("title", title);
        args.putString("msg", msg);
        args.putBoolean("hasOK", false);
        df.setArguments(args);
        df.setCancelable(false);
        df.show(getSupportFragmentManager(), "note");
        return df;
    }

    private class SetupPasscodeTask extends AsyncTask<String, Void, Void> {
        DialogFragment df;

        @Override
        protected void onPreExecute() {
            String msg = mAction == Action.ACTION_CHANGE ? mRes.getString(R.string.passcode_waitchange)
                    : mRes.getString(R.string.passcode_waitsetup);
            df = showModalDialog(mRes.getString(R.string.passcode_waittitle), msg);
        }

        protected Void doInBackground(String... arg0) {
            String passcode = arg0[0];
            // This takes a while (scrypt) ...
            WalletUtil.setPasscode(getApplicationContext(), mWalletService, passcode, mChangePasscode);
            return null;
        }

        @Override
        protected void onPostExecute(Void result) {
            df.dismissAllowingStateLoss();
            setupComplete();
        }
    }

    private class ValidatePasscodeTask extends AsyncTask<String, Void, Boolean> {

        DialogFragment df;

        @Override
        protected void onPreExecute() {
            String msg = mAction == Action.ACTION_LOGIN ? mRes.getString(R.string.passcode_waitdecrypt)
                    : mRes.getString(R.string.passcode_waitvalidate);
            df = showModalDialog(mRes.getString(R.string.passcode_waittitle), msg);
        }

        protected Boolean doInBackground(String... arg0) {
            String passcode = arg0[0];
            // This takes a while (scrypt) ...
            return WalletUtil.passcodeValid(getApplicationContext(), passcode);
        }

        @Override
        protected void onPostExecute(Boolean result) {
            df.dismissAllowingStateLoss();
            validateComplete(result.booleanValue());
        }
    }
}

// Local Variables:
// mode: java
// c-basic-offset: 4
// tab-width: 4
// End: