mirror of
https://github.com/m2049r/xmrwallet
synced 2025-09-05 09:58:42 +02:00
Compare commits
38 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
3e9be418a8 | ||
![]() |
fa5dc9988d | ||
![]() |
857cf8d6d8 | ||
![]() |
63e0c265cb | ||
![]() |
39d2c0a25f | ||
![]() |
ff6e00e1a1 | ||
![]() |
3a839a04d5 | ||
![]() |
42e4db5cc1 | ||
![]() |
d18999e731 | ||
![]() |
425246beb1 | ||
![]() |
e1a2572236 | ||
![]() |
7fdae76f51 | ||
![]() |
b4e1767a7b | ||
![]() |
9b9995437d | ||
![]() |
94abc00422 | ||
![]() |
d7d6601b60 | ||
![]() |
bf95994c9e | ||
![]() |
a2d6ca0740 | ||
![]() |
1115bbb706 | ||
![]() |
0b17ed4322 | ||
![]() |
bcc85a5b3f | ||
![]() |
5b26f1a30b | ||
![]() |
63677d5027 | ||
![]() |
96579c1be4 | ||
![]() |
af58b76f0c | ||
![]() |
7879f31f63 | ||
![]() |
2ca7b41982 | ||
![]() |
8bdc0f8fde | ||
![]() |
0bb9e9cb6c | ||
![]() |
bdc2a72790 | ||
![]() |
073bd96b17 | ||
![]() |
37f22a9dc2 | ||
![]() |
bc3c5b3f66 | ||
![]() |
c61a62cc85 | ||
![]() |
66a6583ec4 | ||
![]() |
d24b37e2f2 | ||
![]() |
161c155de6 | ||
![]() |
dd59233dd4 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,10 +1,7 @@
|
||||
.gradle
|
||||
/build
|
||||
*.iml
|
||||
/.idea/libraries
|
||||
/.idea/workspace.xml
|
||||
/.idea/caches
|
||||
/.idea/codeStyles
|
||||
/.idea
|
||||
/local.properties
|
||||
/captures
|
||||
.externalNativeBuild
|
||||
|
18
README.md
18
README.md
@@ -1,5 +1,12 @@
|
||||
# Monerujo
|
||||
Another Android Monero Wallet
|
||||
Another Android Monero Wallet for Monero
|
||||
**(not
|
||||
Monero Classic,
|
||||
Monero-Classic,
|
||||
Monero Zero,
|
||||
Monero Original,
|
||||
Monero C,
|
||||
Monero V)**
|
||||
|
||||
### QUICKSTART
|
||||
- Download the APK for the most current release [here](https://github.com/m2049r/xmrwallet/releases) and install it
|
||||
@@ -8,6 +15,9 @@ Another Android Monero Wallet
|
||||
- Advanced users can copy over synced wallet files (all files) onto sdcard in directory Monerujo (created first time App is started)
|
||||
- See the [FAQ](doc/FAQ.md)
|
||||
|
||||
## Translations
|
||||
Help us translate Monerujo! You can find instructions [On Taiga](https://taiga.getmonero.org/project/erciccione-monero-localization/wiki/monerujo), and if you need help/support, open an issue or contact the Localization Workgroup. You can find us on the freenode channel `#monero-translations`, also relayed on [MatterMost](https://mattermost.getmonero.org/monero/channels/monero-translations), and matrix/riot.
|
||||
|
||||
### Disclaimer
|
||||
You may lose all your Moneroj if you use this App. Be cautious when spending on the mainnet.
|
||||
|
||||
@@ -24,6 +34,8 @@ You may lose all your Moneroj if you use this App. Be cautious when spending on
|
||||
- see taiga.getmonero.org & issues on github
|
||||
|
||||
### Issues / Pitfalls
|
||||
- Users of Zenfone MAX & Zenfone 2 Laser (possibly others) **MUST** use the armeabi-v7a APK as the arm64-v8a build uses hardware AES
|
||||
functionality these models don't have.
|
||||
- You should backup your wallet files in the "monerujo" folder periodically.
|
||||
- Also note, that on some devices the backups will only be visible on a PC over USB after a reboot of the device (it's an Android bug/feature)
|
||||
- Created wallets on a private testnet are unusable because the restore height is set to that
|
||||
@@ -34,8 +46,8 @@ The official monero client shows the same behaviour.
|
||||
No need to build. Binaries are included:
|
||||
|
||||
- openssl-1.0.2l
|
||||
- monero-v0.11.1.0
|
||||
- boost_1_64_0
|
||||
- monero-v0.12
|
||||
- boost_1_58_0
|
||||
|
||||
If you want to build them yourself (recommended) check out [the instructions](doc/BUILDING-external-libs.md)
|
||||
|
||||
|
@@ -7,8 +7,8 @@ android {
|
||||
applicationId "com.m2049r.xmrwallet"
|
||||
minSdkVersion 21
|
||||
targetSdkVersion 25
|
||||
versionCode 87
|
||||
versionName "1.4.7 'Monero Spedner'"
|
||||
versionCode 91
|
||||
versionName "1.5.1 'CrAzY Nacho'"
|
||||
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
@@ -66,7 +66,6 @@ dependencies {
|
||||
implementation 'com.android.support:support-v4:25.4.0'
|
||||
implementation 'com.android.support:recyclerview-v7:25.4.0'
|
||||
implementation 'com.android.support:cardview-v7:25.4.0'
|
||||
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
|
||||
implementation 'me.dm7.barcodescanner:zxing:1.9.8'
|
||||
|
||||
implementation "com.squareup.okhttp3:okhttp:$rootProject.ext.okHttpVersion"
|
||||
|
@@ -7,6 +7,7 @@
|
||||
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.USE_FINGERPRINT" />
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
|
@@ -695,6 +695,23 @@ Java_com_m2049r_xmrwallet_model_Wallet_isSynchronized(JNIEnv *env, jobject insta
|
||||
return static_cast<jboolean>(wallet->synchronized());
|
||||
}
|
||||
|
||||
//void cn_slow_hash(const void *data, size_t length, char *hash); // from crypto/hash-ops.h
|
||||
JNIEXPORT jbyteArray JNICALL
|
||||
Java_com_m2049r_xmrwallet_util_KeyStoreHelper_cnSlowHash(JNIEnv *env, jobject clazz,
|
||||
jbyteArray data) {
|
||||
|
||||
jbyte *buffer = env->GetByteArrayElements(data, NULL);
|
||||
jsize size = env->GetArrayLength(data);
|
||||
char hash[HASH_SIZE];
|
||||
cn_slow_hash(buffer, (size_t) size, hash);
|
||||
|
||||
env->ReleaseByteArrayElements(data, buffer, JNI_ABORT); // do not update java byte[]
|
||||
|
||||
jbyteArray result = env->NewByteArray(HASH_SIZE);
|
||||
env->SetByteArrayRegion(result, 0, HASH_SIZE, (jbyte *) hash);
|
||||
return result;
|
||||
}
|
||||
|
||||
JNIEXPORT jstring JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_Wallet_getDisplayAmount(JNIEnv *env, jobject clazz,
|
||||
jlong amount) {
|
||||
@@ -1083,7 +1100,8 @@ Java_com_m2049r_xmrwallet_model_PendingTransaction_getTxCount(JNIEnv *env, jobje
|
||||
//static void error(const std::string &category, const std::string &str);
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_WalletManager_initLogger(JNIEnv *env, jobject instance,
|
||||
jstring argv0, jstring default_log_base_name) {
|
||||
jstring argv0,
|
||||
jstring default_log_base_name) {
|
||||
|
||||
const char *_argv0 = env->GetStringUTFChars(argv0, NULL);
|
||||
const char *_default_log_base_name = env->GetStringUTFChars(default_log_base_name, NULL);
|
||||
@@ -1109,7 +1127,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_logDebug(JNIEnv *env, jobject inst
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_WalletManager_logInfo(JNIEnv *env, jobject instance,
|
||||
jstring category, jstring message) {
|
||||
jstring category, jstring message) {
|
||||
|
||||
const char *_category = env->GetStringUTFChars(category, NULL);
|
||||
const char *_message = env->GetStringUTFChars(message, NULL);
|
||||
@@ -1122,7 +1140,7 @@ Java_com_m2049r_xmrwallet_model_WalletManager_logInfo(JNIEnv *env, jobject insta
|
||||
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_WalletManager_logWarning(JNIEnv *env, jobject instance,
|
||||
jstring category, jstring message) {
|
||||
jstring category, jstring message) {
|
||||
|
||||
const char *_category = env->GetStringUTFChars(category, NULL);
|
||||
const char *_message = env->GetStringUTFChars(message, NULL);
|
||||
@@ -1149,11 +1167,10 @@ Java_com_m2049r_xmrwallet_model_WalletManager_logError(JNIEnv *env, jobject inst
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_m2049r_xmrwallet_model_WalletManager_setLogLevel(JNIEnv *env, jobject instance,
|
||||
jint level) {
|
||||
Bitmonero::WalletManagerFactory::setLogLevel(level);
|
||||
Bitmonero::WalletManagerFactory::setLogLevel(level);
|
||||
}
|
||||
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
@@ -18,6 +18,7 @@
|
||||
#define XMRWALLET_WALLET_LIB_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
/*
|
||||
#include <android/log.h>
|
||||
|
||||
@@ -27,13 +28,13 @@
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,LOG_TAG,__VA_ARGS__)
|
||||
*/
|
||||
|
||||
jfieldID getHandleField(JNIEnv *env, jobject obj, const char* fieldName = "handle") {
|
||||
jfieldID getHandleField(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||
jclass c = env->GetObjectClass(obj);
|
||||
return env->GetFieldID(c, fieldName, "J"); // of type long
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
T *getHandle(JNIEnv *env, jobject obj, const char* fieldName = "handle") {
|
||||
template<typename T>
|
||||
T *getHandle(JNIEnv *env, jobject obj, const char *fieldName = "handle") {
|
||||
jlong handle = env->GetLongField(obj, getHandleField(env, obj, fieldName));
|
||||
return reinterpret_cast<T *>(handle);
|
||||
}
|
||||
@@ -42,10 +43,27 @@ void setHandleFromLong(JNIEnv *env, jobject obj, jlong handle) {
|
||||
env->SetLongField(obj, getHandleField(env, obj), handle);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
template<typename T>
|
||||
void setHandle(JNIEnv *env, jobject obj, T *t) {
|
||||
jlong handle = reinterpret_cast<jlong>(t);
|
||||
setHandleFromLong(env, obj, handle);
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C"
|
||||
{
|
||||
#endif
|
||||
|
||||
// from monero-core crypto/hash-ops.h - avoid #including monero code here
|
||||
enum {
|
||||
HASH_SIZE = 32,
|
||||
HASH_DATA_AREA = 136
|
||||
};
|
||||
|
||||
void cn_slow_hash(const void *data, size_t length, char *hash);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif //XMRWALLET_WALLET_LIB_H
|
||||
|
@@ -16,12 +16,15 @@
|
||||
|
||||
package com.m2049r.xmrwallet;
|
||||
|
||||
import android.app.AlertDialog;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.text.Editable;
|
||||
import android.text.Html;
|
||||
import android.text.InputType;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
@@ -32,20 +35,23 @@ import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.widget.Button;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.Switch;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
import com.m2049r.xmrwallet.model.Wallet;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.FingerprintHelper;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.RestoreHeight;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
import com.nulabinc.zxcvbn.Strength;
|
||||
import com.nulabinc.zxcvbn.Zxcvbn;
|
||||
|
||||
import java.io.File;
|
||||
import java.text.ParseException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -59,6 +65,7 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
private TextInputLayout etWalletName;
|
||||
private TextInputLayout etWalletPassword;
|
||||
private LinearLayout llFingerprintAuth;
|
||||
private TextInputLayout etWalletAddress;
|
||||
private TextInputLayout etWalletMnemonic;
|
||||
private TextInputLayout etWalletViewKey;
|
||||
@@ -79,6 +86,7 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
etWalletName = (TextInputLayout) view.findViewById(R.id.etWalletName);
|
||||
etWalletPassword = (TextInputLayout) view.findViewById(R.id.etWalletPassword);
|
||||
llFingerprintAuth = (LinearLayout) view.findViewById(R.id.llFingerprintAuth);
|
||||
etWalletMnemonic = (TextInputLayout) view.findViewById(R.id.etWalletMnemonic);
|
||||
etWalletAddress = (TextInputLayout) view.findViewById(R.id.etWalletAddress);
|
||||
etWalletViewKey = (TextInputLayout) view.findViewById(R.id.etWalletViewKey);
|
||||
@@ -146,6 +154,30 @@ public class GenerateFragment extends Fragment {
|
||||
}
|
||||
});
|
||||
|
||||
if (FingerprintHelper.isDeviceSupported(getContext())) {
|
||||
llFingerprintAuth.setVisibility(View.VISIBLE);
|
||||
|
||||
final Switch swFingerprintAllowed = (Switch) llFingerprintAuth.getChildAt(0);
|
||||
swFingerprintAllowed.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
if (!swFingerprintAllowed.isChecked()) return;
|
||||
|
||||
AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
|
||||
builder.setMessage(Html.fromHtml(getString(R.string.generate_fingerprint_warn)))
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(getString(R.string.label_ok), null)
|
||||
.setNegativeButton(getString(R.string.label_cancel), new DialogInterface.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(DialogInterface dialogInterface, int i) {
|
||||
swFingerprintAllowed.setChecked(false);
|
||||
}
|
||||
})
|
||||
.show();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (type.equals(TYPE_NEW)) {
|
||||
etWalletPassword.getEditText().setImeOptions(EditorInfo.IME_ACTION_DONE);
|
||||
etWalletPassword.getEditText().setOnEditorActionListener(new TextView.OnEditorActionListener() {
|
||||
@@ -423,18 +455,28 @@ public class GenerateFragment extends Fragment {
|
||||
|
||||
String name = etWalletName.getEditText().getText().toString();
|
||||
String password = etWalletPassword.getEditText().getText().toString();
|
||||
boolean fingerprintAuthAllowed = ((Switch) llFingerprintAuth.getChildAt(0)).isChecked();
|
||||
|
||||
// create the real wallet password
|
||||
String crazyPass = KeyStoreHelper.getCrazyPass(getActivity(), password);
|
||||
|
||||
long height = getHeight();
|
||||
if (height < 0) height = 0;
|
||||
|
||||
if (type.equals(TYPE_NEW)) {
|
||||
bGenerate.setEnabled(false);
|
||||
activityCallback.onGenerate(name, password);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
activityCallback.onGenerate(name, crazyPass);
|
||||
} else if (type.equals(TYPE_SEED)) {
|
||||
if (!checkMnemonic()) return;
|
||||
String seed = etWalletMnemonic.getEditText().getText().toString();
|
||||
bGenerate.setEnabled(false);
|
||||
activityCallback.onGenerate(name, password, seed, height);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
activityCallback.onGenerate(name, crazyPass, seed, height);
|
||||
} else if (type.equals(TYPE_KEY) || type.equals(TYPE_VIEWONLY)) {
|
||||
if (checkAddress() && checkViewKey() && checkSpendKey()) {
|
||||
bGenerate.setEnabled(false);
|
||||
@@ -444,7 +486,10 @@ public class GenerateFragment extends Fragment {
|
||||
if (type.equals(TYPE_KEY)) {
|
||||
spendKey = etWalletSpendKey.getEditText().getText().toString();
|
||||
}
|
||||
activityCallback.onGenerate(name, password, address, viewKey, spendKey, height);
|
||||
if (fingerprintAuthAllowed) {
|
||||
KeyStoreHelper.saveWalletUserPass(getActivity(), name, password);
|
||||
}
|
||||
activityCallback.onGenerate(name, crazyPass, address, viewKey, spendKey, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -18,12 +18,15 @@ package com.m2049r.xmrwallet;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.os.Bundle;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.support.design.widget.FloatingActionButton;
|
||||
import android.support.v4.app.Fragment;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v7.widget.RecyclerView;
|
||||
import android.view.Gravity;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
@@ -38,7 +41,9 @@ import android.widget.AdapterView;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.EditText;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.ImageView;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.RelativeLayout;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
@@ -48,13 +53,16 @@ import com.m2049r.xmrwallet.layout.WalletInfoAdapter;
|
||||
import com.m2049r.xmrwallet.model.NetworkType;
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.KeyStoreHelper;
|
||||
import com.m2049r.xmrwallet.util.NodeList;
|
||||
import com.m2049r.xmrwallet.util.Notice;
|
||||
import com.m2049r.xmrwallet.widget.DropDownEditText;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
@@ -71,9 +79,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
private DropDownEditText etDaemonAddress;
|
||||
private ArrayAdapter<String> nodeAdapter;
|
||||
|
||||
private View llXmrToEnabled;
|
||||
private View ibXmrToInfoClose;
|
||||
|
||||
private Listener activityCallback;
|
||||
|
||||
// Container Activity must implement this interface
|
||||
@@ -173,23 +178,8 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
|
||||
etDummy = (EditText) view.findViewById(R.id.etDummy);
|
||||
|
||||
llXmrToEnabled = view.findViewById(R.id.llXmrToEnabled);
|
||||
llXmrToEnabled.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HelpFragment.display(getChildFragmentManager(), R.string.help_xmrto);
|
||||
|
||||
}
|
||||
});
|
||||
ibXmrToInfoClose = view.findViewById(R.id.ibXmrToInfoClose);
|
||||
ibXmrToInfoClose.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
showXmrtoEnabled = false;
|
||||
saveXmrToPrefs();
|
||||
}
|
||||
});
|
||||
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
|
||||
Notice.showAll(llNotice,".*_login");
|
||||
|
||||
etDaemonAddress = (DropDownEditText) view.findViewById(R.id.etDaemonAddress);
|
||||
nodeAdapter = new ArrayAdapter<>(getContext(), android.R.layout.simple_dropdown_item_1line);
|
||||
@@ -237,9 +227,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
});
|
||||
|
||||
loadPrefs();
|
||||
if (!showXmrtoEnabled) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
return view;
|
||||
}
|
||||
@@ -326,6 +313,17 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
ivGunther.setImageDrawable(null);
|
||||
}
|
||||
}
|
||||
|
||||
// remove information of non-existent wallet
|
||||
Set<String> removedWallets = getActivity()
|
||||
.getSharedPreferences(KeyStoreHelper.SecurityConstants.WALLET_PASS_PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.getAll().keySet();
|
||||
for (WalletManager.WalletInfo s : walletList) {
|
||||
removedWallets.remove(s.name);
|
||||
}
|
||||
for (String name : removedWallets) {
|
||||
KeyStoreHelper.removeWalletUserPass(getActivity(), name);
|
||||
}
|
||||
}
|
||||
|
||||
private void showInfo(@NonNull String name) {
|
||||
@@ -379,7 +377,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
|
||||
private static final String PREF_DAEMON_TESTNET = "daemon_testnet";
|
||||
private static final String PREF_DAEMON_MAINNET = "daemon_mainnet";
|
||||
private static final String PREF_SHOW_XMRTO_ENABLED = "info_xmrto_enabled_login";
|
||||
|
||||
private static final String PREF_DAEMONLIST_MAINNET =
|
||||
"node.moneroworld.com:18089;node.xmrbackb.one;node.xmr.be";
|
||||
@@ -390,23 +387,12 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
private NodeList daemonTestNet;
|
||||
private NodeList daemonMainNet;
|
||||
|
||||
boolean showXmrtoEnabled = true;
|
||||
|
||||
void loadPrefs() {
|
||||
SharedPreferences sharedPref = activityCallback.getPrefs();
|
||||
|
||||
daemonMainNet = new NodeList(sharedPref.getString(PREF_DAEMON_MAINNET, PREF_DAEMONLIST_MAINNET));
|
||||
daemonTestNet = new NodeList(sharedPref.getString(PREF_DAEMON_TESTNET, PREF_DAEMONLIST_TESTNET));
|
||||
setNet(testnetCheckMenu, false);
|
||||
|
||||
showXmrtoEnabled = sharedPref.getBoolean(PREF_SHOW_XMRTO_ENABLED, true);
|
||||
}
|
||||
|
||||
void saveXmrToPrefs() {
|
||||
SharedPreferences sharedPref = activityCallback.getPrefs();
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
editor.putBoolean(PREF_SHOW_XMRTO_ENABLED, showXmrtoEnabled);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
void savePrefs() {
|
||||
@@ -428,7 +414,6 @@ public class LoginFragment extends Fragment implements WalletInfoAdapter.OnInter
|
||||
SharedPreferences.Editor editor = sharedPref.edit();
|
||||
editor.putString(PREF_DAEMON_MAINNET, daemonMainNet.toString());
|
||||
editor.putString(PREF_DAEMON_TESTNET, daemonTestNet.toString());
|
||||
editor.putBoolean(PREF_SHOW_XMRTO_ENABLED, showXmrtoEnabled);
|
||||
editor.apply();
|
||||
}
|
||||
|
||||
|
@@ -335,6 +335,7 @@ public class ReceiveFragment extends Fragment {
|
||||
}
|
||||
|
||||
public Bitmap generate(String text, int width, int height) {
|
||||
if ((width <= 0) || (height <= 0)) return null;
|
||||
Map<EncodeHintType, Object> hints = new HashMap<>();
|
||||
hints.put(EncodeHintType.CHARACTER_SET, "utf-8");
|
||||
hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M);
|
||||
|
@@ -49,21 +49,34 @@ import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.UserNotes;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
import timber.log.Timber;
|
||||
|
||||
public class WalletActivity extends SecureActivity implements WalletFragment.Listener,
|
||||
WalletService.Observer, SendFragment.Listener, TxFragment.Listener,
|
||||
GenerateReviewFragment.ListenerWithWallet,
|
||||
GenerateReviewFragment.Listener,
|
||||
GenerateReviewFragment.PasswordChangedListener,
|
||||
ScannerFragment.OnScannedListener, ReceiveFragment.Listener,
|
||||
SendAddressWizardFragment.OnScanListener {
|
||||
|
||||
public static final String REQUEST_ID = "id";
|
||||
public static final String REQUEST_PW = "pw";
|
||||
public static final String REQUEST_FINGERPRINT_USED = "fingerprint";
|
||||
|
||||
private Toolbar toolbar;
|
||||
private boolean needVerifyIdentity;
|
||||
|
||||
private String password;
|
||||
|
||||
@Override
|
||||
public void onPasswordChanged(String newPassword) {
|
||||
password = newPassword;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setToolbarButton(int type) {
|
||||
@@ -119,8 +132,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
||||
if (extras != null) {
|
||||
acquireWakeLock();
|
||||
String walletId = extras.getString(REQUEST_ID);
|
||||
String walletPassword = extras.getString(REQUEST_PW);
|
||||
connectWalletService(walletId, walletPassword);
|
||||
needVerifyIdentity = extras.getBoolean(REQUEST_FINGERPRINT_USED);
|
||||
connectWalletService(walletId, password);
|
||||
} else {
|
||||
finish();
|
||||
//throw new IllegalStateException("No extras passed! Panic!");
|
||||
@@ -174,6 +187,9 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
||||
case R.id.action_details_help:
|
||||
HelpFragment.display(getSupportFragmentManager(), R.string.help_details);
|
||||
return true;
|
||||
case R.id.action_details_changepw:
|
||||
onWalletChangePassword();
|
||||
return true;
|
||||
case R.id.action_help_send:
|
||||
HelpFragment.display(getSupportFragmentManager(), R.string.help_send);
|
||||
return true;
|
||||
@@ -182,6 +198,20 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
||||
}
|
||||
}
|
||||
|
||||
public void onWalletChangePassword() {
|
||||
try {
|
||||
GenerateReviewFragment detailsFragment = (GenerateReviewFragment)
|
||||
getSupportFragmentManager().findFragmentById(R.id.fragment_container);
|
||||
AlertDialog dialog = detailsFragment.createChangePasswordDialog();
|
||||
if (dialog != null) {
|
||||
Helper.showKeyboard(dialog);
|
||||
dialog.show();
|
||||
}
|
||||
} catch (ClassCastException ex) {
|
||||
Timber.w("onWalletChangePassword() called, but no GenerateReviewFragment active");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onCreate(Bundle savedInstanceState) {
|
||||
Timber.d("onCreate()");
|
||||
@@ -229,6 +259,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
||||
.add(R.id.fragment_container, walletFragment, WalletFragment.class.getName()).commit();
|
||||
Timber.d("fragment added");
|
||||
|
||||
password = getIntent().getExtras().getString(REQUEST_PW);
|
||||
|
||||
startWalletService();
|
||||
Timber.d("onCreate() done.");
|
||||
}
|
||||
@@ -380,7 +412,17 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
||||
|
||||
@Override
|
||||
public void onSendRequest() {
|
||||
replaceFragment(new SendFragment(), null, null);
|
||||
if (needVerifyIdentity) {
|
||||
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password, boolean fingerprintUsed) {
|
||||
replaceFragment(new SendFragment(), null, null);
|
||||
needVerifyIdentity = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
replaceFragment(new SendFragment(), null, null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -680,9 +722,21 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
||||
public void onClick(DialogInterface dialog, int which) {
|
||||
switch (which) {
|
||||
case DialogInterface.BUTTON_POSITIVE:
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString("type", GenerateReviewFragment.VIEW_TYPE_WALLET);
|
||||
replaceFragment(new GenerateReviewFragment(), null, extras);
|
||||
final Bundle extras = new Bundle();
|
||||
extras.putString(GenerateReviewFragment.REQUEST_TYPE, GenerateReviewFragment.VIEW_TYPE_WALLET);
|
||||
|
||||
if (needVerifyIdentity) {
|
||||
Helper.promptPassword(WalletActivity.this, getWallet().getName(), true, new Helper.PasswordAction() {
|
||||
@Override
|
||||
public void action(String walletName, String password, boolean fingerprintUsed) {
|
||||
replaceFragment(new GenerateReviewFragment(), null, extras);
|
||||
needVerifyIdentity = false;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
replaceFragment(new GenerateReviewFragment(), null, extras);
|
||||
}
|
||||
|
||||
break;
|
||||
case DialogInterface.BUTTON_NEGATIVE:
|
||||
// do nothing
|
||||
@@ -810,9 +864,8 @@ public class WalletActivity extends SecureActivity implements WalletFragment.Lis
|
||||
|
||||
@Override
|
||||
public boolean verifyWalletPassword(String password) {
|
||||
String walletPath = new File(Helper.getWalletRoot(this),
|
||||
getWalletName() + ".keys").getAbsolutePath();
|
||||
return WalletManager.getInstance().verifyWalletPassword(walletPath, password, true);
|
||||
String walletPassword = Helper.getWalletPassword(getApplicationContext(), getWalletName(), password);
|
||||
return walletPassword != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -352,7 +352,7 @@ public class WalletFragment extends Fragment
|
||||
setProgress(x);
|
||||
ivSynced.setVisibility(View.GONE);
|
||||
} else {
|
||||
sync = getString(R.string.status_synced) + formatter.format(wallet.getBlockChainHeight());
|
||||
sync = getString(R.string.status_synced) + " " + formatter.format(wallet.getBlockChainHeight());
|
||||
ivSynced.setVisibility(View.VISIBLE);
|
||||
}
|
||||
} else {
|
||||
|
@@ -47,6 +47,7 @@ import com.m2049r.xmrwallet.layout.SpendViewPager;
|
||||
import com.m2049r.xmrwallet.model.PendingTransaction;
|
||||
import com.m2049r.xmrwallet.util.Helper;
|
||||
import com.m2049r.xmrwallet.util.NodeList;
|
||||
import com.m2049r.xmrwallet.util.Notice;
|
||||
import com.m2049r.xmrwallet.util.UserNotes;
|
||||
import com.m2049r.xmrwallet.widget.DotBar;
|
||||
import com.m2049r.xmrwallet.widget.Toolbar;
|
||||
@@ -119,27 +120,8 @@ public class SendFragment extends Fragment
|
||||
arrowPrev = getResources().getDrawable(R.drawable.ic_navigate_prev_white_24dp);
|
||||
arrowNext = getResources().getDrawable(R.drawable.ic_navigate_next_white_24dp);
|
||||
|
||||
llXmrToEnabled = view.findViewById(R.id.llXmrToEnabled);
|
||||
llXmrToEnabled.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HelpFragment.display(getChildFragmentManager(), R.string.help_xmrto);
|
||||
|
||||
}
|
||||
});
|
||||
ibXmrToInfoClose = view.findViewById(R.id.ibXmrToInfoClose);
|
||||
ibXmrToInfoClose.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
showXmrtoEnabled = false;
|
||||
saveXmrToPrefs();
|
||||
}
|
||||
});
|
||||
loadPrefs();
|
||||
if (!showXmrtoEnabled) {
|
||||
llXmrToEnabled.setVisibility(View.GONE);
|
||||
}
|
||||
ViewGroup llNotice = (ViewGroup) view.findViewById(R.id.llNotice);
|
||||
Notice.showAll(llNotice,".*_send");
|
||||
|
||||
spendViewPager = (SpendViewPager) view.findViewById(R.id.pager);
|
||||
pagerAdapter = new SpendPagerAdapter(getChildFragmentManager());
|
||||
@@ -291,7 +273,7 @@ public class SendFragment extends Fragment
|
||||
pagerAdapter.notifyDataSetChanged();
|
||||
}
|
||||
});
|
||||
Timber.d("New Mode = " + mode.toString());
|
||||
Timber.d("New Mode = %s", mode.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -350,7 +332,7 @@ public class SendFragment extends Fragment
|
||||
@Override
|
||||
public SendWizardFragment getItem(int position) {
|
||||
Timber.d("getItem(%d) CREATE", position);
|
||||
Timber.d("Mode=" + mode.toString());
|
||||
Timber.d("Mode=%s", mode.toString());
|
||||
if (mode == Mode.XMR) {
|
||||
switch (position) {
|
||||
case POS_ADDRESS:
|
||||
|
@@ -166,7 +166,7 @@ public class Wallet {
|
||||
|
||||
//TODO static static bool keyValid(const std::string &secret_key_string, const std::string &address_string, bool isViewKey, bool testnet, std::string &error);
|
||||
|
||||
public static native String getPaymentIdFromAddress(String address, boolean isTestNet);
|
||||
public static native String getPaymentIdFromAddress(String address, int networkType);
|
||||
|
||||
public static native long getMaximumAllowedAmount();
|
||||
|
||||
@@ -275,5 +275,4 @@ public class Wallet {
|
||||
//virtual bool parse_uri(const std::string &uri, std::string &address, std::string &payment_id, uint64_t &tvAmount, std::string &tx_description, std::string &recipient_name, std::vector<std::string> &unknown_parameters, std::string &error) = 0;
|
||||
//virtual bool rescanSpent() = 0;
|
||||
|
||||
|
||||
}
|
||||
|
@@ -271,6 +271,7 @@ public class WalletManager {
|
||||
//TODO static std::tuple<bool, std::string, std::string, std::string, std::string> checkUpdates(const std::string &software, const std::string &subdir);
|
||||
|
||||
static public native void initLogger(String argv0, String defaultLogBaseName);
|
||||
|
||||
//TODO: maybe put these in an enum like in monero core - but why?
|
||||
static public int LOGLEVEL_SILENT = -1;
|
||||
static public int LOGLEVEL_WARN = 0;
|
||||
@@ -278,9 +279,14 @@ public class WalletManager {
|
||||
static public int LOGLEVEL_DEBUG = 2;
|
||||
static public int LOGLEVEL_TRACE = 3;
|
||||
static public int LOGLEVEL_MAX = 4;
|
||||
|
||||
static public native void setLogLevel(int level);
|
||||
|
||||
static public native void logDebug(String category, String message);
|
||||
|
||||
static public native void logInfo(String category, String message);
|
||||
|
||||
static public native void logWarning(String category, String message);
|
||||
|
||||
static public native void logError(String category, String message);
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import com.m2049r.xmrwallet.model.WalletManager;
|
||||
|
||||
import java.math.BigInteger;
|
||||
|
||||
public class CrazyPassEncoder {
|
||||
static final String BASE = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
|
||||
static final int PW_CHARS = 52;
|
||||
|
||||
// this takes a 32 byte buffer and converts it to 52 alphnumeric characters
|
||||
// separated by blanks every 4 characters = 13 groups of 4
|
||||
// always (padding by Xs if need be
|
||||
static public String encode(byte[] data) {
|
||||
if (data.length != 32) throw new IllegalArgumentException("data[] is not 32 bytes long");
|
||||
BigInteger rest = new BigInteger(1, data);
|
||||
BigInteger remainder;
|
||||
final StringBuilder result = new StringBuilder();
|
||||
final BigInteger base = BigInteger.valueOf(BASE.length());
|
||||
int i = 0;
|
||||
do {
|
||||
if ((i > 0) && (i % 4 == 0)) result.append(' ');
|
||||
i++;
|
||||
remainder = rest.remainder(base);
|
||||
rest = rest.divide(base);
|
||||
result.append(BASE.charAt(remainder.intValue()));
|
||||
} while (!BigInteger.ZERO.equals(rest));
|
||||
// pad it
|
||||
while (i < PW_CHARS) {
|
||||
if ((i > 0) && (i % 4 == 0)) result.append(' ');
|
||||
result.append('2');
|
||||
i++;
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
static public String reformat(String password) {
|
||||
// maybe this is a CrAzYpass without blanks? or lowercase letters
|
||||
String noBlanks = password.toUpperCase().replaceAll(" ", "");
|
||||
if (noBlanks.length() == PW_CHARS) { // looks like a CrAzYpass
|
||||
// insert blanks every 4 characters
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (int i = 0; i < PW_CHARS; i++) {
|
||||
if ((i > 0) && (i % 4 == 0)) sb.append(' ');
|
||||
char c = noBlanks.charAt(i);
|
||||
if (BASE.indexOf(c) < 0) return null; // invalid character found
|
||||
sb.append(c);
|
||||
}
|
||||
return sb.toString();
|
||||
} else {
|
||||
return null; // not a CrAzYpass
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,40 @@
|
||||
package com.m2049r.xmrwallet.util;
|
||||
|
||||
import android.app.KeyguardManager;
|
||||
import android.content.Context;
|
||||
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import android.support.v4.os.CancellationSignal;
|
||||
|
||||
import java.security.KeyStore;
|
||||
import java.security.KeyStoreException;
|
||||
|
||||
public class FingerprintHelper {
|
||||
|
||||
public static boolean isDeviceSupported(Context context) {
|
||||
FingerprintManagerCompat fingerprintManager = FingerprintManagerCompat.from(context);
|
||||
KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
|
||||
|
||||
return keyguardManager != null &&
|
||||
keyguardManager.isKeyguardSecure() &&
|
||||
fingerprintManager.isHardwareDetected() &&
|
||||
fingerprintManager.hasEnrolledFingerprints();
|
||||
}
|
||||
|
||||
public static boolean isFingerprintAuthAllowed(String wallet) throws KeyStoreException {
|
||||
KeyStore keyStore = KeyStore.getInstance(KeyStoreHelper.SecurityConstants.KEYSTORE_PROVIDER_ANDROID_KEYSTORE);
|
||||
try {
|
||||
keyStore.load(null);
|
||||
} catch (Exception ex) {
|
||||
throw new IllegalStateException("Could not load KeyStore", ex);
|
||||
}
|
||||
|
||||
return keyStore.containsAlias(KeyStoreHelper.SecurityConstants.WALLET_PASS_KEY_PREFIX + wallet);
|
||||
}
|
||||
|
||||
public static void authenticate(Context context, CancellationSignal cancelSignal,
|
||||
FingerprintManagerCompat.AuthenticationCallback callback) {
|
||||
FingerprintManagerCompat manager = FingerprintManagerCompat.from(context);
|
||||
manager.authenticate(null, 0, cancelSignal, callback, null);
|
||||
}
|
||||
|
||||
}
|
@@ -18,10 +18,12 @@ package com.m2049r.xmrwallet.util;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.Activity;
|
||||
import android.app.AlertDialog;
|
||||
import android.app.Dialog;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
@@ -30,13 +32,24 @@ import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.drawable.VectorDrawable;
|
||||
import android.os.Environment;
|
||||
import android.support.design.widget.TextInputLayout;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.support.v4.hardware.fingerprint.FingerprintManagerCompat;
|
||||
import android.support.v4.os.CancellationSignal;
|
||||
import android.system.ErrnoException;
|
||||
import android.system.Os;
|
||||
import android.text.Editable;
|
||||
import android.text.TextWatcher;
|
||||
import android.view.KeyEvent;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.animation.Animation;
|
||||
import android.view.animation.AnimationUtils;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.Button;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.BuildConfig;
|
||||
import com.m2049r.xmrwallet.R;
|
||||
@@ -47,9 +60,11 @@ import com.m2049r.xmrwallet.model.WalletManager;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.math.BigInteger;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.SocketTimeoutException;
|
||||
import java.net.URL;
|
||||
import java.security.KeyStoreException;
|
||||
import java.util.Locale;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
@@ -58,8 +73,8 @@ import okhttp3.HttpUrl;
|
||||
import timber.log.Timber;
|
||||
|
||||
public class Helper {
|
||||
static private final String WALLET_DIR = "monerujo";
|
||||
static private final String HOME_DIR = "monero";
|
||||
static private final String WALLET_DIR = "monerujo" + (BuildConfig.DEBUG ? "-debug" : "");
|
||||
static private final String HOME_DIR = "monero" + (BuildConfig.DEBUG ? "-debug" : "");
|
||||
|
||||
static public int DISPLAY_DIGITS_INFO = 5;
|
||||
|
||||
@@ -280,6 +295,14 @@ public class Helper {
|
||||
}
|
||||
}
|
||||
|
||||
private final static char[] HexArray = "0123456789ABCDEF".toCharArray();
|
||||
|
||||
public static String bytesToHex(byte[] data) {
|
||||
if ((data != null) && (data.length > 0))
|
||||
return String.format("%0" + (data.length * 2) + "X", new BigInteger(1, data));
|
||||
else return "";
|
||||
}
|
||||
|
||||
static public void setMoneroHome(Context context) {
|
||||
try {
|
||||
String home = getStorage(context, HOME_DIR).getAbsolutePath();
|
||||
@@ -303,4 +326,178 @@ public class Helper {
|
||||
if (level >= WalletManager.LOGLEVEL_SILENT)
|
||||
WalletManager.setLogLevel(level);
|
||||
}
|
||||
|
||||
// try to figure out what the real wallet password is given the user password
|
||||
// which could be the actual wallet password or a (maybe malformed) CrAzYpass
|
||||
// or the password used to derive the CrAzYpass for the wallet
|
||||
static public String getWalletPassword(Context context, String walletName, String password) {
|
||||
String walletPath = new File(getWalletRoot(context), walletName + ".keys").getAbsolutePath();
|
||||
|
||||
// try with entered password (which could be a legacy password or a CrAzYpass)
|
||||
if (WalletManager.getInstance().verifyWalletPassword(walletPath, password, true)) {
|
||||
return password;
|
||||
}
|
||||
|
||||
// maybe this is a malformed CrAzYpass?
|
||||
String possibleCrazyPass = CrazyPassEncoder.reformat(password);
|
||||
if (possibleCrazyPass != null) { // looks like a CrAzYpass
|
||||
if (WalletManager.getInstance().verifyWalletPassword(walletPath, possibleCrazyPass, true)) {
|
||||
return possibleCrazyPass;
|
||||
}
|
||||
}
|
||||
|
||||
// generate & try with CrAzYpass
|
||||
String crazyPass = KeyStoreHelper.getCrazyPass(context, password);
|
||||
if (WalletManager.getInstance().verifyWalletPassword(walletPath, crazyPass, true)) {
|
||||
return crazyPass;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
static AlertDialog openDialog = null; // for preventing opening of multiple dialogs
|
||||
|
||||
static public void promptPassword(final Context context, final String wallet, boolean fingerprintDisabled, final PasswordAction action) {
|
||||
if (openDialog != null) return; // we are already asking for password
|
||||
LayoutInflater li = LayoutInflater.from(context);
|
||||
final View promptsView = li.inflate(R.layout.prompt_password, null);
|
||||
|
||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(context);
|
||||
alertDialogBuilder.setView(promptsView);
|
||||
|
||||
final TextInputLayout etPassword = (TextInputLayout) promptsView.findViewById(R.id.etPassword);
|
||||
etPassword.setHint(context.getString(R.string.prompt_password, wallet));
|
||||
|
||||
boolean fingerprintAuthCheck;
|
||||
try {
|
||||
fingerprintAuthCheck = FingerprintHelper.isFingerprintAuthAllowed(wallet);
|
||||
} catch (KeyStoreException ex) {
|
||||
fingerprintAuthCheck = false;
|
||||
}
|
||||
|
||||
final boolean fingerprintAuthAllowed = !fingerprintDisabled && fingerprintAuthCheck;
|
||||
final CancellationSignal cancelSignal = new CancellationSignal();
|
||||
|
||||
if (fingerprintAuthAllowed) {
|
||||
promptsView.findViewById(R.id.txtFingerprintAuth).setVisibility(View.VISIBLE);
|
||||
}
|
||||
|
||||
etPassword.getEditText().addTextChangedListener(new TextWatcher() {
|
||||
|
||||
@Override
|
||||
public void afterTextChanged(Editable s) {
|
||||
if (etPassword.getError() != null) {
|
||||
etPassword.setError(null);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeTextChanged(CharSequence s, int start,
|
||||
int count, int after) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextChanged(CharSequence s, int start,
|
||||
int before, int count) {
|
||||
}
|
||||
});
|
||||
|
||||
// set dialog message
|
||||
alertDialogBuilder
|
||||
.setCancelable(false)
|
||||
.setPositiveButton(context.getString(R.string.label_ok), null)
|
||||
.setNegativeButton(context.getString(R.string.label_cancel),
|
||||
new DialogInterface.OnClickListener() {
|
||||
public void onClick(DialogInterface dialog, int id) {
|
||||
Helper.hideKeyboardAlways((Activity) context);
|
||||
cancelSignal.cancel();
|
||||
dialog.cancel();
|
||||
openDialog = null;
|
||||
}
|
||||
});
|
||||
openDialog = alertDialogBuilder.create();
|
||||
|
||||
final FingerprintManagerCompat.AuthenticationCallback fingerprintAuthCallback = new FingerprintManagerCompat.AuthenticationCallback() {
|
||||
@Override
|
||||
public void onAuthenticationError(int errMsgId, CharSequence errString) {
|
||||
((TextView) promptsView.findViewById(R.id.txtFingerprintAuth)).setText(errString);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationSucceeded(FingerprintManagerCompat.AuthenticationResult result) {
|
||||
String userPass = KeyStoreHelper.loadWalletUserPass(context, wallet);
|
||||
if (Helper.processPasswordEntry(context, wallet, userPass, true, action)) {
|
||||
Helper.hideKeyboardAlways((Activity) context);
|
||||
openDialog.dismiss();
|
||||
openDialog = null;
|
||||
} else {
|
||||
etPassword.setError(context.getString(R.string.bad_password));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onAuthenticationFailed() {
|
||||
((TextView) promptsView.findViewById(R.id.txtFingerprintAuth))
|
||||
.setText(context.getString(R.string.bad_fingerprint));
|
||||
}
|
||||
};
|
||||
|
||||
openDialog.setOnShowListener(new DialogInterface.OnShowListener() {
|
||||
@Override
|
||||
public void onShow(DialogInterface dialog) {
|
||||
if (fingerprintAuthAllowed) {
|
||||
FingerprintHelper.authenticate(context, cancelSignal, fingerprintAuthCallback);
|
||||
}
|
||||
Button button = ((AlertDialog) dialog).getButton(AlertDialog.BUTTON_POSITIVE);
|
||||
button.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View view) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (processPasswordEntry(context, wallet, pass, false, action)) {
|
||||
Helper.hideKeyboardAlways((Activity) context);
|
||||
openDialog.dismiss();
|
||||
openDialog = null;
|
||||
} else {
|
||||
etPassword.setError(context.getString(R.string.bad_password));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// accept keyboard "ok"
|
||||
etPassword.getEditText().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)) {
|
||||
String pass = etPassword.getEditText().getText().toString();
|
||||
if (processPasswordEntry(context, wallet, pass, false, action)) {
|
||||
Helper.hideKeyboardAlways((Activity) context);
|
||||
openDialog.dismiss();
|
||||
openDialog = null;
|
||||
} else {
|
||||
etPassword.setError(context.getString(R.string.bad_password));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
||||
Helper.showKeyboard(openDialog);
|
||||
openDialog.show();
|
||||
}
|
||||
|
||||
public interface PasswordAction {
|
||||
void action(String walletName, String password, boolean fingerprintUsed);
|
||||
}
|
||||
|
||||
static private boolean processPasswordEntry(Context context, String walletName, String pass, boolean fingerprintUsed, PasswordAction action) {
|
||||
String walletPassword = Helper.getWalletPassword(context, walletName, pass);
|
||||
if (walletPassword != null) {
|
||||
action.action(walletName, walletPassword, fingerprintUsed);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
283
app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
Normal file
283
app/src/main/java/com/m2049r/xmrwallet/util/KeyStoreHelper.java
Normal file
File diff suppressed because it is too large
Load Diff
135
app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
Normal file
135
app/src/main/java/com/m2049r/xmrwallet/util/Notice.java
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2018 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.util;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.support.v4.app.FragmentActivity;
|
||||
import android.support.v4.app.FragmentManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ImageButton;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.m2049r.xmrwallet.R;
|
||||
import com.m2049r.xmrwallet.dialog.HelpFragment;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class Notice {
|
||||
private static final String PREFS_NAME = "notice";
|
||||
private static List<Notice> notices = null;
|
||||
|
||||
private static final String NOTICE_SHOW_XMRTO_ENABLED_LOGIN = "notice_xmrto_enabled_login";
|
||||
private static final String NOTICE_SHOW_XMRTO_ENABLED_SEND = "notice_xmrto_enabled_send";
|
||||
private static final String NOTICE_SHOW_CRAZYPASS = "notice_crazypass_enabled_login";
|
||||
|
||||
private static void init() {
|
||||
synchronized (Notice.class) {
|
||||
if (notices != null) return;
|
||||
notices = new ArrayList<>();
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_XMRTO_ENABLED_SEND,
|
||||
R.string.info_xmrto_enabled,
|
||||
R.string.help_xmrto,
|
||||
1)
|
||||
);
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_XMRTO_ENABLED_LOGIN,
|
||||
R.string.info_xmrto_enabled,
|
||||
R.string.help_xmrto,
|
||||
1)
|
||||
);
|
||||
notices.add(
|
||||
new Notice(NOTICE_SHOW_CRAZYPASS,
|
||||
R.string.info_crazypass_enabled,
|
||||
R.string.help_details,
|
||||
2)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
public static void showAll(ViewGroup parent, String selector) {
|
||||
if (notices == null) init();
|
||||
for (Notice notice : notices) {
|
||||
if (notice.id.matches(selector))
|
||||
notice.show(parent);
|
||||
}
|
||||
}
|
||||
|
||||
private final String id;
|
||||
private final int textResId;
|
||||
private final int helpResId;
|
||||
private final int defaultCount;
|
||||
private transient int count = -1;
|
||||
|
||||
private Notice(final String id, final int textResId, final int helpResId, final int defaultCount) {
|
||||
this.id = id;
|
||||
this.textResId = textResId;
|
||||
this.helpResId = helpResId;
|
||||
this.defaultCount = defaultCount;
|
||||
}
|
||||
|
||||
// show this notice as a child of the given parent view
|
||||
// NB: it assumes the parent is in a Fragment
|
||||
private void show(final ViewGroup parent) {
|
||||
final Context context = parent.getContext();
|
||||
if (getCount(context) <= 0) return; // don't add it
|
||||
|
||||
final LinearLayout ll =
|
||||
(LinearLayout) LayoutInflater.from(context)
|
||||
.inflate(R.layout.template_notice, parent, false);
|
||||
|
||||
((TextView) ll.findViewById(R.id.tvNotice)).setText(textResId);
|
||||
|
||||
final FragmentManager fragmentManager =
|
||||
((FragmentActivity) context).getSupportFragmentManager();
|
||||
ll.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
HelpFragment.display(fragmentManager, helpResId);
|
||||
}
|
||||
});
|
||||
|
||||
ImageButton ib = (ImageButton) ll.findViewById(R.id.ibClose);
|
||||
ib.setOnClickListener(new View.OnClickListener() {
|
||||
@Override
|
||||
public void onClick(View v) {
|
||||
ll.setVisibility(View.GONE);
|
||||
decCount(context);
|
||||
}
|
||||
});
|
||||
parent.addView(ll);
|
||||
}
|
||||
|
||||
private int getCount(final Context context) {
|
||||
count = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE)
|
||||
.getInt(id, defaultCount);
|
||||
return count;
|
||||
}
|
||||
|
||||
private void decCount(final Context context) {
|
||||
final SharedPreferences prefs = context.getSharedPreferences(PREFS_NAME, Context.MODE_PRIVATE);
|
||||
if (count < 0) // not initialized yet
|
||||
count = prefs.getInt(id, defaultCount);
|
||||
if (count > 0)
|
||||
prefs.edit().putInt(id, count - 1).apply();
|
||||
}
|
||||
}
|
@@ -87,6 +87,8 @@ public class RestoreHeight {
|
||||
blockheight.put("2017-12-01", 1454639L);
|
||||
blockheight.put("2018-01-01", 1477201L);
|
||||
blockheight.put("2018-02-01", 1499599L);
|
||||
blockheight.put("2018-03-01", 1519796L);
|
||||
blockheight.put("2018-04-01", 1542067L);
|
||||
}
|
||||
|
||||
public long getHeight(String date) {
|
||||
|
@@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/white" />
|
||||
<padding
|
||||
android:bottom="8dp"
|
||||
android:left="8dp"
|
||||
android:right="8dp"
|
||||
android:top="8dp" />
|
||||
</shape>
|
@@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:shape="rectangle">
|
||||
<padding
|
||||
android:bottom="8dp"
|
||||
android:left="8dp"
|
||||
android:right="8dp"
|
||||
android:top="8dp" />
|
||||
<corners android:radius="3dp" />
|
||||
<stroke
|
||||
android:width="2dp"
|
||||
android:color="@color/gradientOrange"
|
||||
android:dashGap="16dp"
|
||||
android:dashWidth="16dp" />
|
||||
</shape>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportWidth="24.0"
|
||||
android:viewportHeight="24.0">
|
||||
<path
|
||||
android:fillColor="#FF000000"
|
||||
android:pathData="M19,13h-6v6h-2v-6H5v-2h6V5h2v6h6v2z"/>
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="24dp"
|
||||
android:height="24dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="@color/gradientOrange"
|
||||
android:pathData="M8.59,16.34l4.58,-4.59 -4.58,-4.59L10,5.75l6,6 -6,6z" />
|
||||
</vector>
|
@@ -1,9 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="14dp"
|
||||
android:height="14dp"
|
||||
android:viewportHeight="24.0"
|
||||
android:viewportWidth="24.0">
|
||||
<path
|
||||
android:fillColor="#FFffffff"
|
||||
android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 2,12.28 2,8.5 2,5.42 4.42,3 7.5,3c1.74,0 3.41,0.81 4.5,2.09C13.09,3.81 14.76,3 16.5,3 19.58,3 22,5.42 22,8.5c0,3.78 -3.4,6.86 -8.55,11.54L12,21.35z" />
|
||||
</vector>
|
37
app/src/main/res/drawable/ic_fingerprint.xml
Normal file
37
app/src/main/res/drawable/ic_fingerprint.xml
Normal file
File diff suppressed because one or more lines are too long
@@ -1,9 +0,0 @@
|
||||
<vector android:height="24dp" android:viewportHeight="25.0"
|
||||
android:viewportWidth="25.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="#FF6105" android:fillType="evenOdd"
|
||||
android:pathData="M3,0L22,0A3,3 0,0 1,25 3L25,22A3,3 0,0 1,22 25L3,25A3,3 0,0 1,0 22L0,3A3,3 0,0 1,3 0z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
<path android:fillColor="#FFFFFF" android:fillType="evenOdd"
|
||||
android:pathData="M13.556,13.214L13.556,5L11.968,5L11.968,13.214L9.111,10L8,11.25L12.762,16.607L17.524,11.25L16.413,10L13.556,13.214ZM17.524,17.5L8,17.5L8,19.286L17.524,19.286L17.524,17.5L17.524,17.5Z"
|
||||
android:strokeColor="#00000000" android:strokeWidth="1"/>
|
||||
</vector>
|
@@ -1,14 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="32dp"
|
||||
android:height="32dp"
|
||||
android:viewportHeight="75.0"
|
||||
android:viewportWidth="75.0">
|
||||
|
||||
<path
|
||||
android:fillColor="#ff6600"
|
||||
android:pathData="M 37.3,0.35329395 c -20.377,0 -36.903,16.524 -36.903,36.902 0,4.074 0.66,7.992 1.88,11.657 l 11.036,0 0,-31.049 23.987,23.987 23.987,-23.987 0,31.049 11.037,0 c 1.22,-3.665 1.88,-7.583 1.88,-11.657 0,-20.378 -16.526,-36.902 -36.904,-36.902" />
|
||||
<path
|
||||
android:fillColor="#4c4c4c"
|
||||
android:pathData="M 21.3164,36.895994 l 0,19.537 -15.55,0 c 6.478,10.628 18.178,17.726 31.533,17.726 13.355,0 25.056,-7.098 31.533,-17.726 l -15.549,0 0,-19.537 -15.984,15.984 z" />
|
||||
|
||||
</vector>
|
@@ -1,21 +0,0 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="100dp"
|
||||
android:height="100dp"
|
||||
android:viewportHeight="75.0"
|
||||
android:viewportWidth="75.0">
|
||||
|
||||
<path
|
||||
android:fillColor="#ffffff"
|
||||
android:pathData=" M 37.3, 37.3
|
||||
m -36.9, 0
|
||||
a 36.9,36.9 0 1,0 73.8,0
|
||||
a 36.9,36.9 0 1,0 -73.8,0" />
|
||||
|
||||
<path
|
||||
android:fillColor="#ff6600"
|
||||
android:pathData="M 37.3,0.35329395 c -20.377,0 -36.903,16.524 -36.903,36.902 0,4.074 0.66,7.992 1.88,11.657 l 11.036,0 0,-31.049 23.987,23.987 23.987,-23.987 0,31.049 11.037,0 c 1.22,-3.665 1.88,-7.583 1.88,-11.657 0,-20.378 -16.526,-36.902 -36.904,-36.902" />
|
||||
<path
|
||||
android:fillColor="#4c4c4c"
|
||||
android:pathData="M 21.3164,36.895994 l 0,19.537 -15.55,0 c 6.478,10.628 18.178,17.726 31.533,17.726 13.355,0 25.056,-7.098 31.533,-17.726 l -15.549,0 0,-19.537 -15.984,15.984 z" />
|
||||
|
||||
</vector>
|
@@ -1,22 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:background="@drawable/backgound_all"
|
||||
android:gravity="center_horizontal"
|
||||
android:orientation="vertical">
|
||||
|
||||
<com.m2049r.xmrwallet.widget.Toolbar
|
||||
android:id="@+id/toolbar"
|
||||
style="@style/ToolBarStyle.Event"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="?attr/actionBarSize"
|
||||
android:background="@drawable/backgound_toolbar_mainnet"
|
||||
android:minHeight="?attr/actionBarSize" />
|
||||
|
||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/fragment_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
</LinearLayout>
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user