diff --git a/app/.gitignore b/app/.gitignore
index e783827..b19c207 100644
--- a/app/.gitignore
+++ b/app/.gitignore
@@ -1,2 +1,3 @@
.externalNativeBuild
build
+app.iml
diff --git a/app/app.iml b/app/app.iml
deleted file mode 100644
index a6fa286..0000000
--- a/app/app.iml
+++ /dev/null
@@ -1,147 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- generateDebugSources
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/cpp/monerujo.cpp b/app/src/main/cpp/monerujo.cpp
index 3c11364..1d14de8 100644
--- a/app/src/main/cpp/monerujo.cpp
+++ b/app/src/main/cpp/monerujo.cpp
@@ -302,7 +302,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_recoveryWalletJ(JNIEnv *env, jobje
return reinterpret_cast(wallet);
}
-JNIEXPORT jboolean JNICALL
+JNIEXPORT jlong JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_createWalletFromKeysJ(JNIEnv *env, jobject instance,
jstring path, jstring language,
jboolean isTestNet,
@@ -463,7 +463,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_resolveOpenAlias(JNIEnv *env, jobj
//TODO static std::tuple checkUpdates(const std::string &software, const std::string &subdir);
-// actually a WalletManager function, but logically in Wallet
+// actually a WalletManager function, but logically in onWalletSelected
JNIEXPORT jboolean JNICALL
Java_com_m2049r_xmrwallet_model_WalletManager_closeJ(JNIEnv *env, jobject instance,
jobject walletInstance) {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java
new file mode 100644
index 0000000..d1f846a
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateFragment.java
@@ -0,0 +1,407 @@
+/*
+ * Copyright (c) 2017 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.xmrwallet;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.text.Editable;
+import android.text.TextWatcher;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.inputmethod.EditorInfo;
+import android.widget.Button;
+import android.widget.EditText;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+import android.widget.Toast;
+
+import com.m2049r.xmrwallet.model.WalletManager;
+import com.m2049r.xmrwallet.util.Helper;
+
+import java.io.File;
+
+public class GenerateFragment extends Fragment {
+ static final String TAG = "GenerateFragment";
+
+ EditText etWalletName;
+ EditText etWalletPassword;
+ EditText etWalletAddress;
+ EditText etWalletMnemonic;
+ LinearLayout llRestoreKeys;
+ EditText etWalletViewKey;
+ EditText etWalletSpendKey;
+ EditText etWalletRestoreHeight;
+ Button bGenerate;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ View view = inflater.inflate(R.layout.gen_fragment, container, false);
+
+ etWalletName = (EditText) view.findViewById(R.id.etWalletName);
+ etWalletPassword = (EditText) view.findViewById(R.id.etWalletPassword);
+ etWalletMnemonic = (EditText) view.findViewById(R.id.etWalletMnemonic);
+ etWalletAddress = (EditText) view.findViewById(R.id.etWalletAddress);
+ llRestoreKeys = (LinearLayout) view.findViewById(R.id.llRestoreKeys);
+ etWalletViewKey = (EditText) view.findViewById(R.id.etWalletViewKey);
+ etWalletSpendKey = (EditText) view.findViewById(R.id.etWalletSpendKey);
+ etWalletRestoreHeight = (EditText) view.findViewById(R.id.etWalletRestoreHeight);
+ bGenerate = (Button) view.findViewById(R.id.bGenerate);
+
+ boolean testnet = WalletManager.getInstance().isTestNet();
+ etWalletMnemonic.setTextIsSelectable(testnet);
+
+ etWalletName.requestFocus();
+ Helper.showKeyboard(getActivity());
+ etWalletName.addTextChangedListener(new TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable editable) {
+ if (etWalletName.length() > 0) {
+ bGenerate.setEnabled(true);
+ } else {
+ bGenerate.setEnabled(false);
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+ });
+ etWalletName.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Helper.showKeyboard(getActivity());
+ }
+ });
+ etWalletName.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
+ if (etWalletName.length() > 0) {
+ etWalletPassword.requestFocus();
+ } // otherwise ignore
+ return true;
+ }
+ return false;
+ }
+ });
+
+ etWalletPassword.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ Helper.showKeyboard(getActivity());
+ }
+ });
+ etWalletPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
+ if (etWalletAddress.length() > 0) {
+ etWalletAddress.requestFocus();
+ } else {
+ etWalletMnemonic.requestFocus();
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+
+ etWalletMnemonic.setOnEditorActionListener(new TextView.OnEditorActionListener() {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
+ if (etWalletMnemonic.length() == 0) {
+ etWalletAddress.requestFocus();
+ } else if (mnemonicOk()) {
+ etWalletRestoreHeight.requestFocus();
+ } else {
+ Toast.makeText(getActivity(), getString(R.string.generate_check_mnemonic), Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ etWalletMnemonic.setOnClickListener(new View.OnClickListener()
+
+ {
+ @Override
+ public void onClick(View v) {
+ Helper.showKeyboard(getActivity());
+ }
+ });
+ etWalletMnemonic.addTextChangedListener(new
+
+ TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable editable) {
+ if (etWalletMnemonic.length() > 0) {
+ etWalletRestoreHeight.setVisibility(View.VISIBLE);
+ etWalletAddress.setVisibility(View.INVISIBLE);
+ } else {
+ etWalletAddress.setVisibility(View.VISIBLE);
+ if (etWalletAddress.length() == 0) {
+ etWalletRestoreHeight.setVisibility(View.INVISIBLE);
+ } else {
+ etWalletRestoreHeight.setVisibility(View.VISIBLE);
+ }
+
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+ });
+
+ etWalletAddress.setOnEditorActionListener(new TextView.OnEditorActionListener()
+
+ {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
+ if (etWalletAddress.length() == 0) {
+ if (bGenerate.getVisibility() == View.VISIBLE) {
+ Helper.hideKeyboard(getActivity());
+ generateWallet();
+ }
+ } else if (addressOk()) {
+ etWalletViewKey.requestFocus();
+ } else {
+ Toast.makeText(getActivity(), getString(R.string.generate_check_address), Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ etWalletAddress.setOnClickListener(new View.OnClickListener()
+
+ {
+ @Override
+ public void onClick(View v) {
+ Helper.showKeyboard(getActivity());
+ }
+ });
+ etWalletAddress.addTextChangedListener(new
+
+ TextWatcher() {
+ @Override
+ public void afterTextChanged(Editable editable) {
+ if (etWalletAddress.length() > 0) {
+ llRestoreKeys.setVisibility(View.VISIBLE);
+ etWalletMnemonic.setVisibility(View.INVISIBLE);
+ etWalletRestoreHeight.setVisibility(View.VISIBLE);
+ } else {
+ llRestoreKeys.setVisibility(View.INVISIBLE);
+ etWalletMnemonic.setVisibility(View.VISIBLE);
+ if (etWalletMnemonic.length() == 0) {
+ etWalletRestoreHeight.setVisibility(View.INVISIBLE);
+ } else {
+ etWalletRestoreHeight.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+
+ @Override
+ public void beforeTextChanged(CharSequence s, int start, int count, int after) {
+ }
+
+ @Override
+ public void onTextChanged(CharSequence s, int start, int before, int count) {
+ }
+ });
+
+ etWalletViewKey.setOnEditorActionListener(new TextView.OnEditorActionListener()
+
+ {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
+ if (viewKeyOk()) {
+ etWalletSpendKey.requestFocus();
+ } else {
+ Toast.makeText(getActivity(), getString(R.string.generate_check_key), Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ etWalletViewKey.setOnClickListener(new View.OnClickListener()
+
+ {
+ @Override
+ public void onClick(View v) {
+ Helper.showKeyboard(getActivity());
+ }
+ });
+
+ etWalletSpendKey.setOnEditorActionListener(new TextView.OnEditorActionListener()
+
+ {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
+ if (spendKeyOk()) {
+ etWalletRestoreHeight.requestFocus();
+ } else {
+ Toast.makeText(getActivity(), getString(R.string.generate_check_key), Toast.LENGTH_LONG).show();
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+ etWalletSpendKey.setOnClickListener(new View.OnClickListener()
+
+ {
+ @Override
+ public void onClick(View v) {
+ Helper.showKeyboard(getActivity());
+ }
+ });
+
+ etWalletRestoreHeight.setOnEditorActionListener(new TextView.OnEditorActionListener()
+
+ {
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_NEXT)) {
+ if (bGenerate.getVisibility() == View.VISIBLE) {
+ Helper.hideKeyboard(getActivity());
+ generateWallet();
+ }
+ return true;
+ }
+ return false;
+ }
+ });
+
+ bGenerate.setOnClickListener(new View.OnClickListener()
+
+ {
+ @Override
+ public void onClick(View v) {
+ Helper.hideKeyboard(getActivity());
+ generateWallet();
+ }
+ });
+
+ return view;
+ }
+
+ private boolean mnemonicOk() {
+ String seed = etWalletMnemonic.getText().toString();
+ return (seed.split("\\s").length == 25); // 25 words
+ }
+
+ private boolean addressOk() {
+ String address = etWalletAddress.getText().toString();
+ return ((address.length() == 95) && ("49A".indexOf(address.charAt(0)) >= 0));
+ }
+
+ private boolean viewKeyOk() {
+ String viewKey = etWalletViewKey.getText().toString();
+ return (viewKey.length() == 64) && (viewKey.matches("^[0-9a-fA-F]+$"));
+ }
+
+ private boolean spendKeyOk() {
+ String spendKey = etWalletSpendKey.getText().toString();
+ return ((spendKey.length() == 0) || ((spendKey.length() == 64) && (spendKey.matches("^[0-9a-fA-F]+$"))));
+ }
+
+ private void generateWallet() {
+ String name = etWalletName.getText().toString();
+ if (name.length() == 0) return;
+ String walletPath = Helper.getWalletPath(getActivity(), name);
+ if (WalletManager.getInstance().walletExists(walletPath)) {
+ Toast.makeText(getActivity(), getString(R.string.generate_wallet_exists), Toast.LENGTH_LONG).show();
+ etWalletName.requestFocus();
+ return;
+ }
+ String password = etWalletPassword.getText().toString();
+
+ String seed = etWalletMnemonic.getText().toString();
+ String address = etWalletAddress.getText().toString();
+
+ long height;
+ try {
+ height = Long.parseLong(etWalletRestoreHeight.getText().toString());
+ } catch (NumberFormatException ex) {
+ height = 0; // Keep calm and carry on!
+ }
+
+ // figure out how we want to create this wallet
+ // A. from scratch
+ if ((seed.length() == 0) && (address.length() == 0)) {
+ bGenerate.setVisibility(View.INVISIBLE);
+ activityCallback.onGenerate(name, password);
+ } else
+ // B. from seed
+ if (mnemonicOk()) {
+ bGenerate.setVisibility(View.INVISIBLE);
+ activityCallback.onGenerate(name, password, seed, height);
+ } else
+ // C. from keys
+ if (addressOk() && viewKeyOk() && (spendKeyOk())) {
+ String viewKey = etWalletViewKey.getText().toString();
+ String spendKey = etWalletSpendKey.getText().toString();
+ bGenerate.setVisibility(View.INVISIBLE);
+ activityCallback.onGenerate(name, password, address, viewKey, spendKey, height);
+ } else
+ // D. none of the above :)
+ {
+ Toast.makeText(getActivity(), getString(R.string.generate_check_something), Toast.LENGTH_LONG).show();
+ }
+ }
+
+ public void walletGenerateError() {
+ bGenerate.setEnabled(etWalletName.length() > 0);
+ bGenerate.setVisibility(View.VISIBLE);
+ }
+
+ GenerateFragment.Listener activityCallback;
+
+ public interface Listener {
+ void onGenerate(String name, String password);
+
+ void onGenerate(String name, String password, String seed, long height);
+
+ void onGenerate(String name, String password, String address, String viewKey, String spendKey, long height);
+
+ File getStorageRoot();
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (context instanceof GenerateFragment.Listener) {
+ this.activityCallback = (GenerateFragment.Listener) context;
+ } else {
+ throw new ClassCastException(context.toString()
+ + " must implement Listener");
+ }
+ }
+
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java
new file mode 100644
index 0000000..8089323
--- /dev/null
+++ b/app/src/main/java/com/m2049r/xmrwallet/GenerateReviewFragment.java
@@ -0,0 +1,115 @@
+/*
+ * Copyright (c) 2017 m2049r
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.m2049r.xmrwallet;
+
+import android.app.Fragment;
+import android.content.Context;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.Button;
+import android.widget.TextView;
+
+import com.m2049r.xmrwallet.model.WalletManager;
+
+public class GenerateReviewFragment extends Fragment {
+ static final String TAG = "GenerateReviewFragment";
+
+ TextView tvWalletName;
+ TextView tvWalletPassword;
+ TextView tvWalletAddress;
+ TextView tvWalletMnemonic;
+ TextView tvWalletViewKey;
+ TextView tvWalletSpendKey;
+ Button bAccept;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+
+ View view = inflater.inflate(R.layout.gen_review_fragment, container, false);
+
+ tvWalletName = (TextView) view.findViewById(R.id.tvWalletName);
+ tvWalletPassword = (TextView) view.findViewById(R.id.tvWalletPassword);
+ tvWalletAddress = (TextView) view.findViewById(R.id.tvWalletAddress);
+ tvWalletViewKey = (TextView) view.findViewById(R.id.tvWalletViewKey);
+ tvWalletSpendKey = (TextView) view.findViewById(R.id.tvWalletSpendKey);
+ tvWalletMnemonic = (TextView) view.findViewById(R.id.tvWalletMnemonic);
+
+ bAccept = (Button) view.findViewById(R.id.bAccept);
+
+ boolean testnet = WalletManager.getInstance().isTestNet();
+ tvWalletMnemonic.setTextIsSelectable(testnet);
+
+ bAccept.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ acceptWallet();
+ }
+ });
+
+ showDetails();
+ return view;
+ }
+
+ private void acceptWallet() {
+ String name = tvWalletName.getText().toString();
+ String password = tvWalletPassword.getText().toString();
+ bAccept.setEnabled(false);
+ activityCallback.onAccept(name, password);
+ }
+
+ public void showDetails() {
+ Bundle b = getArguments();
+ String name = b.getString("name");
+ String password = b.getString("password");
+ String address = b.getString("address");
+ String seed = b.getString("seed");
+ String view = b.getString("viewkey");
+ String spend = b.getString("spendkey");
+
+ tvWalletName.setText(name);
+ tvWalletPassword.setText(password);
+ tvWalletAddress.setText(address);
+ tvWalletMnemonic.setText(seed);
+ tvWalletViewKey.setText(view);
+ if (spend.length() > 0) { // should be == 64, but spendkey is not in the API yet
+ tvWalletSpendKey.setText(spend);
+ } else {
+ tvWalletSpendKey.setText(getString(R.string.generate_wallet_watchonly));
+ }
+ bAccept.setEnabled(true);
+ }
+
+ GenerateReviewFragment.Listener activityCallback;
+
+ public interface Listener {
+ void onAccept(String name, String password);
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ if (context instanceof GenerateReviewFragment.Listener) {
+ this.activityCallback = (GenerateReviewFragment.Listener) context;
+ } else {
+ throw new ClassCastException(context.toString()
+ + " must implement Listener");
+ }
+ }
+}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
index 9079c03..1ddc8df 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/LoginActivity.java
@@ -19,29 +19,39 @@ package com.m2049r.xmrwallet;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Fragment;
+import android.app.FragmentManager;
+import android.app.FragmentTransaction;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Bundle;
+import android.provider.MediaStore;
import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
+import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.model.WalletManager;
+import com.m2049r.xmrwallet.service.MoneroHandlerThread;
import com.m2049r.xmrwallet.util.Helper;
import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.nio.channels.FileChannel;
-public class LoginActivity extends Activity implements LoginFragment.LoginFragmentListener {
+public class LoginActivity extends AppCompatActivity
+ implements LoginFragment.Listener, GenerateFragment.Listener, GenerateReviewFragment.Listener {
static final String TAG = "LoginActivity";
static final int DAEMON_TIMEOUT = 500; // deamon must respond in 500ms
@@ -60,9 +70,26 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme
}
}
- // adapted from http://www.mkyong.com/android/android-prompt-user-input-dialog-example/
@Override
- public void promptPassword(final String wallet) {
+ public void onWalletSelected(final String walletName) {
+ Log.d(TAG, "selected wallet is ." + walletName + ".");
+ if (walletName.equals(':' + getString(R.string.generate_title))) {
+ startGenerateFragment();
+ } else {
+ // now it's getting real, check if wallet exists
+ String walletPath = Helper.getWalletPath(this, walletName);
+ if (WalletManager.getInstance().walletExists(walletPath)) {
+ promptPassword(walletName);
+ } else { // this cannot really happen as we prefilter choices
+ Toast.makeText(this, getString(R.string.bad_wallet), Toast.LENGTH_SHORT).show();
+ }
+ }
+ }
+
+ AlertDialog passwordDialog = null; // for preventing multiple clicks in wallet list
+
+ void promptPassword(final String wallet) {
+ if (passwordDialog != null) return; // we are already asking for password
Context context = LoginActivity.this;
LayoutInflater li = LayoutInflater.from(context);
View promptsView = li.inflate(R.layout.prompt_password, null);
@@ -81,37 +108,40 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme
.setPositiveButton("OK",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
- getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ Helper.hideKeyboardAlways(LoginActivity.this);
String pass = etPassword.getText().toString();
processPasswordEntry(wallet, pass);
+ passwordDialog = null;
}
})
.setNegativeButton("Cancel",
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
- getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ Helper.hideKeyboardAlways(LoginActivity.this);
dialog.cancel();
+ passwordDialog = null;
}
});
- final AlertDialog alertDialog = alertDialogBuilder.create();
- // request keyboard
- alertDialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ passwordDialog = alertDialogBuilder.create();
+ Helper.showKeyboard(passwordDialog);
+
// accept keyboard "ok"
etPassword.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
- getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ Helper.hideKeyboardAlways(LoginActivity.this);
String pass = etPassword.getText().toString();
- alertDialog.cancel();
+ passwordDialog.cancel();
processPasswordEntry(wallet, pass);
+ passwordDialog = null;
return false;
}
return false;
}
});
- alertDialog.show();
+ passwordDialog.show();
}
private boolean checkWalletPassword(String walletName, String password) {
@@ -130,7 +160,7 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme
}
////////////////////////////////////////
- // LoginFragment.LoginFragmentListener
+ // LoginFragment.Listener
////////////////////////////////////////
@Override
public SharedPreferences getPrefs() {
@@ -142,6 +172,11 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme
return Helper.getStorageRoot(getApplicationContext());
}
+ @Override
+ public void setTitle(String title) {
+ super.setTitle(title);
+ }
+
////////////////////////////////////////
////////////////////////////////////////
@@ -178,7 +213,7 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme
} else {
String msg = getString(R.string.message_strorage_not_permitted);
Log.e(TAG, msg);
- Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
+ Toast.makeText(this, msg, Toast.LENGTH_LONG).show();
//throw new IllegalStateException(msg);
}
break;
@@ -190,6 +225,209 @@ public class LoginActivity extends Activity implements LoginFragment.LoginFragme
Fragment fragment = new LoginFragment();
getFragmentManager().beginTransaction()
.add(R.id.fragment_container, fragment).commit();
- Log.d(TAG, "fragment added");
+ Log.d(TAG, "LoginFragment added");
+ }
+
+ void startGenerateFragment() {
+ replaceFragment(new GenerateFragment(), "gen", null);
+ Log.d(TAG, "GenerateFragment placed");
+ }
+
+ void startReviewFragment(Bundle extras) {
+ replaceFragment(new GenerateReviewFragment(), null, extras);
+ Log.d(TAG, "GenerateReviewFragment placed");
+ }
+
+ void replaceFragment(Fragment newFragment, String name, Bundle extras) {
+ if (extras != null) {
+ newFragment.setArguments(extras);
+ }
+ FragmentTransaction transaction = getFragmentManager().beginTransaction();
+ transaction.replace(R.id.fragment_container, newFragment);
+ transaction.addToBackStack(name);
+ transaction.commit();
+ }
+
+ //////////////////////////////////////////
+ // GenerateFragment.Listener
+ //////////////////////////////////////////
+ static final String MNEMONIC_LANGUAGE = "English"; // see mnemonics/electrum-words.cpp for more
+
+ public void createWallet(final String name, final String password, final WalletCreator walletCreator) {
+ final GenerateFragment genFragment = (GenerateFragment)
+ getFragmentManager().findFragmentById(R.id.fragment_container);
+ File newWalletFolder = new File(getStorageRoot(), ".new");
+ if (!newWalletFolder.exists()) {
+ if (!newWalletFolder.mkdir()) {
+ Log.e(TAG, "Cannot create new wallet dir " + newWalletFolder.getAbsolutePath());
+ genFragment.walletGenerateError();
+ return;
+ }
+ }
+ if (!newWalletFolder.isDirectory()) {
+ Log.e(TAG, "New wallet dir " + newWalletFolder.getAbsolutePath() + "is not a directory");
+ genFragment.walletGenerateError();
+ return;
+ }
+ File cacheFile = new File(newWalletFolder, name);
+ cacheFile.delete();
+ File keysFile = new File(newWalletFolder, name + ".keys");
+ keysFile.delete();
+ final File addressFile = new File(newWalletFolder, name + ".address.txt");
+ addressFile.delete();
+
+ if (cacheFile.exists() || keysFile.exists() || addressFile.exists()) {
+ Log.e(TAG, "Cannot remove all old wallet files: " + cacheFile.getAbsolutePath());
+ genFragment.walletGenerateError();
+ ;
+ return;
+ }
+
+ final String newWalletPath = new File(newWalletFolder, name).getAbsolutePath();
+ new Thread(null,
+ new Runnable() {
+ @Override
+ public void run() {
+ Log.d(TAG, "creating wallet " + newWalletPath);
+ Wallet newWallet = walletCreator.createWallet(newWalletPath, password);
+ final String seed = newWallet.getSeed();
+ final String address = newWallet.getAddress();
+ final String view = newWallet.getSecretViewKey();
+ final String spend = newWallet.isWatchOnly() ? "" : "not available - use seed for recovery";
+ newWallet.close();
+ Log.d(TAG, "Created " + address);
+ runOnUiThread(new Runnable() {
+ public void run() {
+ Bundle b = new Bundle();
+ b.putString("name", name);
+ b.putString("password", password);
+ b.putString("seed", seed);
+ b.putString("address", address);
+ b.putString("viewkey", view);
+ b.putString("spendkey", spend);
+ startReviewFragment(b);
+ }
+ });
+ }
+ }
+ , "CreateWallet", MoneroHandlerThread.THREAD_STACK_SIZE).start();
+ }
+
+ interface WalletCreator {
+ Wallet createWallet(String path, String password);
+ }
+
+ @Override
+ public void onGenerate(String name, String password) {
+ createWallet(name, password,
+ new WalletCreator() {
+ public Wallet createWallet(String path, String password) {
+ return WalletManager.getInstance()
+ .createWallet(path, password, MNEMONIC_LANGUAGE);
+ }
+ });
+ }
+
+ @Override
+ public void onGenerate(String name, String password, final String seed, final long restoreHeight) {
+ createWallet(name, password,
+ new WalletCreator() {
+ public Wallet createWallet(String path, String password) {
+ Wallet newWallet = WalletManager.getInstance().recoveryWallet(path, seed, restoreHeight);
+ newWallet.setPassword(password);
+ newWallet.store();
+ return newWallet;
+ }
+ });
+ }
+
+ @Override
+ public void onGenerate(String name, String password,
+ final String address, final String viewKey, final String spendKey, final long restoreHeight) {
+ createWallet(name, password,
+ new WalletCreator() {
+ public Wallet createWallet(String path, String password) {
+ Wallet newWallet = WalletManager.getInstance()
+ .createWalletFromKeys(path, MNEMONIC_LANGUAGE, restoreHeight,
+ address, viewKey, spendKey);
+ newWallet.setPassword(password);
+ newWallet.store();
+ return newWallet;
+ }
+ });
+ }
+
+
+ @Override
+ public void onAccept(final String name, final String password) {
+ final File newWalletFolder = new File(getStorageRoot(), ".new");
+ final File walletFolder = getStorageRoot();
+ new Thread(null,
+ new Runnable() {
+ @Override
+ public void run() {
+ final String walletPath = new File(walletFolder, name).getAbsolutePath();
+ final boolean rc = copyWallet(walletFolder, newWalletFolder, name)
+ &&
+ (testWallet(walletPath, password) == Wallet.Status.Status_Ok);
+ runOnUiThread(new Runnable() {
+ public void run() {
+ if (rc) {
+ getFragmentManager().popBackStack("gen",
+ FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ Toast.makeText(LoginActivity.this,
+ getString(R.string.generate_wallet_created), Toast.LENGTH_SHORT).show();
+ } else {
+ Log.e(TAG, "Wallet store failed to " + walletPath);
+ Toast.makeText(LoginActivity.this,
+ getString(R.string.generate_wallet_create_failed_2), Toast.LENGTH_LONG).show();
+ }
+ }
+ });
+ }
+ }
+ , "AcceptWallet", MoneroHandlerThread.THREAD_STACK_SIZE).start();
+ }
+
+ Wallet.Status testWallet(String path, String password) {
+ Log.d(TAG, "testing wallet " + path);
+ Wallet aWallet = WalletManager.getInstance().openWallet(path, password);
+ if (aWallet == null) return Wallet.Status.Status_Error; // does this ever happen?
+ Wallet.Status status = aWallet.getStatus();
+ Log.d(TAG, "wallet tested " + aWallet.getStatus());
+ aWallet.close();
+ return status;
+ }
+
+ boolean copyWallet(File dstDir, File srcDir, String name) {
+ boolean success = false;
+ try {
+ // TODO: the cache is corrupt if we recover (!!)
+ // TODO recoveryheight is ignored but not on watchonly wallet ?! - find out why
+ //copyFile(dstDir, srcDir, name);
+ copyFile(dstDir, srcDir, name + ".keys");
+ copyFile(dstDir, srcDir, name + ".address.txt");
+ success = true;
+ } catch (IOException ex) {
+ Log.e(TAG, "wallet copy failed: " + ex.getMessage());
+ // try to rollback
+ new File(dstDir, name).delete();
+ new File(dstDir, name + ".keys").delete();
+ new File(dstDir, name + ".address.txt").delete();
+ }
+ return success;
+ }
+
+ void copyFile(File dstDir, File srcDir, String name) throws IOException {
+ FileChannel inChannel = new FileInputStream(new File(srcDir, name)).getChannel();
+ FileChannel outChannel = new FileOutputStream(new File(dstDir, name)).getChannel();
+ try {
+ inChannel.transferTo(0, inChannel.size(), outChannel);
+ } finally {
+ if (inChannel != null)
+ inChannel.close();
+ if (outChannel != null)
+ outChannel.close();
+ }
}
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
index f925d08..50e0627 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/LoginFragment.java
@@ -26,9 +26,7 @@ import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
@@ -39,44 +37,59 @@ import android.widget.Toast;
import android.widget.ToggleButton;
import com.m2049r.xmrwallet.model.WalletManager;
+import com.m2049r.xmrwallet.util.Helper;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.ArrayList;
+import java.util.Comparator;
import java.util.Date;
import java.util.List;
+import java.util.Set;
+import java.util.TreeSet;
public class LoginFragment extends Fragment {
private static final String TAG = "LoginFragment";
+ public static final String WALLETNAME_PREAMBLE = "[------] ";
+ public static final int WALLETNAME_PREAMBLE_LENGTH = WALLETNAME_PREAMBLE.length();
+
ListView listView;
- List walletList = new ArrayList<>();
+ Set walletList = new TreeSet<>(new Comparator() {
+ @Override
+ public int compare(String o1, String o2) {
+ return o1.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase()
+ .compareTo(o2.substring(WALLETNAME_PREAMBLE_LENGTH).toLowerCase());
+ }
+ });
List displayedList = new ArrayList<>();
ToggleButton tbMainNet;
EditText etDaemonAddress;
- LoginFragment.LoginFragmentListener activityCallback;
+ Listener activityCallback;
// Container Activity must implement this interface
- public interface LoginFragmentListener {
+ public interface Listener {
SharedPreferences getPrefs();
File getStorageRoot();
- void promptPassword(final String wallet);
+ void onWalletSelected(final String wallet);
+
+ void setTitle(String title);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
- if (context instanceof LoginFragment.LoginFragmentListener) {
- this.activityCallback = (LoginFragment.LoginFragmentListener) context;
+ if (context instanceof Listener) {
+ this.activityCallback = (Listener) context;
} else {
throw new ClassCastException(context.toString()
- + " must implement WalletFragmentListener");
+ + " must implement Listener");
}
}
@@ -96,19 +109,18 @@ public class LoginFragment extends Fragment {
tbMainNet = (ToggleButton) view.findViewById(R.id.tbMainNet);
etDaemonAddress = (EditText) view.findViewById(R.id.etDaemonAddress);
- getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
+ Helper.hideKeyboard(getActivity());
etDaemonAddress.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
- InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Context.INPUT_METHOD_SERVICE);
- imm.showSoftInput(etDaemonAddress, InputMethodManager.SHOW_IMPLICIT);
+ Helper.showKeyboard(getActivity());
}
});
etDaemonAddress.setOnEditorActionListener(new TextView.OnEditorActionListener() {
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
if ((event != null && (event.getKeyCode() == KeyEvent.KEYCODE_ENTER)) || (actionId == EditorInfo.IME_ACTION_DONE)) {
- getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ Helper.hideKeyboard(getActivity());
return false;
}
return false;
@@ -125,6 +137,8 @@ public class LoginFragment extends Fragment {
} else {
setDaemon(daemonTestNet);
}
+ activityCallback.setTitle(getString(R.string.app_name) + " " +
+ getString(mainnet ? R.string.connect_mainnet : R.string.connect_testnet));
filterList();
((BaseAdapter) listView.getAdapter()).notifyDataSetChanged();
}
@@ -143,44 +157,54 @@ public class LoginFragment extends Fragment {
public void onItemClick(AdapterView> parent, View view, int position, long id) {
EditText tvDaemonAddress = (EditText) getView().findViewById(R.id.etDaemonAddress);
if (tvDaemonAddress.getText().toString().length() == 0) {
- Toast.makeText(getActivity(), getString(R.string.prompt_daemon_missing), Toast.LENGTH_LONG).show();
+ Toast.makeText(getActivity(), getString(R.string.prompt_daemon_missing), Toast.LENGTH_SHORT).show();
+ tvDaemonAddress.requestFocus();
+ Helper.showKeyboard(getActivity());
return;
}
String itemValue = (String) listView.getItemAtPosition(position);
- if ((isMainNet() && itemValue.charAt(1) != '4')
- || (!isMainNet() && itemValue.charAt(1) != '9')) {
+
+ if (itemValue.length() <= (WALLETNAME_PREAMBLE_LENGTH)) {
+ Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show();
+ return;
+ }
+
+ String x = isMainNet() ? "4-" : "9A-";
+ if (x.indexOf(itemValue.charAt(1)) < 0) {
Toast.makeText(getActivity(), getString(R.string.prompt_wrong_net), Toast.LENGTH_LONG).show();
return;
}
- final int preambleLength = "[123456] ".length();
- if (itemValue.length() <= (preambleLength)) {
- Toast.makeText(getActivity(), getString(R.string.panic), Toast.LENGTH_LONG).show();
- return;
- }
if (!checkAndSetWalletDaemon(getDaemon(), !isMainNet())) {
- Toast.makeText(getActivity(), getString(R.string.warn_daemon_unavailable), Toast.LENGTH_LONG).show();
+ Toast.makeText(getActivity(), getString(R.string.warn_daemon_unavailable), Toast.LENGTH_SHORT).show();
return;
}
// looking good
savePrefs(false);
- String wallet = itemValue.substring(preambleLength);
- activityCallback.promptPassword(wallet);
+ String wallet = itemValue.substring(WALLETNAME_PREAMBLE_LENGTH);
+ if (itemValue.charAt(1) == '-') wallet = ':' + wallet;
+ activityCallback.onWalletSelected(wallet);
}
});
+
+ activityCallback.setTitle(getString(R.string.app_name) + " " +
+ getString(isMainNet() ? R.string.connect_mainnet : R.string.connect_testnet));
+
loadList();
return view;
}
private void filterList() {
displayedList.clear();
- char x = isMainNet() ? '4' : '9';
+ String x = isMainNet() ? "4" : "9A";
for (String s : walletList) {
- if (s.charAt(1) == x) displayedList.add(s);
+ // Log.d(TAG, "filtering " + s);
+ if (x.indexOf(s.charAt(1)) >= 0) displayedList.add(s);
}
+ displayedList.add(WALLETNAME_PREAMBLE + getString(R.string.generate_title));
}
private void loadList() {
@@ -190,7 +214,7 @@ public class LoginFragment extends Fragment {
walletList.clear();
for (WalletManager.WalletInfo walletInfo : walletInfos) {
- Log.d(TAG, walletInfo.address);
+ // Log.d(TAG, walletInfo.address);
String displayAddress = walletInfo.address;
if (displayAddress.length() == 95) {
displayAddress = walletInfo.address.substring(0, 6);
@@ -230,8 +254,8 @@ public class LoginFragment extends Fragment {
SharedPreferences sharedPref = activityCallback.getPrefs();
boolean mainnet = sharedPref.getBoolean(PREF_MAINNET, false);
- daemonMainNet = sharedPref.getString(PREF_DAEMON_MAINNET, "localhost:18081");
- daemonTestNet = sharedPref.getString(PREF_DAEMON_TESTNET, "localhost:28081");
+ daemonMainNet = sharedPref.getString(PREF_DAEMON_MAINNET, "");
+ daemonTestNet = sharedPref.getString(PREF_DAEMON_TESTNET, "");
setMainNet(mainnet);
if (mainnet) {
diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
index 40ef712..a0c5620 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/WalletActivity.java
@@ -25,13 +25,14 @@ import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.PowerManager;
+import android.support.v7.app.AppCompatActivity;
import android.util.Log;
import android.widget.Toast;
import com.m2049r.xmrwallet.model.Wallet;
import com.m2049r.xmrwallet.service.WalletService;
-public class WalletActivity extends Activity implements WalletFragment.WalletFragmentListener,
+public class WalletActivity extends AppCompatActivity implements WalletFragment.Listener,
WalletService.Observer {
private static final String TAG = "WalletActivity";
@@ -200,11 +201,6 @@ public class WalletActivity extends Activity implements WalletFragment.WalletFra
Intent intent = new Intent(getApplicationContext(), WalletService.class);
intent.putExtra(WalletService.REQUEST, WalletService.REQUEST_CMD_STORE);
startService(intent);
- runOnUiThread(new Runnable() {
- public void run() {
- Toast.makeText(getApplicationContext(), getString(R.string.status_wallet_unloading), Toast.LENGTH_LONG).show();
- }
- });
Log.d(TAG, "STORE request sent");
} else {
Log.e(TAG, "Service not bound");
@@ -212,7 +208,7 @@ public class WalletActivity extends Activity implements WalletFragment.WalletFra
}
//////////////////////////////////////////
- // WalletFragment.WalletFragmentListener
+ // WalletFragment.Listener
//////////////////////////////////////////
@Override
@@ -259,6 +255,15 @@ public class WalletActivity extends Activity implements WalletFragment.WalletFra
});
}
+ @Override
+ public void onWalletStored() {
+ runOnUiThread(new Runnable() {
+ public void run() {
+ Toast.makeText(WalletActivity.this, getString(R.string.status_wallet_unloaded), Toast.LENGTH_SHORT).show();
+ }
+ });
+ }
+
@Override
public void onProgress(final String text) {
//Log.d(TAG, "PROGRESS: " + text);
diff --git a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java
index 50791bd..c708b3c 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/WalletFragment.java
@@ -37,11 +37,13 @@ import com.m2049r.xmrwallet.layout.TransactionInfoAdapter;
import com.m2049r.xmrwallet.model.TransactionInfo;
import com.m2049r.xmrwallet.model.Wallet;
+import java.text.NumberFormat;
import java.util.List;
public class WalletFragment extends Fragment implements TransactionInfoAdapter.OnInteractionListener {
private static final String TAG = "WalletFragment";
private TransactionInfoAdapter adapter;
+ private NumberFormat formatter = NumberFormat.getInstance();
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
@@ -171,7 +173,7 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
long daemonHeight = activityCallback.getDaemonHeight();
if (!wallet.isSynchronized()) {
long n = daemonHeight - wallet.getBlockChainHeight();
- sync = n + " " + getString(R.string.status_remaining);
+ sync = formatter.format(n) + " " + getString(R.string.status_remaining);
if (firstBlock == 0) {
firstBlock = wallet.getBlockChainHeight();
}
@@ -180,7 +182,7 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
if (x == 0) x = -1;
onProgress(x);
} else {
- sync = getString(R.string.status_synced) + ": " + wallet.getBlockChainHeight();
+ sync = getString(R.string.status_synced) + ": " + formatter.format(wallet.getBlockChainHeight());
}
}
String net = (wallet.isTestNet() ? getString(R.string.connect_testnet) : getString(R.string.connect_mainnet));
@@ -188,10 +190,10 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
connectionStatusView.setText(net + " " + daemonConnected.toString().substring(17));
}
- WalletFragmentListener activityCallback;
+ Listener activityCallback;
// Container Activity must implement this interface
- public interface WalletFragmentListener {
+ public interface Listener {
boolean hasBoundService();
Wallet.ConnectionStatus getConnectionStatus();
@@ -205,15 +207,11 @@ public class WalletFragment extends Fragment implements TransactionInfoAdapter.O
@Override
public void onAttach(Context context) {
super.onAttach(context);
- if (context instanceof WalletFragmentListener) {
- this.activityCallback = (WalletFragmentListener) context;
+ if (context instanceof Listener) {
+ this.activityCallback = (Listener) context;
} else {
throw new ClassCastException(context.toString()
- + " must implement WalletFragmentListener");
+ + " must implement Listener");
}
}
-
- private void runOnUiThread(Runnable runnable) {
- if (isAdded()) getActivity().runOnUiThread(runnable);
- }
}
diff --git a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java
index 2757877..357027b 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/model/WalletManager.java
@@ -57,7 +57,7 @@ public class WalletManager {
private void manageWallet(String walletId, Wallet wallet) {
if (getWallet(walletId) != null) {
- throw new IllegalStateException("Wallet already under management!");
+ throw new IllegalStateException(walletId + " already under management!");
}
Log.d(TAG, "Managing " + walletId);
managedWallets.put(walletId, wallet);
@@ -65,7 +65,7 @@ public class WalletManager {
private void unmanageWallet(String walletId) {
if (getWallet(walletId) == null) {
- throw new IllegalStateException("Wallet not under management!");
+ throw new IllegalStateException(walletId + " not under management!");
}
Log.d(TAG, "Unmanaging " + walletId);
managedWallets.remove(walletId);
@@ -78,6 +78,8 @@ public class WalletManager {
return wallet;
}
+ private native long createWalletJ(String path, String password, String language, boolean isTestNet);
+
public Wallet openWallet(String path, String password) {
long walletHandle = openWalletJ(path, password, isTestNet());
Wallet wallet = new Wallet(walletHandle);
@@ -85,6 +87,8 @@ public class WalletManager {
return wallet;
}
+ private native long openWalletJ(String path, String password, boolean isTestNet);
+
public Wallet recoveryWallet(String path, String mnemonic) {
Wallet wallet = recoveryWallet(path, mnemonic, 0);
manageWallet(wallet.getName(), wallet);
@@ -98,12 +102,17 @@ public class WalletManager {
return wallet;
}
- private native long createWalletJ(String path, String password, String language, boolean isTestNet);
-
- private native long openWalletJ(String path, String password, boolean isTestNet);
-
private native long recoveryWalletJ(String path, String mnemonic, boolean isTestNet, long restoreHeight);
+ public Wallet createWalletFromKeys(String path, String language, long restoreHeight,
+ String addressString, String viewKeyString, String spendKeyString) {
+ long walletHandle = createWalletFromKeysJ(path, language, isTestNet(), restoreHeight,
+ addressString, viewKeyString, spendKeyString);
+ Wallet wallet = new Wallet(walletHandle);
+ manageWallet(wallet.getName(), wallet);
+ return wallet;
+ }
+
private native long createWalletFromKeysJ(String path, String language,
boolean isTestNet,
long restoreHeight,
diff --git a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java
index 436d08f..ce4bc3c 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/service/WalletService.java
@@ -111,7 +111,8 @@ public class WalletService extends Service {
fullRefresh = true;
}
}
- observer.onRefreshed(wallet, fullRefresh);
+ if (observer != null)
+ observer.onRefreshed(wallet, fullRefresh);
}
}
}
@@ -191,6 +192,8 @@ public class WalletService extends Service {
void onProgress(String text);
void onProgress(int n);
+
+ void onWalletStored();
}
String progressText = null;
@@ -257,6 +260,7 @@ public class WalletService extends Service {
Log.d(TAG, "storing wallet: " + myWallet.getName());
getWallet().store();
Log.d(TAG, "wallet stored: " + myWallet.getName());
+ if (observer != null) observer.onWalletStored();
}
}
break;
@@ -351,6 +355,7 @@ public class WalletService extends Service {
if (listener == null) {
Log.d(TAG, "start() loadWallet");
Wallet aWallet = loadWallet(walletName, walletPassword);
+ // TODO check aWallet and die gracefully if neccessary
listener = new MyWalletListener(aWallet);
listener.start();
showProgress(100);
@@ -416,6 +421,8 @@ public class WalletService extends Service {
WalletManager.getInstance().close(wallet); // TODO close() failed?
wallet = null;
// TODO what do we do with the progress??
+ // TODO tell the activity this failed
+ // this crashes in MyWalletListener(Wallet aWallet) as wallet == null
}
}
return wallet;
diff --git a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
index 873c89f..182daf5 100644
--- a/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
+++ b/app/src/main/java/com/m2049r/xmrwallet/util/Helper.java
@@ -18,10 +18,13 @@ package com.m2049r.xmrwallet.util;
import android.Manifest;
import android.app.Activity;
+import android.app.Dialog;
import android.content.Context;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.util.Log;
+import android.view.WindowManager;
+import android.view.inputmethod.InputMethodManager;
import com.m2049r.xmrwallet.R;
@@ -82,4 +85,26 @@ public class Helper {
return Environment.MEDIA_MOUNTED.equals(state);
}
+ static public void showKeyboard(Activity act) {
+ InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.showSoftInput(act.getCurrentFocus(), InputMethodManager.SHOW_IMPLICIT);
+ }
+
+ static public void hideKeyboard(Activity act) {
+ if (act.getCurrentFocus() == null) {
+ act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_HIDDEN);
+ } else {
+ InputMethodManager imm = (InputMethodManager) act.getSystemService(Context.INPUT_METHOD_SERVICE);
+ imm.hideSoftInputFromWindow((null == act.getCurrentFocus()) ? null : act.getCurrentFocus().getWindowToken(),
+ InputMethodManager.HIDE_NOT_ALWAYS);
+ }
+ }
+
+ static public void showKeyboard(Dialog dialog) {
+ dialog.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE);
+ }
+
+ static public void hideKeyboardAlways(Activity act) {
+ act.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
+ }
}
diff --git a/app/src/main/res/layout/gen_fragment.xml b/app/src/main/res/layout/gen_fragment.xml
new file mode 100644
index 0000000..f9e23b8
--- /dev/null
+++ b/app/src/main/res/layout/gen_fragment.xml
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/gen_review_fragment.xml b/app/src/main/res/layout/gen_review_fragment.xml
new file mode 100644
index 0000000..9332dc3
--- /dev/null
+++ b/app/src/main/res/layout/gen_review_fragment.xml
@@ -0,0 +1,151 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/login_activity.xml b/app/src/main/res/layout/login_activity.xml
index 017e401..6c2ccbe 100644
--- a/app/src/main/res/layout/login_activity.xml
+++ b/app/src/main/res/layout/login_activity.xml
@@ -1,4 +1,17 @@
-
+
\ No newline at end of file
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin">
+
+
+
+
diff --git a/app/src/main/res/layout/login_fragment.xml b/app/src/main/res/layout/login_fragment.xml
index 5090c92..bf23420 100644
--- a/app/src/main/res/layout/login_fragment.xml
+++ b/app/src/main/res/layout/login_fragment.xml
@@ -1,18 +1,11 @@
+
+ android:orientation="vertical">
diff --git a/app/src/main/res/layout/wallet_activity.xml b/app/src/main/res/layout/wallet_activity.xml
index 017e401..6c2ccbe 100644
--- a/app/src/main/res/layout/wallet_activity.xml
+++ b/app/src/main/res/layout/wallet_activity.xml
@@ -1,4 +1,17 @@
-
+
\ No newline at end of file
+ android:layout_height="match_parent"
+ android:gravity="center_horizontal"
+ android:orientation="vertical"
+ android:paddingBottom="@dimen/activity_vertical_margin"
+ android:paddingLeft="@dimen/activity_horizontal_margin"
+ android:paddingRight="@dimen/activity_horizontal_margin"
+ android:paddingTop="@dimen/activity_vertical_margin">
+
+
+
+
diff --git a/app/src/main/res/layout/wallet_fragment.xml b/app/src/main/res/layout/wallet_fragment.xml
index 7c80acd..2a2e578 100644
--- a/app/src/main/res/layout/wallet_fragment.xml
+++ b/app/src/main/res/layout/wallet_fragment.xml
@@ -1,16 +1,10 @@
+
+ android:orientation="vertical">
- Loading Wallet List
Loading Wallet …
Saving Wallet
+ Wallet saved
Connecting …
Working on it …
Password for
Bad password!
+ Wallet does not exists!
Daemon address must be set!
- Daemon type does not fit to wallet!
- Cannot connect to daemon!
+ Wallet does not match selected net
+ Cannot connect to daemon! Try again.
Something\'s wrong!
Amount
@@ -43,4 +45,44 @@
External Storage is not writable! Panic!
We really need those External Storage permissions!
+ Generate Wallet
+ Wallet Name
+ Wallet Password
+ Do it already!
+ Mnemonic Seed
+ I have noted the mnemonic seed\nNow, I want to loose all my money!
+ I\'m confused - Let me start again!
+
+ <Watch Only Wallet>
+
+ Wallet exists! Choose another name
+ Wallet created
+ Wallet create failed (1/2)
+ Wallet create failed (2/2)
+ 9tDC52GsMjTNt4dpnRCwAF7ekVBkbkgkXGaMKTcSTpBhGpqkPX56jCNRydLq9oGjbbAQBsZhLfgmTKsntmxRd3TaJFYM2f8
+ e2b99f4cc3d644774c4b118db05f8aa9967583a01ca4d47058c3860af10bd306
+ 300a54208ab0a638a8407a12e3de946da76f5a9ded303338452332ec7755210d
+ camp feline inflamed memoir afloat eight alerts females gutter cogs menu waveform gather tawny judge gusts yahoo doctor females biscuit alchemy reef agony austere camp
+ 1307882
+
+ Public Address (optional)
+ View Key
+ Spend Key (optional)
+ Mnemonic Seed (optional)
+ Restore Height (optional)
+
+ Wallet
+ Password
+ Public Address
+ View Key
+ Spend Key
+ Mnemonic Seed
+ Restore Height:
+
+ Check your keys!
+ Check your key!
+ Check your address!
+ Check your mnemonic seed!
+ Check your entry!
+