/* * Aegis Bitcoin Wallet - The secure Bitcoin wallet for Android * Copyright 2014 Bojan Simic and specularX.co, designed by Reuven Yamrom * * 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.aegiswallet; import android.app.AlarmManager; import android.app.Application; import android.app.Dialog; import android.content.Context; import android.content.Intent; import android.content.SharedPreferences; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.preference.PreferenceManager; import android.text.InputType; import android.util.Log; import android.view.View; import android.view.Window; import android.widget.Button; import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.aegiswallet.actions.MainActivity; import com.aegiswallet.helpers.secretshare.SecretShare; import com.aegiswallet.listeners.PasswordProvidedListener; import com.aegiswallet.listeners.WalletUpdateListener; import com.aegiswallet.objects.KeyCache; import com.aegiswallet.services.PeerBlockchainService; import com.aegiswallet.tasks.PopulateContactsTask; import com.aegiswallet.tasks.SendMessageTask; import com.aegiswallet.tasks.SendShamirValueTask; import com.aegiswallet.utils.Constants; import com.aegiswallet.utils.WalletUtils; import com.google.android.gms.common.ConnectionResult; import com.google.android.gms.common.api.GoogleApiClient; import com.google.android.gms.wearable.Wearable; import com.google.bitcoin.core.ECKey; import com.google.bitcoin.core.NetworkParameters; import com.google.bitcoin.core.Wallet; import com.google.bitcoin.store.UnreadableWalletException; import com.google.bitcoin.store.WalletProtobufSerializer; import com.google.bitcoin.wallet.WalletFiles; import org.joda.money.CurrencyUnit; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigInteger; import java.security.SecureRandom; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; /** * Created by bsimic on 2/12/14. */ public class PayBitsApplication extends Application { private String TAG = this.getClass().getName(); private Wallet wallet; private File walletFile; private Intent blockchainServiceIntent; private Intent blockchainServiceResetBlockchainIntent; private WalletUpdateListener walletListener; private KeyCache keyCache; private Dialog passwordDialog; private Dialog nfcDialog; private Uri backupFileUri; public static long lastReminderTime; private ArrayList<Map<String, String>> peopleList; private GoogleApiClient mGoogleApiClient; private SharedPreferences prefs; public static final NetworkParameters params = Constants.NETWORK_PARAMETERS; @Override public void onCreate() { walletFile = getFileStreamPath(Constants.WALLET_FILENAME_PROTOBUF); prefs = PreferenceManager.getDefaultSharedPreferences(this); migrateWalletToProtobuf(); loadWalletFromProtobuf(); keyCache = null; wallet.autosaveToFile(walletFile, 1, TimeUnit.SECONDS, new WalletAutosaveEventListener()); blockchainServiceResetBlockchainIntent = new Intent(PeerBlockchainService.ACTION_RESET_BLOCKCHAIN, null, this, PeerBlockchainService.class); blockchainServiceIntent = new Intent(this, PeerBlockchainService.class); peopleList = new ArrayList<Map<String, String>>(); PopulateContactsTask populateContactsTask = new PopulateContactsTask(getApplicationContext(), peopleList); populateContactsTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); setKeyDefaults(); mGoogleApiClient = new GoogleApiClient.Builder(this) .addConnectionCallbacks(new ConnectionCallbacks()) .addOnConnectionFailedListener(new ConnectionFailedListener()) .addApi(Wearable.API) .build(); if (null != mGoogleApiClient && !mGoogleApiClient.isConnected()) { mGoogleApiClient.connect(); } try { CurrencyUnit.registerCurrency("BTC", -1, 8, new ArrayList<String>()); } catch (IllegalArgumentException ex) { Log.d(TAG, ex.getMessage()); } } public void sendMessage(String type, String data){ SendMessageTask sendMessageTask = new SendMessageTask(mGoogleApiClient, type, data, prefs); sendMessageTask.execute(); } public GoogleApiClient getmGoogleApiClient(){ return this.mGoogleApiClient; } private class ConnectionCallbacks implements GoogleApiClient.ConnectionCallbacks { @Override public void onConnected(Bundle bundle) { Log.d(TAG, "connected to device...calling send message"); sendMessage("MessageAddress", prefs.getString(Constants.PREFS_KEY_SELECTED_ADDRESS, null)); if(wallet != null) { String currencyValue = WalletUtils.getWalletCurrencyValue(getApplicationContext(), prefs, wallet.getBalance(Wallet.BalanceType.ESTIMATED)); String currencyType = prefs.getString(Constants.CURRENCY_PREF_KEY, null); sendMessage("MessageBalance", currencyValue); } } @Override public void onConnectionSuspended(int i) { Log.d(TAG, "Connection suspended"); } } private class ConnectionFailedListener implements GoogleApiClient.OnConnectionFailedListener { @Override public void onConnectionFailed(ConnectionResult result) { Log.d(TAG, "Connection failed..."); } } public void addWalletListener(Handler handler) { walletListener = new WalletUpdateListener(handler); wallet.addEventListener(walletListener); } public Wallet getWallet() { return wallet; } private void migrateWalletToProtobuf() { final File oldWalletFile = getFileStreamPath(Constants.WALLET_FILENAME); if (oldWalletFile.exists()) { final long start = System.currentTimeMillis(); // read wallet = restoreWalletFromBackup(); try { // write protobufSerializeWallet(wallet); // delete oldWalletFile.delete(); } catch (final IOException x) { throw new Error("cannot migrate wallet", x); } } else { Log.i("PB App", "File does not exit"); } } private void loadWalletFromProtobuf() { if (walletFile.exists()) { final long start = System.currentTimeMillis(); FileInputStream walletStream = null; try { walletStream = new FileInputStream(walletFile); wallet = new WalletProtobufSerializer().readWallet(walletStream); } catch (final FileNotFoundException x) { Toast.makeText(PayBitsApplication.this, x.getClass().getName(), Toast.LENGTH_LONG).show(); wallet = restoreWalletFromBackup(); } catch (final UnreadableWalletException x) { Toast.makeText(PayBitsApplication.this, x.getClass().getName(), Toast.LENGTH_LONG).show(); wallet = restoreWalletFromBackup(); } finally { if (walletStream != null) { try { walletStream.close(); } catch (final IOException x) { // swallow } } } if (!wallet.getParams().equals(params)) throw new Error("bad wallet network parameters: " + wallet.getParams().getId()); } else { wallet = new Wallet(params); } } private void protobufSerializeWallet(@Nonnull final Wallet wallet) throws IOException { wallet.saveToFile(walletFile); } private Wallet restoreWalletFromBackup() { try { final Wallet wallet = readKeys(openFileInput(Constants.WALLET_KEY_BACKUP_BASE58)); resetBlockchain(); Toast.makeText(this, "wallet reset", Toast.LENGTH_LONG).show(); return wallet; } catch (final IOException x) { throw new RuntimeException(x); } } public void startBlockchainService(final boolean cancelCoinsReceived) { startService(blockchainServiceIntent); } public void stopBlockchainService() { stopService(blockchainServiceIntent); } private void setKeyDefaults() { for (final ECKey key : wallet.getKeys()) if (!wallet.isKeyRotating(key)) { return; // found } addNewKeyToWallet(); } public void addNewKeyToWallet() { wallet.addKey(new ECKey()); prefs.edit().putBoolean(Constants.PREFS_KEY_REMIND_BACKUP, true).commit(); } private static Wallet readKeys(@Nonnull final InputStream is) throws IOException { final BufferedReader in = new BufferedReader(new InputStreamReader(is, Constants.UTF_8)); final List<ECKey> keys = WalletUtils.readKeys(in); in.close(); final Wallet wallet = new Wallet(Constants.NETWORK_PARAMETERS); for (final ECKey key : keys) wallet.addKey(key); return wallet; } public void resetBlockchain() { // actually stops the service startService(blockchainServiceResetBlockchainIntent); } private static final class WalletAutosaveEventListener implements WalletFiles.Listener { @Override public void onBeforeAutoSave(final File file) { } @Override public void onAfterAutoSave(final File file) { } } public void showImportCompletedPrompt(final Context context, String fileName) { final Dialog importCompleteDialog = new Dialog(context); importCompleteDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); importCompleteDialog.setContentView(R.layout.import_completed_prompt); Button cancelButton = (Button) importCompleteDialog.findViewById(R.id.import_prompt_cancel_button); cancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { importCompleteDialog.dismiss(); } }); TextView fileNameView = (TextView) importCompleteDialog.findViewById(R.id.import_prompt_completed_filename_message); if(fileName != null) fileNameView.setText(context.getString(R.string.import_completed_start) + " " + fileName); final Button okButton = (Button) importCompleteDialog.findViewById(R.id.import_prompt_ok_button); okButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { deleteBlockchainAndRestartApp(context, importCompleteDialog); //System.exit(0); } }); importCompleteDialog.show(); } public void deleteBlockchainAndRestartApp(Context context, Dialog dialog){ Log.d(TAG, "RESETTING BLOCKCHAIN"); resetBlockchain(); Intent intent = new Intent(context, MainActivity.class); if(dialog != null) dialog.dismiss(); context.startActivity(intent); } public void showNFCPrompt(final Context context){ nfcDialog = new Dialog(context); nfcDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); nfcDialog.setContentView(R.layout.nfc_prompt); Button cancel = (Button) nfcDialog.findViewById(R.id.nfc_prompt_cancel_button); cancel.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { nfcDialog.dismiss(); } }); nfcDialog.show(); } public void cancelNFCPrompt(final Context context){ if(nfcDialog != null) nfcDialog.dismiss(); } public void showPasswordPrompt(final Context context, final int action) { passwordDialog = new Dialog(context); passwordDialog.requestWindowFeature(Window.FEATURE_NO_TITLE); passwordDialog.setContentView(R.layout.password_prompt); Button cancelButton = (Button) passwordDialog.findViewById(R.id.password_prompt_cancel_button); cancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { passwordDialog.dismiss(); } }); final Button okayButton = (Button) passwordDialog.findViewById(R.id.password_prompt_encrypt_button); final EditText passwordInput = (EditText) passwordDialog.findViewById(R.id.password_prompt_field); okayButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { String password = passwordInput.getText().toString(); if (WalletUtils.checkPassword(password, prefs)) { passwordDialog.dismiss(); ((PasswordProvidedListener) context).onPasswordProvided(password, action); } else { Toast.makeText(context, getString(R.string.invalid_password_string), Toast.LENGTH_LONG).show(); } } }); CheckBox passwordCheckbox = (CheckBox) passwordDialog.findViewById(R.id.password_checkbox); passwordCheckbox.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { @Override public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { if (isChecked) { passwordInput.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); } else { passwordInput.setInputType(129); } } }); passwordDialog.show(); } public void initializeShamirSecretSharing(Context context) { if (!prefs.contains(Constants.SHAMIR_LOCAL_KEY)) { SecureRandom r = new SecureRandom(); BigInteger secret = new BigInteger(256, r); BigInteger prime = BigInteger.probablePrime(256 + 64, r); SecretShare.PublicInfo publicInfo = new SecretShare.PublicInfo(Constants.SHAMIR_N, Constants.SHAMIR_K, prime, null); SecretShare secretShare = new SecretShare(publicInfo); SecretShare.SplitSecretOutput splitSecretOutput = secretShare.split(secret, r); List<SecretShare.ShareInfo> infos = splitSecretOutput.getShareInfos(); if (infos != null) { SecretShare.ShareInfo shareInfo1 = infos.get(0); SecretShare.ShareInfo shareInfo2 = infos.get(1); SecretShare.ShareInfo shareInfo3 = infos.get(2); if (shareInfo1 != null && shareInfo2 != null && shareInfo3 != null) { prefs.edit().putString(Constants.SHAMIR_LOCAL_KEY, shareInfo1.getX() + ":" + shareInfo1.getShare().toString()).commit(); prefs.edit().putString(Constants.SHAMIR_ENCRYPTED_KEY, shareInfo2.getX() + ":" + shareInfo2.getShare().toString()).commit(); prefs.edit().putString(Constants.SHAMIR_EXPORTED_KEY, shareInfo3.getX() + ":" + shareInfo3.getShare().toString()).commit(); prefs.edit().putString(Constants.SHAMIR_EXPORTED_KEY_SHA256, WalletUtils.convertToSha256(shareInfo3.getX() + ":" + shareInfo3.getShare().toString())).commit(); } } } Log.d(TAG, "About to send shamir"); if(prefs.contains(Constants.SHAMIR_EXPORTED_KEY)){ SendShamirValueTask sendShamirValueTask = new SendShamirValueTask(context, prefs.getString(Constants.SHAMIR_EXPORTED_KEY, null), this); //sendShamirValueTask.execute(); sendShamirValueTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); } } public SharedPreferences getPrefs() { return prefs; } public void setKeyCache(KeyCache keyCache) { this.keyCache = keyCache; } public KeyCache getKeyCache() { return this.keyCache; } public void setBackupFileUri(Uri uri){ this.backupFileUri = uri; } public Uri getBackupFileUri(){ return this.backupFileUri; } public ArrayList<Map<String, String>> getPeopleList(){ return this.peopleList; }; }