From ad6ac6058b786c629229ccdb05bef9dd9047fdc4 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Tue, 8 Oct 2024 22:14:47 +0100 Subject: [PATCH 01/35] =?UTF-8?q?=F0=9F=9A=80[Release=20v2.11.1=20071024]?= =?UTF-8?q?=20Merge=20into=20Main=20(#246)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * replaced the control in PeerManager.c Signed-off-by: kcw-grunt * reverted BRBitcoinAmount Signed-off-by: kcw-grunt * Cleaning up the files Signed-off-by: kcw-grunt * Removed Pusher remnants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt --- .../main/java/com/breadwallet/BreadApp.java | 39 - .../tools/security/KeyStoreManager.java | 677 ------------------ app/src/main/jni/transition/PeerManager.c | 5 + app/src/main/jni/transition/wallet.c | 3 +- .../main/res/layout/button_buy_bitcoin.xml | 32 - 5 files changed, 6 insertions(+), 750 deletions(-) delete mode 100644 app/src/main/java/com/breadwallet/tools/security/KeyStoreManager.java delete mode 100644 app/src/main/res/layout/button_buy_bitcoin.xml diff --git a/app/src/main/java/com/breadwallet/BreadApp.java b/app/src/main/java/com/breadwallet/BreadApp.java index fc83177f2..9f25fac96 100644 --- a/app/src/main/java/com/breadwallet/BreadApp.java +++ b/app/src/main/java/com/breadwallet/BreadApp.java @@ -110,45 +110,6 @@ protected void attachBaseContext(Context base) { public interface OnAppBackgrounded { void onBackgrounded(); } - private void loadAdvertisingAndPush(String instanceID, Context app) { - new Thread(new Runnable() { - @Override - public void run() { - try { - AdvertisingIdClient.Info adInfo = AdvertisingIdClient.getAdvertisingIdInfo(app); - finishedLoadingPushService( instanceID, adInfo); - } catch (IllegalStateException e) { - e.printStackTrace(); - } catch (GooglePlayServicesRepairableException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); - } catch (GooglePlayServicesNotAvailableException e) { - e.printStackTrace(); - } - String emptyID = ""; - finishedLoadingPushService(emptyID,null); - Bundle params = new Bundle(); - params.putString("pusher_instanceid_not_found",emptyID); - AnalyticsManager.logCustomEventWithParams(BRConstants._20200112_ERR, params); - } - }).start(); - } - private void finishedLoadingPushService(final String instanceID, AdvertisingIdClient.Info adInfo) { - if(adInfo!=null && instanceID != "") { - // setup Pusher Interests - String adInfoString = adInfo.getId(); - String generalAndroidInterest = "general-android"; - String debugGeneralAndroidInterest = "debug-general-android"; - - PushNotifications.start(getApplicationContext(), instanceID); - PushNotifications.addDeviceInterest(generalAndroidInterest); - PushNotifications.addDeviceInterest(debugGeneralAndroidInterest); - - //Send params for pusher setup - AnalyticsManager.logCustomEvent(BRConstants._20240123_RAGI); - } - } private static class CrashReportingTree extends Timber.Tree { private static final String CRASHLYTICS_KEY_PRIORITY = "priority"; private static final String CRASHLYTICS_KEY_TAG = "tag"; diff --git a/app/src/main/java/com/breadwallet/tools/security/KeyStoreManager.java b/app/src/main/java/com/breadwallet/tools/security/KeyStoreManager.java deleted file mode 100644 index 798f980a1..000000000 --- a/app/src/main/java/com/breadwallet/tools/security/KeyStoreManager.java +++ /dev/null @@ -1,677 +0,0 @@ -package com.breadwallet.tools.security; - -import android.app.Activity; -import android.app.KeyguardManager; -import android.content.Context; -import android.content.DialogInterface; -import android.content.Intent; -import android.security.keystore.KeyGenParameterSpec; -import android.security.keystore.KeyPermanentlyInvalidatedException; -import android.security.keystore.KeyProperties; -import android.security.keystore.UserNotAuthenticatedException; -import android.util.Log; - -import com.breadwallet.R; -import com.breadwallet.exceptions.BRKeystoreErrorException; -import com.breadwallet.presenter.activities.IntroActivity; -import com.breadwallet.presenter.activities.MainActivity; -import com.breadwallet.tools.animation.BRAnimator; -import com.breadwallet.tools.util.ByteReader; -import com.breadwallet.tools.manager.SharedPreferencesManager; -import com.breadwallet.tools.util.TypesConverter; -import com.breadwallet.wallet.BRWalletManager; - -import junit.framework.Assert; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.UnsupportedEncodingException; -import java.security.InvalidAlgorithmParameterException; -import java.security.InvalidKeyException; -import java.security.KeyStore; -import java.security.KeyStoreException; -import java.security.NoSuchAlgorithmException; -import java.security.NoSuchProviderException; -import java.security.UnrecoverableKeyException; -import java.security.cert.CertificateException; -import java.text.Normalizer; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.SynchronousQueue; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.CipherOutputStream; -import javax.crypto.KeyGenerator; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; - -/** - * BreadWallet - *

- * Created by Mihail Gutan on 9/29/15. - * Copyright (c) 2016 breadwallet LLC - *

- * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - *

- * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - *

- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ - -public class KeyStoreManager { - private static final String TAG = KeyStoreManager.class.getName(); - - public static final String CIPHER_ALGORITHM = "AES/CBC/PKCS7Padding"; - public static final String PADDING = KeyProperties.ENCRYPTION_PADDING_PKCS7; - public static final String BLOCK_MODE = KeyProperties.BLOCK_MODE_CBC; - public static final String ANDROID_KEY_STORE = "AndroidKeyStore"; - - public static Map aliasObjectMap; - - private static final String PHRASE_IV = "ivphrase"; - private static final String CANARY_IV = "ivcanary"; - private static final String PUB_KEY_IV = "ivpubkey"; - private static final String WALLET_CREATION_TIME_IV = "ivtime"; - private static final String PASS_CODE_IV = "ivpasscode"; - private static final String FAIL_COUNT_IV = "ivfailcount"; - private static final String SPENT_LIMIT_IV = "ivspendlimit"; - private static final String FAIL_TIMESTAMP_IV = "ivfailtimestamp"; - private static final String AUTH_KEY_IV = "ivauthkey"; - private static final String TOKEN_IV = "ivtoken"; - private static final String PASS_TIME_IV = "passtimetoken"; - - public static final String PHRASE_ALIAS = "phrase"; - public static final String CANARY_ALIAS = "canary"; - public static final String PUB_KEY_ALIAS = "pubKey"; - public static final String WALLET_CREATION_TIME_ALIAS = "creationTime"; - public static final String PASS_CODE_ALIAS = "passCode"; - public static final String FAIL_COUNT_ALIAS = "failCount"; - public static final String SPEND_LIMIT_ALIAS = "spendlimit"; - public static final String FAIL_TIMESTAMP_ALIAS = "failTimeStamp"; - public static final String AUTH_KEY_ALIAS = "authKey"; - public static final String TOKEN_ALIAS = "token"; - public static final String PASS_TIME_ALIAS = "passTime"; - - private static final String PHRASE_FILENAME = "my_phrase"; - private static final String CANARY_FILENAME = "my_canary"; - private static final String PUB_KEY_FILENAME = "my_pub_key"; - private static final String WALLET_CREATION_TIME_FILENAME = "my_creation_time"; - private static final String PASS_CODE_FILENAME = "my_pass_code"; - private static final String FAIL_COUNT_FILENAME = "my_fail_count"; - private static final String SPEND_LIMIT_FILENAME = "my_spend_limit"; - private static final String FAIL_TIMESTAMP_FILENAME = "my_fail_timestamp"; - private static final String AUTH_KEY_FILENAME = "my_auth_key"; - private static final String TOKEN_FILENAME = "my_token"; - private static final String PASS_TIME_FILENAME = "my_pass_time"; - - public static final int AUTH_DURATION_SEC = 300; - - static { - aliasObjectMap = new HashMap<>(); - aliasObjectMap.put(PHRASE_ALIAS, new AliasObject(PHRASE_ALIAS, PHRASE_FILENAME, PHRASE_IV)); - aliasObjectMap.put(CANARY_ALIAS, new AliasObject(CANARY_ALIAS, CANARY_FILENAME, CANARY_IV)); - aliasObjectMap.put(PUB_KEY_ALIAS, new AliasObject(PUB_KEY_ALIAS, PUB_KEY_FILENAME, PUB_KEY_IV)); - aliasObjectMap.put(WALLET_CREATION_TIME_ALIAS, new AliasObject(WALLET_CREATION_TIME_ALIAS, WALLET_CREATION_TIME_FILENAME, WALLET_CREATION_TIME_IV)); - aliasObjectMap.put(PASS_CODE_ALIAS, new AliasObject(PASS_CODE_ALIAS, PASS_CODE_FILENAME, PASS_CODE_IV)); - aliasObjectMap.put(FAIL_COUNT_ALIAS, new AliasObject(FAIL_COUNT_ALIAS, FAIL_COUNT_FILENAME, FAIL_COUNT_IV)); - aliasObjectMap.put(FAIL_COUNT_ALIAS, new AliasObject(FAIL_COUNT_ALIAS, FAIL_COUNT_FILENAME, FAIL_COUNT_IV)); - aliasObjectMap.put(SPEND_LIMIT_ALIAS, new AliasObject(SPEND_LIMIT_ALIAS, SPEND_LIMIT_FILENAME, SPENT_LIMIT_IV)); - aliasObjectMap.put(FAIL_TIMESTAMP_ALIAS, new AliasObject(FAIL_TIMESTAMP_ALIAS, FAIL_TIMESTAMP_FILENAME, FAIL_TIMESTAMP_IV)); - aliasObjectMap.put(AUTH_KEY_ALIAS, new AliasObject(AUTH_KEY_ALIAS, AUTH_KEY_FILENAME, AUTH_KEY_IV)); - aliasObjectMap.put(TOKEN_ALIAS, new AliasObject(TOKEN_ALIAS, TOKEN_FILENAME, TOKEN_IV)); - aliasObjectMap.put(PASS_TIME_ALIAS, new AliasObject(PASS_TIME_ALIAS, PASS_TIME_FILENAME, PASS_TIME_IV)); - - Assert.assertEquals(aliasObjectMap.size(), 11); - Assert.assertEquals(AUTH_DURATION_SEC, 300); - } - - private static android.app.AlertDialog dialog; - - private static boolean _setData(Activity context, byte[] data, String alias, String alias_file, String alias_iv, int request_code, boolean auth_required) { - if (alias.equals(alias_file) || alias.equals(alias_iv) || alias_file.equals(alias_iv)) - throw new IllegalArgumentException("mistake in parameters!"); - try { - KeyStore keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); - keyStore.load(null); - // Create the keys if necessary - if (!keyStore.containsAlias(alias)) { - KeyGenerator keyGenerator = KeyGenerator.getInstance( - KeyProperties.KEY_ALGORITHM_AES, ANDROID_KEY_STORE); - - // Set the alias of the entry in Android KeyStore where the key will appear - // and the constrains (purposes) in the constructor of the Builder - keyGenerator.init(new KeyGenParameterSpec.Builder(alias, - KeyProperties.PURPOSE_ENCRYPT | KeyProperties.PURPOSE_DECRYPT) - .setBlockModes(BLOCK_MODE) - .setKeySize(256) - .setUserAuthenticationRequired(auth_required) - .setUserAuthenticationValidityDurationSeconds(AUTH_DURATION_SEC) - .setRandomizedEncryptionRequired(false) - .setEncryptionPaddings(PADDING) - .build()); - SecretKey key = keyGenerator.generateKey(); - - } - - String encryptedDataFilePath = getEncryptedDataFilePath(alias_file, context); - - SecretKey secret = (SecretKey) keyStore.getKey(alias, null); - if (secret == null) return false; - Cipher inCipher = Cipher.getInstance(CIPHER_ALGORITHM); - inCipher.init(Cipher.ENCRYPT_MODE, secret); - byte[] iv = inCipher.getIV(); - String path = getEncryptedDataFilePath(alias_iv, context); - boolean success = writeBytesToFile(path, iv); - if (!success) throw new NullPointerException("FAILED TO WRITE BYTES TO FILE"); - CipherOutputStream cipherOutputStream = new CipherOutputStream( - new FileOutputStream(encryptedDataFilePath), inCipher); - cipherOutputStream.write(data); - try { - cipherOutputStream.close(); - } catch (Exception ex) { - ex.printStackTrace(); - } - return true; - } catch (UserNotAuthenticatedException e) { - Log.e(TAG, Log.getStackTraceString(e)); - showAuthenticationScreen(context, request_code); - } catch (CertificateException | NoSuchAlgorithmException | InvalidKeyException | NullPointerException - | NoSuchPaddingException | KeyStoreException | UnrecoverableKeyException | - InvalidAlgorithmParameterException | NoSuchProviderException | IOException e) { - e.printStackTrace(); - } - return false; - } - - private static byte[] _getData(final Activity context, String alias, String alias_file, String alias_iv, int request_code) - throws BRKeystoreErrorException { - - if (alias.equals(alias_file) || alias.equals(alias_iv) || alias_file.equals(alias_iv)) - throw new RuntimeException("mistake in parameters!"); - Log.e(TAG, "_getData: " + alias); - KeyStore keyStore; - - String encryptedDataFilePath = getEncryptedDataFilePath(alias_file, context); - byte[] result = new byte[0]; - try { - keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); - keyStore.load(null); - SecretKey secretKey = (SecretKey) - keyStore.getKey(alias, null); - if (secretKey == null) { - /** no such key, the key is just simply not there */ - boolean fileExists = new File(encryptedDataFilePath).exists(); - Log.e(TAG, "_getData: " + alias + " file exist: " + fileExists); - if (!fileExists) return result; /** file also not there, fine then */ - showKeyStoreFailedToLoad(context); - throw new BRKeystoreErrorException("no key but the phrase is there"); - } - - if (!new File(getEncryptedDataFilePath(alias_iv, context)).exists() || - !new File(getEncryptedDataFilePath(alias_file, context)).exists()) { - removeAliasAndFiles(alias, context); - return result; - } - - byte[] iv = readBytesFromFile(getEncryptedDataFilePath(alias_iv, context)); - Cipher outCipher; - outCipher = Cipher.getInstance(CIPHER_ALGORITHM); - outCipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); - - CipherInputStream cipherInputStream = new CipherInputStream( - new FileInputStream(encryptedDataFilePath), outCipher); - return ByteReader.readBytesFromStream(cipherInputStream); - } catch (InvalidKeyException e) { - Log.e(TAG, "_getData: InvalidKeyException"); - if (e instanceof UserNotAuthenticatedException) { - /**user not authenticated, ask the system for authentication*/ - Log.e(TAG, Log.getStackTraceString(e)); - showAuthenticationScreen(context, request_code); - throw new BRKeystoreErrorException(e.getMessage()); - } else if (e instanceof KeyPermanentlyInvalidatedException) { - showKeyStoreDialog("KeyStore Error", "Your Breadwallet encrypted data was recently invalidated because you " + - "disabled your Android lock screen. Please input your phrase to recover your Breadwallet now.", context.getString(R.string.ok), null, - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - dialog.cancel(); - } - }, null, new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialogInterface) { - if (context instanceof IntroActivity) { - if (BRAnimator.checkTheMultipressingAvailability()) { - ((IntroActivity) context).showRecoverWalletFragment(); - } - } - } - }); - throw new BRKeystoreErrorException("KeyPermanentlyInvalidatedException"); - } else { - Log.e(TAG, "_getData: InvalidKeyException", e); - showKeyStoreFailedToLoad(context); - throw new BRKeystoreErrorException("Key store error"); - } - } catch (IOException | CertificateException | KeyStoreException e) { - /** keyStore.load(null) threw the Exception, meaning the keystore is unavailable */ - Log.e(TAG, "_getData: keyStore.load(null) threw the Exception, meaning the keystore is unavailable", e); - if (e instanceof FileNotFoundException) { - Log.e(TAG, "_getData: File not found exception", e); - throw new RuntimeException("the key is present but the phrase on the disk no???"); - } else { - showKeyStoreFailedToLoad(context); - throw new BRKeystoreErrorException("Failed to load KeyStore"); - } - - } catch (UnrecoverableKeyException | InvalidAlgorithmParameterException | NoSuchAlgorithmException | NoSuchPaddingException e) { - /** if for any other reason the keystore fails, crash! */ - Log.e(TAG, "getData: error: " + e.getClass().getSuperclass().getName()); - throw new RuntimeException(e.getMessage()); - } - } - - private static String getEncryptedDataFilePath(String fileName, Context context) { - String filesDirectory = context.getFilesDir().getAbsolutePath(); - return filesDirectory + File.separator + fileName; - } - - private static void showKeyStoreFailedToLoad(final Activity context) { - showKeyStoreDialog("KeyStore Error", "Failed to load KeyStore. Please try again later or enter your phrase to recover your Breadwallet now.", "recover now", "try later", - context instanceof IntroActivity ? - new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - if (BRAnimator.checkTheMultipressingAvailability()) { - ((IntroActivity) context).showRecoverWalletFragment(); - } - } - } : null, new DialogInterface.OnClickListener() { - public void onClick(DialogInterface dialog, int which) { - context.finish(); - } - }, - null); - } - - public static boolean putKeyStorePhrase(byte[] strToStore, Activity context, int requestCode) { - AliasObject obj = aliasObjectMap.get(PHRASE_ALIAS); - return !(strToStore == null || strToStore.length == 0) && _setData(context, strToStore, obj.alias, obj.datafileName, obj.ivFileName, requestCode, true); - } - - public static byte[] getKeyStorePhrase(final Activity context, int requestCode) - throws BRKeystoreErrorException { - AliasObject obj = aliasObjectMap.get(PHRASE_ALIAS); - return _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); - } - - public static boolean putKeyStoreCanary(String strToStore, Activity context, int requestCode) { - if (strToStore == null || strToStore.isEmpty()) return false; - AliasObject obj = aliasObjectMap.get(CANARY_ALIAS); - byte[] strBytes = new byte[0]; - try { - strBytes = strToStore.getBytes("UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return strBytes.length != 0 && _setData(context, strBytes, obj.alias, obj.datafileName, obj.ivFileName, requestCode, true); - } - - public static String getKeyStoreCanary(final Activity context, int requestCode) - throws BRKeystoreErrorException { - AliasObject obj = aliasObjectMap.get(CANARY_ALIAS); - byte[] data = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, requestCode); - String result = null; - try { - result = new String(data, "UTF-8"); - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - } - return result; - } - - public static boolean putMasterPublicKey(byte[] masterPubKey, Activity context) { - AliasObject obj = aliasObjectMap.get(PUB_KEY_ALIAS); - return masterPubKey != null && masterPubKey.length != 0 && _setData(context, masterPubKey, obj.alias, obj.datafileName, obj.ivFileName, 0, false); - } - - public static byte[] getMasterPublicKey(final Activity context) { - byte[] result = new byte[0]; - AliasObject obj = aliasObjectMap.get(PUB_KEY_ALIAS); - try { - result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); - } catch (BRKeystoreErrorException e) { - e.printStackTrace(); - } - return result; - } - - public static boolean putAuthKey(byte[] authKey, Activity context) { - AliasObject obj = aliasObjectMap.get(AUTH_KEY_ALIAS); - return authKey != null && authKey.length != 0 && _setData(context, authKey, obj.alias, obj.datafileName, obj.ivFileName, 0, false); - } - - public static byte[] getAuthKey(final Activity context) { - AliasObject obj = aliasObjectMap.get(AUTH_KEY_ALIAS); - byte[] result = new byte[0]; - try { - result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); - } catch (BRKeystoreErrorException e) { - e.printStackTrace(); - } - return result; - } - - public static boolean putToken(byte[] token, Activity context) { - AliasObject obj = aliasObjectMap.get(TOKEN_ALIAS); - return token != null && token.length != 0 && _setData(context, token, obj.alias, obj.datafileName, obj.ivFileName, 0, false); - } - - public static byte[] getToken(final Activity context) { - AliasObject obj = aliasObjectMap.get(TOKEN_ALIAS); - byte[] result = new byte[0]; - try { - result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); - } catch (BRKeystoreErrorException e) { - e.printStackTrace(); - } - return result; - } - - public static boolean putWalletCreationTime(int creationTime, Activity context) { - Log.e(TAG, "putWalletCreationTime: " + creationTime); - AliasObject obj = aliasObjectMap.get(WALLET_CREATION_TIME_ALIAS); - byte[] bytesToStore = TypesConverter.intToBytes(creationTime); - return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); - } - - public static int getWalletCreationTime(final Activity context) { - AliasObject obj = aliasObjectMap.get(WALLET_CREATION_TIME_ALIAS); - byte[] result = new byte[0]; - try { - result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); - } catch (BRKeystoreErrorException e) { - e.printStackTrace(); - } - return result.length > 0 ? TypesConverter.bytesToInt(result) : 0; - } - - public static boolean putPassCode(String passcode, Activity context) { - AliasObject obj = aliasObjectMap.get(PASS_CODE_ALIAS); - byte[] bytesToStore = passcode.getBytes(); - return _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); - } - - public static String getPassCode(final Activity context) { - AliasObject obj = aliasObjectMap.get(PASS_CODE_ALIAS); - byte[] result = new byte[0]; - try { - result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); - } catch (BRKeystoreErrorException e) { - e.printStackTrace(); - } - String passCode = new String(result); - try { - int test = Integer.parseInt(passCode); - } catch (Exception e) { - Log.e(TAG, "getPassCode: " + e.getMessage()); - passCode = ""; - putPassCode(passCode, context); - KeyStoreManager.putFailCount(0, context); - KeyStoreManager.putFailTimeStamp(0, context); - return passCode; - } - if (passCode.length() != 4) { - passCode = ""; - putPassCode(passCode, context); - } - return passCode; - } - - public static boolean putFailCount(int failCount, Activity context) { - AliasObject obj = aliasObjectMap.get(FAIL_COUNT_ALIAS); - if (failCount >= 3) { - long time = SharedPreferencesManager.getSecureTime(context); - putFailTimeStamp(time, context); - } - byte[] bytesToStore = TypesConverter.intToBytes(failCount); - return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); - } - - public static int getFailCount(final Activity context) { - AliasObject obj = aliasObjectMap.get(FAIL_COUNT_ALIAS); - byte[] result = new byte[0]; - try { - result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); - } catch (BRKeystoreErrorException e) { - e.printStackTrace(); - } - - return result.length > 0 ? TypesConverter.bytesToInt(result) : 0; - } - - public static boolean putSpendLimit(long spendLimit, Activity context) { - AliasObject obj = aliasObjectMap.get(SPEND_LIMIT_ALIAS); - byte[] bytesToStore = TypesConverter.long2byteArray(spendLimit); - return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); - } - - public static long getSpendLimit(final Activity context) { - AliasObject obj = aliasObjectMap.get(SPEND_LIMIT_ALIAS); - byte[] result = new byte[0]; - try { - result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); - } catch (BRKeystoreErrorException e) { - e.printStackTrace(); - } - - return result.length > 0 ? TypesConverter.byteArray2long(result) : 0; - } - - public static boolean putFailTimeStamp(long spendLimit, Activity context) { - AliasObject obj = aliasObjectMap.get(FAIL_TIMESTAMP_ALIAS); - byte[] bytesToStore = TypesConverter.long2byteArray(spendLimit); - return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); - } - - public static long getFailTimeStamp(final Activity context) { - AliasObject obj = aliasObjectMap.get(FAIL_TIMESTAMP_ALIAS); - byte[] result = new byte[0]; - try { - result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); - } catch (BRKeystoreErrorException e) { - e.printStackTrace(); - } - - return result.length > 0 ? TypesConverter.byteArray2long(result) : 0; - } - - public static boolean putLastPasscodeUsedTime(long time, Activity context) { - AliasObject obj = aliasObjectMap.get(PASS_TIME_ALIAS); - byte[] bytesToStore = TypesConverter.long2byteArray(time); - return bytesToStore.length != 0 && _setData(context, bytesToStore, obj.alias, obj.datafileName, obj.ivFileName, 0, false); - } - - public static long getLastPasscodeUsedTime(final Activity context) { - AliasObject obj = aliasObjectMap.get(PASS_TIME_ALIAS); - byte[] result = new byte[0]; - try { - result = _getData(context, obj.alias, obj.datafileName, obj.ivFileName, 0); - } catch (BRKeystoreErrorException e) { - e.printStackTrace(); - } - return result.length > 0 ? TypesConverter.byteArray2long(result) : 0; - } - - public static boolean phraseIsValid(String insertedPhrase, Activity activity) { - String normalizedPhrase = Normalizer.normalize(insertedPhrase.trim(), Normalizer.Form.NFKD); - if (!BRWalletManager.getInstance(activity).validatePhrase(activity, normalizedPhrase)) - return false; - BRWalletManager m = BRWalletManager.getInstance(activity); - byte[] rawPhrase = normalizedPhrase.getBytes(); - byte[] bytePhrase = TypesConverter.getNullTerminatedPhrase(rawPhrase); - byte[] pubKey = m.getMasterPubKey(bytePhrase); - byte[] pubKeyFromKeyStore = KeyStoreManager.getMasterPublicKey(activity); - Arrays.fill(bytePhrase, (byte) 0); - return Arrays.equals(pubKey, pubKeyFromKeyStore); - } - - public static boolean resetWalletKeyStore(Context context) { - KeyStore keyStore; - try { - keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); - keyStore.load(null); - int count = 0; - for (String a : aliasObjectMap.keySet()) { - removeAliasAndFiles(a, context); - count++; - } - Assert.assertEquals(count, 11); - - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - return false; - } catch (KeyStoreException e) { - e.printStackTrace(); - return false; - } catch (IOException e) { - e.printStackTrace(); - return false; - } catch (java.security.cert.CertificateException e) { - e.printStackTrace(); - } - return true; - } - - public static void removeAliasAndFiles(String alias, Context context) { - KeyStore keyStore; - try { - boolean b1 = new File(getEncryptedDataFilePath(aliasObjectMap.get(alias).datafileName, context)).delete(); - boolean b2 = new File(getEncryptedDataFilePath(aliasObjectMap.get(alias).ivFileName, context)).delete(); - keyStore = KeyStore.getInstance(ANDROID_KEY_STORE); - keyStore.load(null); - keyStore.deleteEntry(alias); - } catch (KeyStoreException | CertificateException | NoSuchAlgorithmException | IOException e) { - e.printStackTrace(); - } - - - } - - public static void showAuthenticationScreen(Activity context, int requestCode) { - // Create the Confirm Credentials screen. You can customize the title and description. Or - // we will provide a generic one for you if you leave it null - Log.e(TAG, "showAuthenticationScreen: " + requestCode); - KeyguardManager mKeyguardManager = (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE); - Intent intent = mKeyguardManager.createConfirmDeviceCredentialIntent(context.getString(R.string.auth_required), context.getString(R.string.auth_message)); - if (intent != null) { - context.startActivityForResult(intent, requestCode); - } else { - throw new NullPointerException("no passcode is set"); - } - } - - public static byte[] readBytesFromFile(String path) { - byte[] bytes = null; - try { - File file = new File(path); - FileInputStream fin = new FileInputStream(file); - bytes = ByteReader.readBytesFromStream(fin); - } catch (IOException e) { - e.printStackTrace(); - } - return bytes; - } - - public static boolean writeBytesToFile(String path, byte[] data) { - - FileOutputStream fos = null; - - try { - File file = new File(path); - fos = new FileOutputStream(file); - - // Writes bytes from the specified byte array to this file output stream - fos.write(data); - return true; - } catch (FileNotFoundException e) { - System.out.println("File not found" + e); - } catch (IOException ioe) { - System.out.println("Exception while writing file " + ioe); - } finally { - // close the streams using close method - try { - if (fos != null) { - fos.close(); - } - } catch (IOException ioe) { - System.out.println("Error while closing stream: " + ioe); - } - - } - return false; - } - - private static void showKeyStoreDialog(final String title, final String message, final String posButton, final String negButton, - final DialogInterface.OnClickListener posButtonListener, - final DialogInterface.OnClickListener negButtonListener, - final DialogInterface.OnDismissListener dismissListener) { - Log.e(TAG, "showKeyStoreDialog"); - Activity app = MainActivity.app; - if (app == null) app = IntroActivity.app; - if (app == null) { - Log.e(TAG, "showCustomDialog: FAILED, context is null"); - return; - } - final Activity finalApp = app; - finalApp.runOnUiThread(new Runnable() { - @Override - public void run() { - if (dialog != null && dialog.isShowing()) { - System.out.println("some"); - if (dialog.getOwnerActivity() != null && !dialog.getOwnerActivity().isDestroyed()) - dialog.dismiss(); - else - return; - } - dialog = new android.app.AlertDialog.Builder(finalApp). - setTitle(title) - .setMessage(message) - .setPositiveButton(posButton, posButtonListener) - .setNegativeButton(negButton, negButtonListener) - .setOnDismissListener(dismissListener) - .setIcon(android.R.drawable.ic_dialog_alert) - .show(); - } - }); - } - - private static class AliasObject { - String alias; - String datafileName; - String ivFileName; - - AliasObject(String alias, String datafileName, String ivFileName) { - this.alias = alias; - this.datafileName = datafileName; - this.ivFileName = ivFileName; - } - - } - -} \ No newline at end of file diff --git a/app/src/main/jni/transition/PeerManager.c b/app/src/main/jni/transition/PeerManager.c index f512cd9e0..79117cef8 100644 --- a/app/src/main/jni/transition/PeerManager.c +++ b/app/src/main/jni/transition/PeerManager.c @@ -277,6 +277,11 @@ Java_com_breadwallet_wallet_BRPeerManager_create(JNIEnv *env, jobject thiz, __android_log_print(ANDROID_LOG_DEBUG, "Message from C: ", "earliestKeyTime: %d", earliestKeyTime); _peerManager = BRPeerManagerNew(&BR_CHAIN_PARAMS, _wallet, (uint32_t) earliestKeyTime, _blocks, + (size_t) blocksCount, + _peers, (size_t) peersCount, (double) fpRate); + BRPeerManagerSetCallbacks(_peerManager, NULL, syncStarted, syncStopped, + txStatusUpdate, + saveBlocks, savePeers, networkIsReachable, threadCleanup); } if (_peerManager == NULL) { diff --git a/app/src/main/jni/transition/wallet.c b/app/src/main/jni/transition/wallet.c index 4b9a1142d..b45b67151 100644 --- a/app/src/main/jni/transition/wallet.c +++ b/app/src/main/jni/transition/wallet.c @@ -600,13 +600,12 @@ Java_com_breadwallet_wallet_BRWalletManager_transactionIsVerified(JNIEnv *env, j return (jboolean) (result ? JNI_TRUE : JNI_FALSE); } - JNIEXPORT jlong JNICALL Java_com_breadwallet_wallet_BRWalletManager_bitcoinAmount(JNIEnv *env, jobject thiz, jlong localAmount, double price) { __android_log_print(ANDROID_LOG_DEBUG, "Message from C: ", "bitcoinAmount: localAmount: %lli, price: %lf", localAmount, price); - return (jlong) BRLitecoinAmount(localAmount, price); + return (jlong) BRBitcoinAmount(localAmount, price); } JNIEXPORT jlong diff --git a/app/src/main/res/layout/button_buy_bitcoin.xml b/app/src/main/res/layout/button_buy_bitcoin.xml deleted file mode 100644 index 8f2f02f3d..000000000 --- a/app/src/main/res/layout/button_buy_bitcoin.xml +++ /dev/null @@ -1,32 +0,0 @@ - - - - - - - - \ No newline at end of file From dcb363e3cc1098c6009dcadd11507d08016b4b08 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Thu, 21 Nov 2024 15:34:28 +0000 Subject: [PATCH 02/35] =?UTF-8?q?=F0=9F=9A=80[Release=20v2.12.2=2020241118?= =?UTF-8?q?]=20Merge=20into=20Main=20(#273)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚀[Release v2.11.1 071024] Merge into Develop (#245) * replaced the control in PeerManager.c Signed-off-by: kcw-grunt * reverted BRBitcoinAmount Signed-off-by: kcw-grunt * Cleaning up the files Signed-off-by: kcw-grunt * Removed Pusher remnants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Added Fiat feature, and FCM for push notifications (#247) * Added fiat feature based on language user chose * added fcm for push notifications * Update PushNotificationService.kt * Added new Icon, removed duplicate fcm library, added in app messaging * Added new Icon, removed duplicate fcm library, added in app messaging * Tech debt/add af sdk (#248) * Recent newline Signed-off-by: kcw-grunt * AF added Refactored cruft - AF working Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/refactor brevents syncmarkers (#254) * Tech debt/add af sdk (#248) - AF working - Changed requiredActivity - Added analytics error report - test this.Activity is null or not - Bugfix - Phrase Reminder crash - added an exception handler for UserNotAuthenticatedException. - note: this should allow for the system to display the native authorization UI when needed - fixes issue - https://console.firebase.google.com/u/0/project/litewallet-beta/crashlytics/app/android:com.loafwallet/issues/09dac17241309f0e823ef597a9a82cd4 - Added dev note - remove calls to BREventManager - removed BREventManager - renamed error message - fixed Firebase Analytics event error Signed-off-by: kcw-grunt * version bump update gitignore Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/ fixed send issue add syncing measurements (#255) * removed unused BRSharedPreferences Signed-off-by: kcw-grunt * started commenting out more Timber Signed-off-by: kcw-grunt * Added more shared preferences for syncing Signed-off-by: kcw-grunt * Commented out many verbose Timber logs Signed-off-by: kcw-grunt * WIP Still trying to figure out the eplased time and last and start timestamps…??? I dunno Signed-off-by: kcw-grunt * Moved sync measurment into the method calls Signed-off-by: kcw-grunt * Debugged the install issue with send - Moved all the measurements in new methods - recalculated measure points - Added analytics - event did_complete_sync was shown! - adding a dummy file to make sure file system is set ||||WIP still verifying the JSON is present - added a blockheight label - changed the sleep time (a hack to allow the device time to refresh the view) to 100 ms from 500ms!! -WIP Why is the bundle not showing on time Signed-off-by: kcw-grunt * replaces afID to first location Signed-off-by: kcw-grunt * Removed redundant sync measurements Signed-off-by: kcw-grunt * code bump Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/test refactor cruft removal (#250) * Removed non english seed words -Refactor tests - Reset AnalyticsTests Signed-off-by: kcw-grunt * Rename Bitcoinletter to Litecoinletters -added tests testLitecoinSymbolConstants testAppExternalURLConstants testFirebaseAnalyticsConstants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * fix: [#152] make sure using Fragment, FragmentManager and FragmentTransaction from androidx (#260) * fix: [#126] the issue came from FragmentBalanceSeedReminder.fetchSeedPhrase, so we just wrap with runCatching for now to avoid crash (#263) * version bump Signed-off-by: kcw-grunt * Removed donation button removed xml removed fragments removed references of the donation removed analytical events Signed-off-by: kcw-grunt * fix: prevent activity close * code bump Signed-off-by: kcw-grunt * enabled for Debug Signed-off-by: kcw-grunt * fix: [#264] add null checking for BRKeyStore.removeAliasAndFiles (#270) * fix: [#265] add null checking, migrate viewpage… (#271) * fix: [WIP][#265] work in progress add null checking, migrate viewpager to viewpager2 and more * fix: [#265] finalize migration some deprecated class and implement null checking at FragmentTransactionItem * fix: dix dismiss outside FragmentTransactionDetails * code bump Signed-off-by: kcw-grunt * version bump Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt Co-authored-by: Josi Kie <54074780+josikie@users.noreply.github.com> Co-authored-by: Andhika Yuana --- .gitignore | 1 + .idea/androidTestResultsUserPreferences.xml | 65 - app/build.gradle | 12 +- app/evil.json | 4 - .../breadwallet/analytics/AnalyticsTests.java | 72 - .../PaperKeyTests.java | 45 +- .../litewallet/analytics/ConstantsTests.java | 79 + .../database/DatabaseTests.java | 2 +- .../platform/KVStoreTests.java | 2 +- .../platform/PlatformTests.java | 5 +- .../activities/tests/MainActivityTest.java | 0 .../tests/CurrencyFormatterFixedTests.java | 0 .../tests/CurrencyFormatterTests.java | 0 .../security/KeyStoreTests.java | 2 +- .../security/NewKeyStoreTests.java | 2 +- .../wallet/WalletTests.java | 4 +- app/src/canary-file.json | 3 + app/src/main/AndroidManifest.xml | 12 + .../main/java/com/breadwallet/BreadApp.java | 67 +- .../entities/IntroLanguageResource.kt | 7 +- .../presenter/activities/BreadActivity.java | 52 +- .../activities/intro/IntroActivity.java | 35 + .../presenter/entities/PartnerNames.kt | 6 +- .../presenter/entities/TxItem.java | 5 +- .../fragments/DynamicDonationFragment.java | 231 -- .../fragments/FragmentBalanceSeedReminder.kt | 22 +- .../fragments/FragmentFingerprint.java | 7 +- .../presenter/fragments/FragmentPin.java | 4 +- .../presenter/fragments/FragmentSend.kt | 16 +- .../fragments/FragmentTransactionDetails.java | 44 +- .../fragments/FragmentTransactionItem.java | 70 +- .../presenter/history/HistoryFragment.kt | 42 +- .../tools/adapter/TransactionListAdapter.java | 13 +- .../adapter/TransactionPagerAdapter.java | 30 +- .../tools/animation/BRAnimator.java | 30 +- .../tools/manager/BRApiManager.java | 3 +- .../tools/manager/BREventManager.java | 116 - .../tools/manager/BRSharedPrefs.java | 118 +- .../tools/manager/InternetManager.java | 2 - .../tools/manager/SyncManager.java | 125 +- .../breadwallet/tools/manager/TxManager.java | 9 +- .../tools/security/AuthManager.java | 10 +- .../tools/security/BRKeyStore.java | 19 +- .../breadwallet/tools/security/BRSender.java | 8 +- .../tools/security/BitcoinUrlHandler.java | 13 - .../tools/threads/PaymentProtocolTask.java | 2 +- .../breadwallet/tools/util/BRConstants.java | 18 +- .../breadwallet/tools/util/BRCurrency.java | 8 +- .../breadwallet/tools/util/BRExchange.java | 8 +- .../tools/util/PushNotificationService.kt | 34 + .../com/breadwallet/tools/util/Utils.java | 25 +- .../com/breadwallet/wallet/BRPeerManager.java | 42 +- .../breadwallet/wallet/BRWalletManager.java | 14 +- app/src/main/java/com/platform/APIClient.java | 17 +- .../com/platform/tools/KVStoreManager.java | 7 +- .../res/drawable/litewallet_fin_icon_200.jpg | Bin 0 -> 12328 bytes ...itewallet_logotype_white_notifications.png | Bin 0 -> 4413 bytes .../res/layout/fragment_dynamic_donation.xml | 266 --- app/src/main/res/layout/fragment_send.xml | 26 - .../layout/fragment_transaction_details.xml | 6 +- app/src/test/resources/es-BIP39Words.txt | 2048 ----------------- app/src/test/resources/fr-BIP39Words.txt | 2048 ----------------- app/src/test/resources/ja-BIP39Words.txt | 2048 ----------------- app/src/test/resources/zh-BIP39Words.txt | 2048 ----------------- .../src/main/assets/canary-file.json | 3 + 65 files changed, 653 insertions(+), 9429 deletions(-) delete mode 100644 .idea/androidTestResultsUserPreferences.xml delete mode 100644 app/evil.json delete mode 100644 app/src/androidTest/java/com/breadwallet/analytics/AnalyticsTests.java rename app/src/androidTest/java/com/{breadwallet => litewallet}/PaperKeyTests.java (55%) create mode 100644 app/src/androidTest/java/com/litewallet/analytics/ConstantsTests.java rename app/src/androidTest/java/com/{breadwallet => litewallet}/database/DatabaseTests.java (99%) rename app/src/androidTest/java/com/{breadwallet => litewallet}/platform/KVStoreTests.java (99%) rename app/src/androidTest/java/com/{breadwallet => litewallet}/platform/PlatformTests.java (96%) rename app/src/androidTest/java/com/{breadwallet => litewallet}/presenter/activities/tests/MainActivityTest.java (100%) rename app/src/androidTest/java/com/{breadwallet => litewallet}/presenter/currencyformatting/tests/CurrencyFormatterFixedTests.java (100%) rename app/src/androidTest/java/com/{breadwallet => litewallet}/presenter/currencyformatting/tests/CurrencyFormatterTests.java (100%) rename app/src/androidTest/java/com/{breadwallet => litewallet}/security/KeyStoreTests.java (99%) rename app/src/androidTest/java/com/{breadwallet => litewallet}/security/NewKeyStoreTests.java (99%) rename app/src/androidTest/java/com/{breadwallet => litewallet}/wallet/WalletTests.java (98%) create mode 100644 app/src/canary-file.json delete mode 100644 app/src/main/java/com/breadwallet/presenter/fragments/DynamicDonationFragment.java delete mode 100644 app/src/main/java/com/breadwallet/tools/manager/BREventManager.java create mode 100644 app/src/main/java/com/breadwallet/tools/util/PushNotificationService.kt create mode 100644 app/src/main/res/drawable/litewallet_fin_icon_200.jpg create mode 100644 app/src/main/res/drawable/litewallet_logotype_white_notifications.png delete mode 100644 app/src/main/res/layout/fragment_dynamic_donation.xml delete mode 100644 app/src/test/resources/es-BIP39Words.txt delete mode 100644 app/src/test/resources/fr-BIP39Words.txt delete mode 100644 app/src/test/resources/ja-BIP39Words.txt delete mode 100644 app/src/test/resources/zh-BIP39Words.txt create mode 100644 install_time_asset_pack/src/main/assets/canary-file.json diff --git a/.gitignore b/.gitignore index 8830a3e22..4430b926c 100644 --- a/.gitignore +++ b/.gitignore @@ -101,3 +101,4 @@ app/src/litewalletDebug/google-services.json /app/release/google-services.json /app/debug/google-services.json /.idea/dictionaries/grunt.xml +androidTestResultsUserPreferences.xml diff --git a/.idea/androidTestResultsUserPreferences.xml b/.idea/androidTestResultsUserPreferences.xml deleted file mode 100644 index 10e9cbdaa..000000000 --- a/.idea/androidTestResultsUserPreferences.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle index a0c0ecbcd..45dc85227 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -3,6 +3,7 @@ apply plugin: 'kotlin-android' apply plugin: 'kotlin-kapt' apply plugin: 'com.google.firebase.crashlytics' apply plugin: 'kotlin-parcelize' +apply plugin: 'com.google.gms.google-services' configurations { ktlint @@ -62,8 +63,8 @@ android { applicationId = 'com.loafwallet' minSdkVersion 31 targetSdkVersion 34 - versionCode 20241007 - versionName "v2.11.1" + versionCode 20241118 + versionName "v2.12.2" multiDexEnabled true archivesBaseName = "${versionName}(${versionCode})" @@ -239,6 +240,10 @@ android { minifyEnabled = false debuggable = true } + + firebaseCrashlytics { + nativeSymbolUploadEnabled true + } } } @@ -313,7 +318,8 @@ dependencies { // push notifications implementation 'com.google.firebase:firebase-messaging:23.4.0' - implementation 'com.pusher:push-notifications-android:1.9.0' + implementation("com.google.firebase:firebase-inappmessaging-display") + // Timber implementation 'com.jakewharton.timber:timber:4.7.1' diff --git a/app/evil.json b/app/evil.json deleted file mode 100644 index 3b34e00a3..000000000 --- a/app/evil.json +++ /dev/null @@ -1,4 +0,0 @@ -{"this_is_evil": "TRUE", - "series" : [1,2,3], - "configuration_version": "1" -} diff --git a/app/src/androidTest/java/com/breadwallet/analytics/AnalyticsTests.java b/app/src/androidTest/java/com/breadwallet/analytics/AnalyticsTests.java deleted file mode 100644 index b0c11c103..000000000 --- a/app/src/androidTest/java/com/breadwallet/analytics/AnalyticsTests.java +++ /dev/null @@ -1,72 +0,0 @@ -package com.breadwallet.analytics; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import android.util.Log; -import androidx.test.ext.junit.rules.ActivityScenarioRule; - -import com.breadwallet.presenter.activities.intro.IntroActivity; -import com.breadwallet.tools.manager.AnalyticsManager; -import com.breadwallet.tools.util.BRConstants; - -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -@RunWith(AndroidJUnit4.class) -public class AnalyticsTests { - public static final String TAG = AnalyticsTests.class.getName(); - @Rule - public ActivityScenarioRule mActivityRule = new ActivityScenarioRule<>(IntroActivity.class); - @Before - public void setUp() { - Log.e(TAG, "setUp: "); - } - - @After - public void tearDown() { - } - - /// This needs to be debugged. Some logs: - /// Error: WARNING: The option setting is experimental and unsupported - /// Manifest merger failed with multiple errors, see logs - /// Execution failed for task ':app:processLoafDebugAndroidTestManifest'. - - @Test - public void testFirebaseAnalyticsConstants() { - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20191105_AL)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20191105_VSC)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20202116_VRC)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20191105_DSL)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20191105_DTBT)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200111_RNI)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200111_FNI)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200111_TNI)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200111_WNI)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200111_PNI)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200111_UTST)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200112_ERR)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200112_DSR)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200125_DSRR)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20201118_DTGS)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200217_DUWP)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200217_DUWB)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200223_DD)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200225_DCD)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200301_DUDFPK)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20201121_SIL)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20201121_DRIA)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20201121_FRIA)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20200207_DTHB)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20210427_HCIEEH)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20220822_UTOU)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20230131_NENR)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20230407_DCS)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20240123_RAGI)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20231225_UAP)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20240101_US)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20241006_DRR)); - Assert.assertNotNull(AnalyticsManager.logCustomEvent(BRConstants._20241006_UCR)); - } -} diff --git a/app/src/androidTest/java/com/breadwallet/PaperKeyTests.java b/app/src/androidTest/java/com/litewallet/PaperKeyTests.java similarity index 55% rename from app/src/androidTest/java/com/breadwallet/PaperKeyTests.java rename to app/src/androidTest/java/com/litewallet/PaperKeyTests.java index 18af06792..184779db9 100644 --- a/app/src/androidTest/java/com/breadwallet/PaperKeyTests.java +++ b/app/src/androidTest/java/com/litewallet/PaperKeyTests.java @@ -1,4 +1,4 @@ -package com.breadwallet; +package com.litewallet; import android.util.Log; @@ -6,7 +6,9 @@ import com.breadwallet.wallet.BRWalletManager; import org.apache.commons.io.IOUtils; +import org.junit.Assert; import org.junit.Test; +import org.junit.runner.RunWith; import java.io.IOException; import java.io.InputStream; @@ -18,43 +20,32 @@ import static org.hamcrest.Matchers.notNullValue; import static org.junit.Assert.assertThat; -public class PaperKeyTests { - - private static final String TAG = PaperKeyTests.class.getName(); - public static final String PAPER_KEY_JAP = "こせき ぎじにってい けっこん せつぞく うんどう ふこう にっすう こせい きさま なまみ たきび はかい";//japanese - public static final String PAPER_KEY_ENG = "stick sword keen afraid smile sting huge relax nominee arena area gift ";//english - public static final String PAPER_KEY_FRE = "vocation triage capsule marchand onduler tibia illicite entier fureur minorer amateur lubie";//french - public static final String PAPER_KEY_SPA = "zorro turismo mezcla nicho morir chico blanco pájaro alba esencia roer repetir";//spanish - public static final String PAPER_KEY_CHI = "怨 贪 旁 扎 吹 音 决 廷 十 助 畜 怒";//chinese +import androidx.test.ext.junit.runners.AndroidJUnit4; +import androidx.test.filters.LargeTest; +@RunWith(AndroidJUnit4.class) +@LargeTest -// @Test -// public void testWordsValid() { -// -// List list = getAllWords(); -// assertThat(list.size(), is(10240)); -// -// assertThat(isValid(PAPER_KEY_JAP, list), is(true)); -// assertThat(isValid(PAPER_KEY_ENG, list), is(true)); -// assertThat(isValid(PAPER_KEY_FRE, list), is(true)); -// assertThat(isValid(PAPER_KEY_SPA, list), is(true)); -// assertThat(isValid(PAPER_KEY_CHI, list), is(true)); -// } +public class PaperKeyTests { + private static final String TAG = PaperKeyTests.class.getName(); + public static final List PAPER_KEY_ENG = Arrays.asList("stick","sword","keen","afraid","smile","sting","huge","relax","nominee","arena","area","gift"); + @Test + public void testWordsValid() { + List wordList = getAllWords(); + Assert.assertEquals(wordList.size(), 2048); + Assert.assertSame(PAPER_KEY_ENG.containsAll(wordList),true); + } @Test public void testPaperKeyValidation() { List list = getAllWords(); - assertThat(list.size(), is(10240)); + assertThat(list.size(), is(2048)); } private List getAllWords() { List result = new ArrayList<>(); List names = new ArrayList<>(); names.add("en-BIP39Words.txt"); - names.add("es-BIP39Words.txt"); - names.add("fr-BIP39Words.txt"); - names.add("ja-BIP39Words.txt"); - names.add("zh-BIP39Words.txt"); for (String fileName : names) { InputStream in = null; @@ -78,7 +69,7 @@ private List getAllWords() { String cleanWord = Bip39Reader.cleanWord(s); cleanList.add(cleanWord); } - assertThat(cleanList.size(), is(10240)); + assertThat(cleanList.size(), is(2048)); return cleanList; } diff --git a/app/src/androidTest/java/com/litewallet/analytics/ConstantsTests.java b/app/src/androidTest/java/com/litewallet/analytics/ConstantsTests.java new file mode 100644 index 000000000..befde8c11 --- /dev/null +++ b/app/src/androidTest/java/com/litewallet/analytics/ConstantsTests.java @@ -0,0 +1,79 @@ +package com.litewallet.analytics; +import androidx.test.ext.junit.runners.AndroidJUnit4; +import android.util.Log; +import androidx.test.ext.junit.rules.ActivityScenarioRule; + +import com.breadwallet.presenter.activities.intro.IntroActivity; +import com.breadwallet.tools.util.BRConstants; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; + +import java.net.URI; + +@RunWith(AndroidJUnit4.class) +public class ConstantsTests { + public static final String TAG = ConstantsTests.class.getName(); + @Rule + public ActivityScenarioRule mActivityRule = new ActivityScenarioRule<>(IntroActivity.class); + @Before + public void setUp() { + Log.e(TAG, "setUp: "); + } + + @After + public void tearDown() { + } + @Test + public void testLitecoinSymbolConstants() { + Assert.assertSame(BRConstants.litecoinLowercase,"ł"); + Assert.assertSame(BRConstants.litecoinUppercase,"Ł"); + } + @Test + public void testAppExternalURLConstants() { + Assert.assertSame(BRConstants.TWITTER_LINK,"https://twitter.com/Litewallet_App"); + Assert.assertSame(BRConstants.INSTAGRAM_LINK,"https://www.instagram.com/litewallet.app"); + Assert.assertSame(BRConstants.WEB_LINK,"https://litewallet.io"); + Assert.assertSame(BRConstants.TOS_LINK,"https://litewallet.io/privacy"); + Assert.assertSame(BRConstants.CUSTOMER_SUPPORT_LINK,"https://support.litewallet.io/hc/en-us/requests/new"); + Assert.assertSame(BRConstants.BITREFILL_AFFILIATE_LINK,"https://www.bitrefill.com/"); + } + @Test + public void testFirebaseAnalyticsConstants() { + Assert.assertSame(BRConstants._20191105_AL,"app_launched"); + Assert.assertSame(BRConstants._20191105_VSC,"visit_send_controller"); + Assert.assertSame(BRConstants._20202116_VRC,"visit_receive_controller"); + Assert.assertSame(BRConstants._20191105_DSL,"did_send_ltc"); + Assert.assertSame(BRConstants._20191105_DTBT,"did_tap_buy_tab"); + Assert.assertSame(BRConstants._20200111_RNI,"rate_not_initialized"); + Assert.assertSame(BRConstants._20200111_FNI,"feeperkb_not_initialized"); + Assert.assertSame(BRConstants._20200111_TNI,"transaction_not_initialized"); + Assert.assertSame(BRConstants._20200111_WNI,"wallet_not_initialized"); + Assert.assertSame(BRConstants._20200111_PNI,"phrase_not_initialized"); + Assert.assertSame(BRConstants._20200111_UTST,"unable_to_sign_transaction"); + Assert.assertSame(BRConstants._20200112_ERR,"error"); + Assert.assertSame(BRConstants._20200112_DSR,"did_start_resync"); + Assert.assertSame(BRConstants._20200125_DSRR,"did_show_review_request"); + Assert.assertSame(BRConstants._20201118_DTGS,"did_tap_get_support"); + Assert.assertSame(BRConstants._20200217_DUWP,"did_unlock_with_pin"); + Assert.assertSame(BRConstants._20200217_DUWB,"did_unlock_with_biometrics"); + Assert.assertSame(BRConstants._20200301_DUDFPK,"did_use_default_fee_per_kb"); + Assert.assertSame(BRConstants._20201121_SIL,"started_IFPS_lookup"); + Assert.assertSame(BRConstants._20201121_DRIA,"did_resolve_IPFS_address"); + Assert.assertSame(BRConstants._20201121_FRIA,"failed_resolve_IPFS_address"); + Assert.assertSame(BRConstants._20200207_DTHB,"did_tap_header_balance"); + Assert.assertSame(BRConstants._20210427_HCIEEH,"heartbeat_check_if_event_even_happens"); + Assert.assertSame(BRConstants._20220822_UTOU,"user_tapped_on_ud"); + Assert.assertSame(BRConstants._20230131_NENR,"no_error_nominal_response"); + Assert.assertSame(BRConstants._20230407_DCS,"did_complete_sync"); + Assert.assertSame(BRConstants._20240123_RAGI,"registered_android_general_interest"); + Assert.assertSame(BRConstants._20231225_UAP,"user_accepted_push"); + Assert.assertSame(BRConstants._20240101_US,"user_signup"); + Assert.assertSame(BRConstants._20241006_DRR,"did_request_rating"); + Assert.assertSame(BRConstants._20241006_UCR,"user_completed_rating"); + } +} diff --git a/app/src/androidTest/java/com/breadwallet/database/DatabaseTests.java b/app/src/androidTest/java/com/litewallet/database/DatabaseTests.java similarity index 99% rename from app/src/androidTest/java/com/breadwallet/database/DatabaseTests.java rename to app/src/androidTest/java/com/litewallet/database/DatabaseTests.java index aff149c3a..bee21429d 100644 --- a/app/src/androidTest/java/com/breadwallet/database/DatabaseTests.java +++ b/app/src/androidTest/java/com/litewallet/database/DatabaseTests.java @@ -1,4 +1,4 @@ -package com.breadwallet.database; +package com.litewallet.database; import androidx.test.filters.LargeTest; import androidx.test.rule.ActivityTestRule; diff --git a/app/src/androidTest/java/com/breadwallet/platform/KVStoreTests.java b/app/src/androidTest/java/com/litewallet/platform/KVStoreTests.java similarity index 99% rename from app/src/androidTest/java/com/breadwallet/platform/KVStoreTests.java rename to app/src/androidTest/java/com/litewallet/platform/KVStoreTests.java index 61e31d724..cbb193773 100644 --- a/app/src/androidTest/java/com/breadwallet/platform/KVStoreTests.java +++ b/app/src/androidTest/java/com/litewallet/platform/KVStoreTests.java @@ -1,4 +1,4 @@ -package com.breadwallet.platform; +package com.litewallet.platform; import androidx.test.filters.LargeTest; import androidx.test.rule.ActivityTestRule; diff --git a/app/src/androidTest/java/com/breadwallet/platform/PlatformTests.java b/app/src/androidTest/java/com/litewallet/platform/PlatformTests.java similarity index 96% rename from app/src/androidTest/java/com/breadwallet/platform/PlatformTests.java rename to app/src/androidTest/java/com/litewallet/platform/PlatformTests.java index b96fd6b13..901d5aeb0 100644 --- a/app/src/androidTest/java/com/breadwallet/platform/PlatformTests.java +++ b/app/src/androidTest/java/com/litewallet/platform/PlatformTests.java @@ -1,4 +1,4 @@ -package com.breadwallet.platform; +package com.litewallet.platform; import androidx.test.rule.ActivityTestRule; import androidx.test.ext.junit.runners.AndroidJUnit4; @@ -8,11 +8,8 @@ import com.breadwallet.presenter.activities.BreadActivity; import com.breadwallet.tools.util.BRCompressor; import com.breadwallet.tools.util.BRConstants; -import com.breadwallet.tools.util.Utils; -import com.jniwrappers.BRKey; import com.platform.APIClient; -import org.apache.commons.io.IOUtils; import org.junit.Assert; import org.junit.Rule; import org.junit.Test; diff --git a/app/src/androidTest/java/com/breadwallet/presenter/activities/tests/MainActivityTest.java b/app/src/androidTest/java/com/litewallet/presenter/activities/tests/MainActivityTest.java similarity index 100% rename from app/src/androidTest/java/com/breadwallet/presenter/activities/tests/MainActivityTest.java rename to app/src/androidTest/java/com/litewallet/presenter/activities/tests/MainActivityTest.java diff --git a/app/src/androidTest/java/com/breadwallet/presenter/currencyformatting/tests/CurrencyFormatterFixedTests.java b/app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterFixedTests.java similarity index 100% rename from app/src/androidTest/java/com/breadwallet/presenter/currencyformatting/tests/CurrencyFormatterFixedTests.java rename to app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterFixedTests.java diff --git a/app/src/androidTest/java/com/breadwallet/presenter/currencyformatting/tests/CurrencyFormatterTests.java b/app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterTests.java similarity index 100% rename from app/src/androidTest/java/com/breadwallet/presenter/currencyformatting/tests/CurrencyFormatterTests.java rename to app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterTests.java diff --git a/app/src/androidTest/java/com/breadwallet/security/KeyStoreTests.java b/app/src/androidTest/java/com/litewallet/security/KeyStoreTests.java similarity index 99% rename from app/src/androidTest/java/com/breadwallet/security/KeyStoreTests.java rename to app/src/androidTest/java/com/litewallet/security/KeyStoreTests.java index c7a589d99..5de7a564f 100644 --- a/app/src/androidTest/java/com/breadwallet/security/KeyStoreTests.java +++ b/app/src/androidTest/java/com/litewallet/security/KeyStoreTests.java @@ -1,4 +1,4 @@ -package com.breadwallet.security; +package com.litewallet.security; import android.security.keystore.UserNotAuthenticatedException; import androidx.test.rule.ActivityTestRule; diff --git a/app/src/androidTest/java/com/breadwallet/security/NewKeyStoreTests.java b/app/src/androidTest/java/com/litewallet/security/NewKeyStoreTests.java similarity index 99% rename from app/src/androidTest/java/com/breadwallet/security/NewKeyStoreTests.java rename to app/src/androidTest/java/com/litewallet/security/NewKeyStoreTests.java index 730c052bb..5e1e9a64b 100644 --- a/app/src/androidTest/java/com/breadwallet/security/NewKeyStoreTests.java +++ b/app/src/androidTest/java/com/litewallet/security/NewKeyStoreTests.java @@ -1,4 +1,4 @@ -package com.breadwallet.security; +package com.litewallet.security; import android.app.Activity; import android.security.keystore.UserNotAuthenticatedException; diff --git a/app/src/androidTest/java/com/breadwallet/wallet/WalletTests.java b/app/src/androidTest/java/com/litewallet/wallet/WalletTests.java similarity index 98% rename from app/src/androidTest/java/com/breadwallet/wallet/WalletTests.java rename to app/src/androidTest/java/com/litewallet/wallet/WalletTests.java index 07bbbaab2..3fa3b0a87 100644 --- a/app/src/androidTest/java/com/breadwallet/wallet/WalletTests.java +++ b/app/src/androidTest/java/com/litewallet/wallet/WalletTests.java @@ -1,12 +1,10 @@ -package com.breadwallet.wallet; +package com.litewallet.wallet; import com.breadwallet.presenter.entities.RequestObject; import com.breadwallet.tools.security.BitcoinUrlHandler; import com.breadwallet.tools.util.BRConstants; -import com.breadwallet.tools.util.Utils; -import org.junit.Assert; import org.junit.Test; import java.math.BigDecimal; diff --git a/app/src/canary-file.json b/app/src/canary-file.json new file mode 100644 index 000000000..2f87c4ecb --- /dev/null +++ b/app/src/canary-file.json @@ -0,0 +1,3 @@ +{ + "filename": "dummy-file-for-testing" +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 126a570f5..633d9aea5 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -224,6 +224,18 @@ android:enabled="true" android:exported="false" android:label="SyncReceiver" /> + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/com/breadwallet/BreadApp.java b/app/src/main/java/com/breadwallet/BreadApp.java index 9f25fac96..f306f2031 100644 --- a/app/src/main/java/com/breadwallet/BreadApp.java +++ b/app/src/main/java/com/breadwallet/BreadApp.java @@ -3,13 +3,9 @@ import android.app.Activity; import android.app.Application; import android.content.Context; -import android.graphics.Point; -import android.hardware.fingerprint.FingerprintManager; -import android.os.Bundle; -import android.util.Log; -import android.view.Display; -import android.view.WindowManager; +import android.content.res.Resources; import com.breadwallet.presenter.activities.util.BRActivity; +import com.breadwallet.presenter.entities.PartnerNames; import com.breadwallet.tools.listeners.SyncReceiver; import com.breadwallet.tools.manager.AnalyticsManager; import com.breadwallet.tools.util.BRConstants; @@ -21,17 +17,27 @@ import com.google.android.gms.common.GooglePlayServicesNotAvailableException; import com.google.android.gms.common.GooglePlayServicesRepairableException; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.os.Build; +import com.google.firebase.messaging.FirebaseMessaging; + +import java.io.File; +import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; +import java.util.Scanner; import java.util.Timer; import java.util.TimerTask; import java.util.concurrent.atomic.AtomicInteger; import timber.log.Timber; import com.appsflyer.AppsFlyerLib; + +import org.json.JSONException; + public class BreadApp extends Application { public static int DISPLAY_HEIGHT_PX; - FingerprintManager mFingerprintManager; public static String HOST = "api.loafwallet.org"; private static List listeners; private static Timer isBackgroundChecker; @@ -43,27 +49,18 @@ public class BreadApp extends Application { public void onCreate() { super.onCreate(); - boolean enableCrashlytics = true; - if (Utils.isEmulatorOrDebug(this)) { - enableCrashlytics = false; - } - - // setup Timber - if(BuildConfig.DEBUG){ - Timber.plant(new Timber.DebugTree()); - } - + /// DEV: Top placement requirement. + boolean enableCrashlytics = !Utils.isEmulatorOrDebug(this); FirebaseCrashlytics.getInstance().setCrashlyticsCollectionEnabled(enableCrashlytics); AnalyticsManager.init(this); AnalyticsManager.logCustomEvent(BRConstants._20191105_AL); - AppsFlyerLib.getInstance().init("XXXX", null, this); - WindowManager wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); - Display display = wm.getDefaultDisplay(); - Point size = new Point(); - display.getSize(size); - DISPLAY_HEIGHT_PX = size.y; - mFingerprintManager = (FingerprintManager) getSystemService(Context.FINGERPRINT_SERVICE); + if(BuildConfig.DEBUG) Timber.plant(new Timber.DebugTree()); + DISPLAY_HEIGHT_PX = Resources.getSystem().getDisplayMetrics().heightPixels; + + String afID = Utils.fetchPartnerKey(this, PartnerNames.AFDEVID); + AppsFlyerLib.getInstance().init(afID, null, this); + AppsFlyerLib.getInstance().start(this); } public static Context getBreadContext() { return currentActivity == null ? SyncReceiver.app : currentActivity; @@ -110,26 +107,4 @@ protected void attachBaseContext(Context base) { public interface OnAppBackgrounded { void onBackgrounded(); } - private static class CrashReportingTree extends Timber.Tree { - private static final String CRASHLYTICS_KEY_PRIORITY = "priority"; - private static final String CRASHLYTICS_KEY_TAG = "tag"; - private static final String CRASHLYTICS_KEY_MESSAGE = "message"; - - @Override - protected void log(int priority, String tag, String message, Throwable throwable) { - if (priority == Log.VERBOSE || priority == Log.DEBUG) { - return; - } - - Throwable t = throwable != null ? throwable : new Exception(message); - - // Firebase Crash Reporting - FirebaseCrashlytics crashlytics = FirebaseCrashlytics.getInstance(); - crashlytics.setCustomKey(CRASHLYTICS_KEY_PRIORITY, priority); - crashlytics.setCustomKey(CRASHLYTICS_KEY_TAG, tag); - crashlytics.setCustomKey(CRASHLYTICS_KEY_MESSAGE, message); - - crashlytics.recordException(t); - } - } } diff --git a/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt b/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt index 4f8a27ee7..4e1bc4781 100644 --- a/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt +++ b/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt @@ -3,7 +3,10 @@ package com.breadwallet.entities import com.breadwallet.R class IntroLanguageResource { - + private val USD: String = "USD" + private val EUR: String = "EUR" + private val RMB: String = "RMB" + private val JPY: String = "JPY" fun loadResources() : Array{ return arrayOf ( IntroLanguage( @@ -20,7 +23,7 @@ class IntroLanguageResource { "La forma más segura de usar Litecoin.", R.raw.spanish, "¿Estás seguro de que quieres cambiar el idioma a español?", - Language.SPANISH + Language.SPANISH, ), IntroLanguage( Language.INDONESIAN.code, diff --git a/app/src/main/java/com/breadwallet/presenter/activities/BreadActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/BreadActivity.java index f738341ae..3a9d4c883 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/BreadActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/BreadActivity.java @@ -1,6 +1,7 @@ package com.breadwallet.presenter.activities; import android.animation.LayoutTransition; +import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; @@ -25,7 +26,9 @@ import androidx.transition.TransitionManager; import androidx.transition.TransitionSet; +import com.breadwallet.BreadApp; import com.breadwallet.R; +import com.breadwallet.entities.Language; import com.breadwallet.presenter.activities.intro.IntroActivity; import com.breadwallet.presenter.activities.util.BRActivity; import com.breadwallet.presenter.customviews.BRNotificationBar; @@ -44,16 +47,21 @@ import com.breadwallet.tools.util.BRCurrency; import com.breadwallet.tools.util.BRExchange; import com.breadwallet.tools.util.ExtensionKt; +import com.breadwallet.tools.util.LocaleHelper; import com.breadwallet.tools.util.Utils; import com.breadwallet.wallet.BRPeerManager; import com.breadwallet.wallet.BRWalletManager; +import com.google.android.gms.tasks.Task; import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.play.core.review.ReviewInfo; import com.google.android.play.core.review.ReviewManager; import com.google.android.play.core.review.ReviewManagerFactory; -import com.google.android.gms.tasks.Task; import java.math.BigDecimal; +import java.text.DateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; import timber.log.Timber; @@ -367,6 +375,25 @@ public void onBalanceChanged(final long balance) { updateUI(); } + private static final Map fiat = new HashMap<>(); + + static { + fiat.put(Language.ENGLISH, "USD"); + fiat.put(Language.SPANISH, "EUR"); + fiat.put(Language.GERMAN, "EUR"); + fiat.put(Language.FRENCH, "EUR"); + fiat.put(Language.JAPANESE, "JPY"); + fiat.put(Language.INDONESIAN, "USD"); + fiat.put(Language.ITALIAN, "EUR"); + fiat.put(Language.PORTUGUESE, "EUR"); + fiat.put(Language.TURKISH, "EUR"); + fiat.put(Language.UKRAINIAN, "EUR"); + fiat.put(Language.RUSSIAN, "EUR"); + fiat.put(Language.KOREAN, "EUR"); + fiat.put(Language.CHINESE_TRADITIONAL, "USD"); + fiat.put(Language.CHINESE_SIMPLIFIED, "RMB"); + } + public void updateUI() { BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(() -> { Thread.currentThread().setName(Thread.currentThread().getName() + ":updateUI"); @@ -381,8 +408,12 @@ public void updateUI() { final String formattedBTCAmount = BRCurrency.getFormattedCurrencyString(BreadActivity.this, "LTC", btcAmount); //amount in currency units - final BigDecimal curAmount = BRExchange.getAmountFromLitoshis(BreadActivity.this, iso, amount); - final String formattedCurAmount = BRCurrency.getFormattedCurrencyString(BreadActivity.this, iso, curAmount); + Language currentLanguage = LocaleHelper.Companion.getInstance().getCurrentLocale(); + String correspondingFiat = fiat.getOrDefault(currentLanguage, "USD"); + + assert correspondingFiat != null; + final BigDecimal curAmount = BRExchange.getAmountFromLitoshis(BreadActivity.this, correspondingFiat, amount); + final String formattedCurAmount = BRCurrency.getFormattedCurrencyString(BreadActivity.this, correspondingFiat, curAmount); runOnUiThread(() -> { primaryPrice.setText(formattedBTCAmount); secondaryPrice.setText(String.format("%s", formattedCurAmount)); @@ -425,6 +456,9 @@ public void onRequestPermissionsResult(int requestCode, @NonNull String[] permis @Override public void onConnectionChanged(boolean isConnected) { + + Context thisContext = BreadActivity.this; + Context app = BreadApp.getBreadContext(); if (isConnected) { if (barFlipper != null) { if (barFlipper.getDisplayedChild() == 1) { @@ -432,17 +466,17 @@ public void onConnectionChanged(boolean isConnected) { } } BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(() -> { - final double progress = BRPeerManager.syncProgress(BRSharedPrefs.getStartHeight(BreadActivity.this)); - Timber.d("timber: Sync Progress: %s", progress); - if (progress < 1 && progress > 0) { - SyncManager.getInstance().startSyncingProgressThread(); + final double progress = BRPeerManager.syncProgress(BRSharedPrefs.getStartHeight(thisContext)); + if (progress > 0 && progress < 1) { + SyncManager.getInstance().startSyncingProgressThread(app); } }); - } else { + } + else { if (barFlipper != null) { addNotificationBar(); } - SyncManager.getInstance().stopSyncingProgressThread(); + SyncManager.getInstance().stopSyncingProgressThread(app); } } diff --git a/app/src/main/java/com/breadwallet/presenter/activities/intro/IntroActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/intro/IntroActivity.java index e186f38e1..56a710d13 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/intro/IntroActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/intro/IntroActivity.java @@ -2,6 +2,7 @@ package com.breadwallet.presenter.activities.intro; import android.app.Dialog; +import android.content.Context; import android.content.Intent; import android.graphics.Point; import android.os.Bundle; @@ -12,8 +13,11 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; + +import com.appsflyer.AppsFlyerLib; import com.breadwallet.entities.IntroLanguageResource; import com.breadwallet.presenter.activities.SetPinActivity; +import com.breadwallet.presenter.entities.PartnerNames; import com.breadwallet.tools.adapter.CountryLanguageAdapter; import com.breadwallet.tools.util.LocaleHelper; import com.google.android.material.snackbar.Snackbar; @@ -30,8 +34,13 @@ import com.breadwallet.tools.util.Utils; import com.breadwallet.wallet.BRWalletManager; import com.platform.APIClient; + +import java.io.File; +import java.io.FileNotFoundException; import java.io.Serializable; import java.util.Objects; +import java.util.Scanner; + import timber.log.Timber; public class IntroActivity extends BRActivity implements Serializable { @@ -158,10 +167,36 @@ public void run() { APIClient apiClient = APIClient.getInstance(IntroActivity.this); long endTime = System.currentTimeMillis(); Timber.d("timber: updateBundle DONE in %sms",endTime - startTime); + + + //DEV Moved this back until after the bundle is loaded + //STILL NOT WORKING + // String afID = Utils.fetchPartnerKey(IntroActivity.this, PartnerNames.AFDEVID) + // AppsFlyerLib.getInstance().init(afID, null, IntroActivity.this); + // AppsFlyerLib.getInstance().start(IntroActivity.this); + // boolean didVerify = verifyInstallAssets(IntroActivity.this); } }); } + public static boolean verifyInstallAssets(final Context context) { + Timber.d("timber: verify"); + + String pusherStagingKey = Utils.fetchPartnerKey(context, PartnerNames.PUSHERSTAGING); + boolean isCanaryFilePresent = false; + try (Scanner scanner = new Scanner(new File("canary-file.json"))) { + while (scanner.hasNextLine()) { + String line = scanner.nextLine(); + Timber.d("timber: canary file : %s", line); + isCanaryFilePresent = line.length() > 5; + } + } catch (RuntimeException | FileNotFoundException e) { + Timber.e(e); + } + + return ( pusherStagingKey.contains("4cc2-94df") && isCanaryFilePresent ); + } + private void setListeners() { newWalletButton.setOnClickListener(new View.OnClickListener() { @Override diff --git a/app/src/main/java/com/breadwallet/presenter/entities/PartnerNames.kt b/app/src/main/java/com/breadwallet/presenter/entities/PartnerNames.kt index f01b6088b..ad43d1962 100644 --- a/app/src/main/java/com/breadwallet/presenter/entities/PartnerNames.kt +++ b/app/src/main/java/com/breadwallet/presenter/entities/PartnerNames.kt @@ -1,12 +1,10 @@ package com.breadwallet.presenter.entities enum class PartnerNames(val key: String) { - MOONPAY("moonpay"), - BITREFILL("bitrefill"), - INFURA("infura-api"), LITEWALLETOPS("litewallet-ops"), OPSALL("litewallet-ops"), LITEWALLETSTART("litewallet-start"), PUSHER("pusher-instance-id"), PUSHERSTAGING("pusher-staging-instance-id"), -} + AFDEVID("af-dev-id") +} \ No newline at end of file diff --git a/app/src/main/java/com/breadwallet/presenter/entities/TxItem.java b/app/src/main/java/com/breadwallet/presenter/entities/TxItem.java index 8cac523a3..ec7ec44ca 100644 --- a/app/src/main/java/com/breadwallet/presenter/entities/TxItem.java +++ b/app/src/main/java/com/breadwallet/presenter/entities/TxItem.java @@ -1,10 +1,11 @@ package com.breadwallet.presenter.entities; -import com.breadwallet.tools.util.Utils; import com.platform.entities.TxMetaData; -public class TxItem { +import java.io.Serializable; + +public class TxItem implements Serializable { public static final String TAG = TxItem.class.getName(); private long timeStamp; private int blockHeight; diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/DynamicDonationFragment.java b/app/src/main/java/com/breadwallet/presenter/fragments/DynamicDonationFragment.java deleted file mode 100644 index a467fef3d..000000000 --- a/app/src/main/java/com/breadwallet/presenter/fragments/DynamicDonationFragment.java +++ /dev/null @@ -1,231 +0,0 @@ -package com.breadwallet.presenter.fragments; - -import android.app.Activity; -import android.os.Bundle; -import android.util.Pair; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.AdapterView; -import android.widget.ArrayAdapter; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.SeekBar; -import android.widget.Spinner; -import android.widget.TextView; - -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; - -import com.breadwallet.R; -import com.breadwallet.presenter.customviews.BRDialogView; -import com.breadwallet.presenter.entities.PartnerNames; -import com.breadwallet.presenter.entities.TransactionItem; -import com.breadwallet.tools.manager.AnalyticsManager; -import com.breadwallet.tools.manager.BRSharedPrefs; -import com.breadwallet.tools.manager.FeeManager; -import com.breadwallet.tools.security.BRSender; -import com.breadwallet.tools.util.BRConstants; -import com.breadwallet.tools.util.BRCurrency; -import com.breadwallet.tools.util.BRExchange; -import com.breadwallet.tools.util.Utils; -import com.breadwallet.wallet.BRWalletManager; - -import java.math.BigDecimal; - -/** - * Litewallet - * Created by Mohamed Barry on 3/2/20 - * email: mosadialiou@gmail.com - * Copyright © 2020 Litecoin Foundation. All rights reserved. - */ -public class DynamicDonationFragment extends Fragment { - - static final long BALANCE_STEP = 1_000_000; - - private long currentBalance; - - private TextView addressVal; - private TextView amountVal; - private TextView feeVal; - private TextView totalVal; - - private TextView donationToTheLitewalletTeam; - - private TextView amountSliderVal; - - private SeekBar seekBar; - private String selectedIso; - private boolean isLTCSwap = true; - private String chosenAddress = BRConstants.DONATION_ADDRESS; - private long mDonationAmount; - - - @Nullable - @Override - public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, Bundle savedInstanceState) { - return inflater.inflate(R.layout.fragment_dynamic_donation, container, false); - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - selectedIso = BRSharedPrefs.getIsoSymbol(getContext()); - isLTCSwap = BRSharedPrefs.getPreferredLTC(getContext()); - - addressVal = view.findViewById(R.id.addressVal); - addressVal.setText(chosenAddress); - - donationToTheLitewalletTeam = view.findViewById(R.id.donationAddressesPhrase); - donationToTheLitewalletTeam.setText(getString(R.string.Donate_toThe_LWTeam)); - - TextView processingTimeLbl = view.findViewById(R.id.processingTimeLbl); - processingTimeLbl.setText(getString(R.string.Confirmation_processingAndDonationTime, "2.5-5")); - - amountVal = view.findViewById(R.id.amountVal); - feeVal = view.findViewById(R.id.feeVal); - totalVal = view.findViewById(R.id.totalVal); - - Button cancelBut = view.findViewById(R.id.cancelBut); - cancelBut.setOnClickListener(v -> { - AnalyticsManager.logCustomEvent(BRConstants._20200225_DCD); - getActivity().onBackPressed(); - }); - - Button donateBut = view.findViewById(R.id.donateBut); - donateBut.setOnClickListener(v -> { - BRDialogView dialog = new BRDialogView(); - dialog.setTitle(getString(R.string.Donate_Dialog_title)); - - dialog.setMessage(getString(R.string.Donate_Dialog_message)); - dialog.setNegButton(getString(R.string.Donate_Dialog_Negative_text)); - dialog.setPosButton(getString(R.string.Donate_Dialog_Positive_text)); - dialog.setPosListener(brDialogView -> { - dialog.dismiss(); - sendDonation(); - }); - dialog.setNegListener(brDialogView -> dialog.dismiss()); - dialog.show(((Activity) getActivity()).getFragmentManager(), dialog.getClass().getName()); - - }); - - amountSliderVal = view.findViewById(R.id.amountSliderVal); - - seekBar = view.findViewById(R.id.seekBar); - - ImageButton upAmountBut = view.findViewById(R.id.upAmountBut); - ImageButton downAmountBut = view.findViewById(R.id.downAmountBut); - - upAmountBut.setOnClickListener(v -> { - seekBar.incrementProgressBy(diff()); - - long newAmount = newAmount(seekBar.getProgress()); - updateDonationValues(newAmount); - }); - - downAmountBut.setOnClickListener(v -> { - seekBar.incrementProgressBy(-diff()); - - long newAmount = newAmount(seekBar.getProgress()); - updateDonationValues(newAmount); - }); - - seekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() { - @Override - public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) { - updateDonationValues(newAmount(progress)); - } - - @Override - public void onStartTrackingTouch(SeekBar seekBar) { - //NO-OP - } - - @Override - public void onStopTrackingTouch(SeekBar seekBar) { - //NO-OP - } - }); - - setFeeToRegular(); - - currentBalance = BRSharedPrefs.getCatchedBalance(getContext()); - - updateDonationValues(BRConstants.DONATION_AMOUNT); - } - - private void sendDonation() { - String memo = getString(R.string.Donate_toThe_LWTeam) + chosenAddress; - TransactionItem request = new TransactionItem(chosenAddress, - null, - null, - mDonationAmount, - 0, - null, - false, - memo); - - Bundle params = new Bundle(); - params.putString("donation_address", chosenAddress); - params.putLong("donation_amount", mDonationAmount); - params.putString("address_scheme", "v2"); - AnalyticsManager.logCustomEventWithParams(BRConstants._20200223_DD, params); - BRSender.getInstance().sendTransaction(getContext(), request); - } - - private void setFeeToRegular() { - FeeManager feeManager = FeeManager.getInstance(); - - //TODO: This should be inserted into the FeeManager after v0.4.0 - AnalyticsManager.logCustomEvent(BRConstants._20200301_DUDFPK); - - feeManager.resetFeeType(); - BRWalletManager.getInstance().setFeePerKb(feeManager.currentFees.regular); - } - - private int diff() { - float step = (currentBalance - BRConstants.DONATION_AMOUNT) * 1f / BALANCE_STEP; - int diff = (int) (seekBar.getMax() * 1f / step); - return Math.max(diff, 1); - } - - private long newAmount(int progress) { - long maxFee = BRWalletManager.getInstance().feeForTransactionAmount(currentBalance); - long adjustedAmount = (long) ((progress * 1f / seekBar.getMax()) * (currentBalance - BRConstants.DONATION_AMOUNT - maxFee)); - return adjustedAmount + BRConstants.DONATION_AMOUNT; - } - - private void updateDonationValues(long donationAmount) { - mDonationAmount = donationAmount; - final BigDecimal donation = new BigDecimal(donationAmount); - - long feeAmount = BRWalletManager.getInstance().feeForTransactionAmount(donationAmount); - final BigDecimal fee = new BigDecimal(feeAmount); - - final BigDecimal total = new BigDecimal(donationAmount + feeAmount); - - amountVal.setText(formatResultAmount(formatLtcAmount(donation), formatIsoAmount(donation))); - feeVal.setText(formatResultAmount(formatLtcAmount(fee), formatIsoAmount(fee))); - totalVal.setText(formatResultAmount(formatLtcAmount(total), formatIsoAmount(total))); - - amountSliderVal.setText(totalVal.getText()); - } - - private String formatLtcAmount(BigDecimal amount) { - BigDecimal ltcAmount = BRExchange.getLitecoinForLitoshis(getContext(), amount); - return BRCurrency.getFormattedCurrencyString(getContext(), "LTC", ltcAmount); - } - - private String formatIsoAmount(BigDecimal amount) { - BigDecimal fiatAmount = BRExchange.getAmountFromLitoshis(getContext(), selectedIso, amount); - return BRCurrency.getFormattedCurrencyString(getContext(), selectedIso, fiatAmount); - } - - private String formatResultAmount(String ltcAmount, String isoAmount) { - String format = "%s (%s)"; - if (isLTCSwap) { - return String.format(format, ltcAmount, isoAmount); - } else { - return String.format(format, isoAmount, ltcAmount); - } - } -} diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentBalanceSeedReminder.kt b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentBalanceSeedReminder.kt index fc523031e..d9a624a54 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentBalanceSeedReminder.kt +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentBalanceSeedReminder.kt @@ -10,7 +10,11 @@ import android.widget.* import androidx.fragment.app.Fragment import com.breadwallet.R import com.breadwallet.tools.animation.BRAnimator +import com.breadwallet.tools.manager.AnalyticsManager +import com.breadwallet.tools.manager.TxManager import com.breadwallet.tools.security.BRKeyStore +import com.breadwallet.tools.util.BRConstants +import timber.log.Timber import java.util.* class FragmentBalanceSeedReminder : Fragment() { @@ -66,14 +70,22 @@ class FragmentBalanceSeedReminder : Fragment() { setListeners() fetchSeedPhrase() } - + private fun registerAnalyticsError(errorString: String) { + Timber.d("Fragment Balance Seed: RegisterError : %s", errorString) + val params = Bundle() + params.putString("lwa_error_message", errorString); + AnalyticsManager.logCustomEventWithParams(BRConstants._20200112_ERR, params) + } fun fetchSeedPhrase() { - try { - seedPhraseTextView.text = String(BRKeyStore.getPhrase(context, 0)) - } catch (_: UserNotAuthenticatedException) { + seedPhraseTextView.text = "NO_PHRASE" + if (this.activity == null) { + registerAnalyticsError("null_in_fragment_balance_fetch_seed") + } + else { + seedPhraseTextView.text = runCatching { BRKeyStore.getPhrase(this.activity, 0) } + .getOrNull()?.decodeToString() ?: "NO_PHRASE" } } - private fun animateClose() { BRAnimator.animateBackgroundDim(backgroundLayout, true) BRAnimator.animateSignalSlide(signalLayout, true) { close() } diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentFingerprint.java b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentFingerprint.java index f731ee1d4..1ae433f15 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentFingerprint.java +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentFingerprint.java @@ -5,7 +5,6 @@ import android.animation.ArgbEvaluator; import android.animation.ValueAnimator; import android.app.Activity; -import android.app.Fragment; import android.content.Context; import android.hardware.fingerprint.FingerprintManager; import android.os.Bundle; @@ -22,6 +21,8 @@ import android.widget.TextView; import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import com.breadwallet.R; import com.breadwallet.presenter.activities.BreadActivity; @@ -155,7 +156,7 @@ public void onPause() { @Override public void onAuthenticated() { - final Activity app = getActivity(); + final FragmentActivity app = getActivity(); authSucceeded = true; if (completion != null) completion.onComplete(); @@ -174,7 +175,7 @@ public void setCompletion(BRAuthCompletion completion) { public void onError() { String authError = "auth_prompt_failed"; Bundle params = new Bundle(); - params.putString("error_message",authError); + params.putString("lwa_error_message",authError); AnalyticsManager.logCustomEventWithParams(BRConstants._20200112_ERR, params); } diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentPin.java b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentPin.java index 527f736ea..5ed659281 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentPin.java +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentPin.java @@ -1,7 +1,6 @@ package com.breadwallet.presenter.fragments; import android.app.Activity; -import android.app.Fragment; import android.os.Bundle; import android.os.Handler; import android.view.LayoutInflater; @@ -14,6 +13,7 @@ import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; +import androidx.fragment.app.Fragment; import com.breadwallet.R; import com.breadwallet.presenter.activities.BreadActivity; @@ -65,7 +65,7 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa mainLayout.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - getActivity().getFragmentManager().beginTransaction().remove(FragmentPin.this).commit(); + requireActivity().getSupportFragmentManager().beginTransaction().remove(FragmentPin.this).commit(); } }); diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentSend.kt b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentSend.kt index 6cfe35a40..fbfeabb07 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentSend.kt +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentSend.kt @@ -43,7 +43,7 @@ import java.util.regex.Pattern class FragmentSend : Fragment() { private lateinit var signalLayout: LinearLayout; private lateinit var keyboardLayout: LinearLayout - private lateinit var scanButton: Button; private lateinit var pasteButton: Button; private lateinit var sendButton: Button; private lateinit var donateButton: Button; private lateinit var isoCurrencySymbolButton: Button + private lateinit var scanButton: Button; private lateinit var pasteButton: Button; private lateinit var sendButton: Button; private lateinit var isoCurrencySymbolButton: Button private lateinit var commentEdit: EditText; private lateinit var addressEdit: EditText;private lateinit var amountEdit: EditText private lateinit var isoCurrencySymbolText: TextView; private lateinit var balanceText: TextView; private lateinit var feeText: TextView; private lateinit var feeDescription: TextView; private lateinit var warningText: TextView private var amountLabelOn = true; private var ignoreCleanup = false; private var feeButtonsShown = false @@ -74,7 +74,6 @@ class FragmentSend : Fragment() { pasteButton = rootView.findViewById(R.id.paste_button) as Button sendButton = rootView.findViewById(R.id.send_button) as Button - donateButton = rootView.findViewById(R.id.donate_button) as Button commentEdit = rootView.findViewById(R.id.comment_edit) as EditText amountEdit = rootView.findViewById(R.id.amount_edit) as EditText balanceText = rootView.findViewById(R.id.balance_text) as TextView @@ -389,14 +388,6 @@ class FragmentSend : Fragment() { } }, ) - donateButton.setOnClickListener( - View.OnClickListener { - if (!BRAnimator.isClickAllowed()) { - return@OnClickListener - } - BRAnimator.showDynamicDonationFragment(requireActivity()) - }, - ) backgroundLayout.setOnClickListener( View.OnClickListener { if (!BRAnimator.isClickAllowed()) return@OnClickListener @@ -615,11 +606,6 @@ class FragmentSend : Fragment() { formattedNetworkFee, formattedServiceFee, formattedTotalFees) - //(Network + Service): $0.01 + $1.37 = $1.38 - donateButton.text = getString(R.string.Donate_title, currencySymbol) - - ///Added another check. User must have sent once already before donating - donateButton.isEnabled = currentBalance >= BRConstants.DONATION_AMOUNT * 2 && (TxManager.getInstance().adapter.items.size > 1) amountLayout.requestLayout() } diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionDetails.java b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionDetails.java index fd623e708..f67f7ee16 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionDetails.java +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionDetails.java @@ -1,9 +1,6 @@ package com.breadwallet.presenter.fragments; -import android.app.Activity; -import android.app.Fragment; import android.os.Bundle; -import android.util.TypedValue; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -11,8 +8,11 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.viewpager.widget.ViewPager; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentActivity; +import androidx.viewpager2.widget.ViewPager2; import com.breadwallet.R; import com.breadwallet.presenter.entities.TxItem; @@ -23,14 +23,20 @@ import timber.log.Timber; -public class FragmentTransactionDetails extends Fragment { +public class FragmentTransactionDetails extends DialogFragment { public TextView mTitle; public LinearLayout backgroundLayout; - private ViewPager txViewPager; + private ViewPager2 txViewPager; private TransactionPagerAdapter txPagerAdapter; private List items; + @Override + public void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setStyle(STYLE_NO_TITLE, android.R.style.Theme_DeviceDefault_Light_NoActionBar); + } + @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { // The last two arguments ensure LayoutParams are inflated @@ -38,31 +44,19 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa View rootView = inflater.inflate(R.layout.fragment_transaction_details, container, false); mTitle = (TextView) rootView.findViewById(R.id.title); backgroundLayout = (LinearLayout) rootView.findViewById(R.id.background_layout); - txViewPager = (ViewPager) rootView.findViewById(R.id.tx_list_pager); - txViewPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() { - public void onPageScrollStateChanged(int state) { - } - - public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) { - } - - public void onPageSelected(int position) { - // Check if this is the page you want. - } - }); - txPagerAdapter = new TransactionPagerAdapter(getChildFragmentManager(), items); + txViewPager = (ViewPager2) rootView.findViewById(R.id.tx_list_pager); + txPagerAdapter = new TransactionPagerAdapter(getChildFragmentManager(), getLifecycle(), items); txViewPager.setAdapter(txPagerAdapter); txViewPager.setOffscreenPageLimit(5); - int margin = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 16 * 2, getResources().getDisplayMetrics()); - txViewPager.setPageMargin(-margin); - int pos = getArguments().getInt("pos"); + + int pos = getArguments().getInt("pos", 0); txViewPager.setCurrentItem(pos, false); return rootView; } @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); final ViewTreeObserver observer = txViewPager.getViewTreeObserver(); @@ -79,11 +73,11 @@ public void onGlobalLayout() { } public void close() { - final Activity app = getActivity(); + final FragmentActivity app = getActivity(); BRAnimator.animateBackgroundDim(backgroundLayout, true); BRAnimator.animateSignalSlide(txViewPager, true, () -> { if (app != null && !app.isFinishing()) - app.getFragmentManager().popBackStack(); + app.getSupportFragmentManager().popBackStack(); else Timber.d("timber: onAnimationEnd: app is null"); }); diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionItem.java b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionItem.java index 714c35239..a23d77d78 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionItem.java +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionItem.java @@ -1,7 +1,6 @@ package com.breadwallet.presenter.fragments; -import android.app.Activity; -import android.app.Fragment; + import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -16,7 +15,10 @@ import android.widget.LinearLayout; import android.widget.TextView; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentActivity; import com.breadwallet.R; import com.breadwallet.presenter.entities.PartnerNames; @@ -49,6 +51,9 @@ import timber.log.Timber; public class FragmentTransactionItem extends Fragment { + + private static final String ARG_ITEM = "arg_item"; + public TextView mTitle; private TextView mLargeDescriptionText; private TextView mSubHeader; @@ -98,28 +103,28 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa } @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); + if (getArguments() == null) { + requireActivity().getSupportFragmentManager().popBackStack(); + return; + } + + item = (TxItem) getArguments().getSerializable(ARG_ITEM); + fillTexts(); } private void fillTexts() { + if (item == null) { + requireActivity().getSupportFragmentManager().popBackStack(); + return; + } + //get the current iso String iso = BRSharedPrefs.getPreferredLTC(getActivity()) ? "LTC" : BRSharedPrefs.getIsoSymbol(getContext()); - long opsAmount = Long.MAX_VALUE; - long[] outAmounts = item.getOutAmounts(); - if (outAmounts.length == 3) { - for (int i = 0; i < outAmounts.length; i++) { - long value = outAmounts[i]; - if (value < opsAmount && value != 0L) { - opsAmount = value; - } - } - } - else { - opsAmount = 0L; - } + long opsAmount = getOpsAmount(); //get the tx amount BigDecimal txAmount = new BigDecimal(item.getReceived() - item.getSent()).abs(); @@ -149,7 +154,7 @@ private void fillTexts() { //Filter method if (filteredAddress.stream().findFirst().isPresent()) { - sendAddress = filteredAddress.stream().findFirst().get(); + sendAddress = filteredAddress.stream().findFirst().get(); } else { sendAddress = "ERROR-ADDRESS"; } @@ -221,6 +226,23 @@ private void fillTexts() { mAddressText.setText(sendAddress); } + private long getOpsAmount() { + long opsAmount = 0; + + if (item == null || item.getOutAmounts() == null || item.getOutAmounts().length != 3) { + return opsAmount; + } + + long[] outAmounts = item != null ? item.getOutAmounts() : new long[0]; + for (long value : outAmounts) { + if (value < opsAmount) { + opsAmount = value; + } + } + + return opsAmount; + } + private int getLevel(TxItem item) { int blockHeight = item.getBlockHeight(); int confirms = blockHeight == Integer.MAX_VALUE ? 0 : BRSharedPrefs.getLastBlockHeight(getContext()) - blockHeight + 1; @@ -260,7 +282,7 @@ public void onResume() { @Override public void onPause() { String comment = mCommentText.getText().toString(); - final Activity app = getActivity(); + final FragmentActivity app = getActivity(); if (!comment.equals(oldComment)) { final TxMetaData md = new TxMetaData(); md.comment = comment; @@ -279,12 +301,12 @@ public void run() { public static FragmentTransactionItem newInstance(TxItem item) { FragmentTransactionItem f = new FragmentTransactionItem(); - f.setItem(item); - return f; - } - public void setItem(TxItem item) { - this.item = item; + Bundle args = new Bundle(); + args.putSerializable(ARG_ITEM, item); + f.setArguments(args); + + return f; } private String getFormattedDate(long timeStamp) { @@ -312,4 +334,4 @@ private void close() { ((FragmentTransactionDetails) parentFragment).close(); } } -} +} \ No newline at end of file diff --git a/app/src/main/java/com/breadwallet/presenter/history/HistoryFragment.kt b/app/src/main/java/com/breadwallet/presenter/history/HistoryFragment.kt index 8aa05d0b5..f65b1dbc7 100644 --- a/app/src/main/java/com/breadwallet/presenter/history/HistoryFragment.kt +++ b/app/src/main/java/com/breadwallet/presenter/history/HistoryFragment.kt @@ -7,15 +7,18 @@ import android.view.ViewGroup import com.breadwallet.databinding.FragmentHistoryBinding import com.breadwallet.presenter.activities.BreadActivity import com.breadwallet.presenter.base.BaseFragment +import com.breadwallet.tools.manager.AnalyticsManager import com.breadwallet.tools.manager.BRSharedPrefs import com.breadwallet.tools.manager.BRSharedPrefs.OnIsoChangedListener import com.breadwallet.tools.manager.TxManager import com.breadwallet.tools.sqlite.TransactionDataSource.OnTxAddedListener import com.breadwallet.tools.threads.BRExecutor +import com.breadwallet.tools.util.BRConstants import com.breadwallet.wallet.BRPeerManager import com.breadwallet.wallet.BRPeerManager.OnTxStatusUpdate import com.breadwallet.wallet.BRWalletManager import com.breadwallet.wallet.BRWalletManager.OnBalanceChanged +import timber.log.Timber /** Litewallet * Created by Mohamed Barry on 6/1/20 @@ -56,11 +59,22 @@ class HistoryFragment : BRPeerManager.getInstance().removeListener(this) BRSharedPrefs.removeListener(this) } - + private fun registerAnalyticsError(errorString: String) { + val params = Bundle() + params.putString("lwa_error_message", errorString); + AnalyticsManager.logCustomEventWithParams(BRConstants._20200112_ERR, params) + Timber.d("History Fragment: RegisterError : %s", errorString) + } override fun onResume() { super.onResume() addObservers() - TxManager.getInstance().onResume(requireActivity() as BreadActivity) + + if (this.activity == null) { + registerAnalyticsError("null_in_history_fragment_on_resume") + } + else { + TxManager.getInstance().onResume(this.activity) + } } override fun onPause() { @@ -74,7 +88,12 @@ class HistoryFragment : override fun onStatusUpdate() { BRExecutor.getInstance().forBackgroundTasks().execute { - TxManager.getInstance().updateTxList(requireActivity() as BreadActivity) + if (this.activity == null) { + registerAnalyticsError("null_in_history_fragment_on_status_update") + } + else { + TxManager.getInstance().updateTxList(this.activity) + } } } @@ -84,14 +103,23 @@ class HistoryFragment : override fun onTxAdded() { BRExecutor.getInstance().forBackgroundTasks().execute { - TxManager.getInstance().updateTxList(requireActivity() as BreadActivity) + if (this.activity == null) { + registerAnalyticsError("null_in_history_fragment_on_tx_added") + } + else { + TxManager.getInstance().updateTxList(this.activity) + } } } - private fun updateUI() { BRExecutor.getInstance().forLightWeightBackgroundTasks().execute { - Thread.currentThread().name = Thread.currentThread().name + "HistoryFragment:updateUI" - TxManager.getInstance().updateTxList(requireActivity() as BreadActivity) + if (this.activity == null) { + registerAnalyticsError("null_in_history_fragment_update_ui") + } + else { + Thread.currentThread().name = Thread.currentThread().name + "HistoryFragment:updateUI" + TxManager.getInstance().updateTxList(this.activity) + } } } diff --git a/app/src/main/java/com/breadwallet/tools/adapter/TransactionListAdapter.java b/app/src/main/java/com/breadwallet/tools/adapter/TransactionListAdapter.java index 196dbfd8d..a2aa5acce 100644 --- a/app/src/main/java/com/breadwallet/tools/adapter/TransactionListAdapter.java +++ b/app/src/main/java/com/breadwallet/tools/adapter/TransactionListAdapter.java @@ -112,7 +112,7 @@ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType else if (viewType == promptType) return new PromptHolder(inflater.inflate(promptResId, parent, false)); else if (viewType == syncingType) - return new SyncingHolder(inflater.inflate(syncingResId, parent, false)); + return new SyncingProgressViewHolder(inflater.inflate(syncingResId, parent, false)); return null; } @Override @@ -125,7 +125,7 @@ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { setPrompt((PromptHolder) holder); break; case syncingType: - setSyncing((SyncingHolder) holder); + setSyncing((SyncingProgressViewHolder) holder); break; } } @@ -304,8 +304,8 @@ private void setPrompt(final PromptHolder prompt) { prompt.description.setText(TxManager.getInstance().promptInfo.description); } - private void setSyncing(final SyncingHolder syncing) { - TxManager.getInstance().syncingHolder = syncing; + private void setSyncing(final SyncingProgressViewHolder syncing) { + TxManager.getInstance().syncingProgressViewHolder = syncing; syncing.mainLayout.setBackgroundResource(R.drawable.tx_rounded); } @@ -430,14 +430,15 @@ public PromptHolder(View view) { } } - public class SyncingHolder extends RecyclerView.ViewHolder { + public class SyncingProgressViewHolder extends RecyclerView.ViewHolder { public RelativeLayout mainLayout; public ConstraintLayout constraintLayout; public TextView date; public TextView label; public ProgressBar progress; + public TextView blockheightView; - public SyncingHolder(View view) { + public SyncingProgressViewHolder(View view) { super(view); mainLayout = (RelativeLayout) view.findViewById(R.id.main_layout); constraintLayout = (ConstraintLayout) view.findViewById(R.id.syncing_layout); diff --git a/app/src/main/java/com/breadwallet/tools/adapter/TransactionPagerAdapter.java b/app/src/main/java/com/breadwallet/tools/adapter/TransactionPagerAdapter.java index e38b8087c..388590109 100644 --- a/app/src/main/java/com/breadwallet/tools/adapter/TransactionPagerAdapter.java +++ b/app/src/main/java/com/breadwallet/tools/adapter/TransactionPagerAdapter.java @@ -1,36 +1,34 @@ package com.breadwallet.tools.adapter; -import android.app.Fragment; -import android.app.FragmentManager; -import androidx.legacy.app.FragmentPagerAdapter; +import androidx.annotation.NonNull; +import androidx.fragment.app.Fragment; +import androidx.fragment.app.FragmentManager; +import androidx.lifecycle.Lifecycle; +import androidx.viewpager2.adapter.FragmentStateAdapter; import com.breadwallet.presenter.entities.TxItem; import com.breadwallet.presenter.fragments.FragmentTransactionItem; import java.util.List; -public class TransactionPagerAdapter extends FragmentPagerAdapter { +public class TransactionPagerAdapter extends FragmentStateAdapter { + private static final String TAG = TransactionPagerAdapter.class.getName(); - private List items; + private final List items; - public TransactionPagerAdapter(FragmentManager fm, List items) { - super(fm); + public TransactionPagerAdapter(@NonNull FragmentManager fragmentManager, @NonNull Lifecycle lifecycle, List items) { + super(fragmentManager, lifecycle); this.items = items; - - } - - public TransactionPagerAdapter(FragmentManager fm){ - super(fm); } + @NonNull @Override - public Fragment getItem(int pos) { - return FragmentTransactionItem.newInstance(items.get(pos)); + public Fragment createFragment(int position) { + return FragmentTransactionItem.newInstance(items.get(position)); } @Override - public int getCount() { + public int getItemCount() { return items == null ? 0 : items.size(); } - } diff --git a/app/src/main/java/com/breadwallet/tools/animation/BRAnimator.java b/app/src/main/java/com/breadwallet/tools/animation/BRAnimator.java index 5c9a3cffe..63ca496d1 100644 --- a/app/src/main/java/com/breadwallet/tools/animation/BRAnimator.java +++ b/app/src/main/java/com/breadwallet/tools/animation/BRAnimator.java @@ -11,6 +11,7 @@ import android.app.Activity; import android.app.FragmentManager; import android.app.FragmentTransaction; + import android.content.Intent; import android.content.pm.PackageManager; import android.os.Bundle; @@ -18,7 +19,6 @@ import android.view.ViewGroup; import android.view.animation.DecelerateInterpolator; import android.view.animation.OvershootInterpolator; -import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -29,11 +29,9 @@ import com.breadwallet.R; import com.breadwallet.presenter.activities.BreadActivity; import com.breadwallet.presenter.activities.LoginActivity; -import com.breadwallet.presenter.activities.camera.CameraActivity; import com.breadwallet.presenter.activities.camera.ScanQRActivity; import com.breadwallet.presenter.customviews.BRDialogView; import com.breadwallet.presenter.entities.TxItem; -import com.breadwallet.presenter.fragments.DynamicDonationFragment; import com.breadwallet.presenter.fragments.FragmentBalanceSeedReminder; import com.breadwallet.presenter.fragments.FragmentBuy; import com.breadwallet.presenter.fragments.FragmentGreetings; @@ -43,6 +41,7 @@ import com.breadwallet.presenter.fragments.FragmentSend; import com.breadwallet.presenter.fragments.FragmentSignal; import com.breadwallet.presenter.fragments.FragmentTransactionDetails; + import com.breadwallet.presenter.interfaces.BROnSignalCompletion; import com.breadwallet.tools.threads.BRExecutor; import com.breadwallet.tools.util.BRConstants; @@ -137,12 +136,14 @@ public static void popBackStackTillEntry(Activity app, int entryIndex) { } } - public static void showTransactionPager(Activity app, List items, int position) { + public static void showTransactionPager(FragmentActivity app, List items, int position) { if (app == null) { Timber.i("timber: showSendFragment: app is null"); return; } - FragmentTransactionDetails fragmentTransactionDetails = (FragmentTransactionDetails) app.getFragmentManager().findFragmentByTag(FragmentTransactionDetails.class.getName()); + FragmentTransactionDetails fragmentTransactionDetails = (FragmentTransactionDetails) app + .getSupportFragmentManager() + .findFragmentByTag(FragmentTransactionDetails.class.getName()); if (fragmentTransactionDetails != null && fragmentTransactionDetails.isAdded()) { fragmentTransactionDetails.setItems(items); Timber.i("timber: showTransactionPager: Already showing"); @@ -154,7 +155,8 @@ public static void showTransactionPager(Activity app, List items, int po bundle.putInt("pos", position); fragmentTransactionDetails.setArguments(bundle); - app.getFragmentManager().beginTransaction() + app.getSupportFragmentManager() + .beginTransaction() .setCustomAnimations(0, 0, 0, R.animator.plain_300) .add(android.R.id.content, fragmentTransactionDetails, FragmentTransactionDetails.class.getName()) .addToBackStack(FragmentTransactionDetails.class.getName()).commit(); @@ -274,14 +276,6 @@ public static void showBuyFragment(FragmentActivity app, String currency, Fragme .commit(); } - public static void showDynamicDonationFragment(@NonNull FragmentActivity app) { - app.getSupportFragmentManager().beginTransaction() - .setCustomAnimations(0, 0, 0, R.animator.plain_300) - .add(android.R.id.content, new DynamicDonationFragment(), DynamicDonationFragment.class.getName()) - .addToBackStack(DynamicDonationFragment.class.getName()) - .commit(); - } - public static void showMenuFragment(Activity app) { if (app == null) { Timber.i("timber: showReceiveFragment: app is null"); @@ -324,9 +318,11 @@ public void run() { } else return false; } - public static void killAllFragments(Activity app) { - if (app != null && !app.isDestroyed()) - app.getFragmentManager().popBackStack(null, FragmentManager.POP_BACK_STACK_INCLUSIVE); + public static void killAllFragments(FragmentActivity app) { + //DEV: Needs refactor + if (app != null && !app.isDestroyed() && !app.getSupportFragmentManager().isStateSaved()) { + app.getSupportFragmentManager().popBackStack(); + } } public static void startBreadIfNotStarted(Activity app) { diff --git a/app/src/main/java/com/breadwallet/tools/manager/BRApiManager.java b/app/src/main/java/com/breadwallet/tools/manager/BRApiManager.java index 34666fbd6..62dff5bb9 100644 --- a/app/src/main/java/com/breadwallet/tools/manager/BRApiManager.java +++ b/app/src/main/java/com/breadwallet/tools/manager/BRApiManager.java @@ -138,8 +138,9 @@ public static JSONArray fetchRates(Activity activity) { if (jsonString == null) return null; try { jsonArray = new JSONArray(jsonString); + // DEV Uncomment to view values + // Timber.d("timber: JSON %s",jsonArray.toString()); - Timber.d("timber: JSON %s",jsonArray.toString()); } catch (JSONException ex) { Timber.e(ex); } diff --git a/app/src/main/java/com/breadwallet/tools/manager/BREventManager.java b/app/src/main/java/com/breadwallet/tools/manager/BREventManager.java deleted file mode 100644 index a337aa117..000000000 --- a/app/src/main/java/com/breadwallet/tools/manager/BREventManager.java +++ /dev/null @@ -1,116 +0,0 @@ -package com.breadwallet.tools.manager; - -import android.content.Context; -import android.content.pm.PackageInfo; -import android.content.pm.PackageManager; - -import com.breadwallet.BreadApp; -import com.breadwallet.tools.util.BRConstants; -import com.breadwallet.tools.util.Utils; -import com.platform.APIClient; - -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; - -import java.io.File; -import java.io.FileInputStream; -import java.io.FileWriter; -import java.io.IOException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.UUID; - -import okhttp3.MediaType; -import okhttp3.Request; -import okhttp3.RequestBody; -import okhttp3.Response; -import timber.log.Timber; - -import static com.platform.APIClient.BASE_URL; - -public class BREventManager implements BreadApp.OnAppBackgrounded { - private static BREventManager instance; - private String sessionId; - private List events = new ArrayList<>(); - - private BREventManager() { - sessionId = UUID.randomUUID().toString(); - BreadApp.addOnBackgroundedListener(this); - } - - public static BREventManager getInstance() { - if (instance == null) instance = new BREventManager(); - return instance; - } - - public void pushEvent(String eventName, Map attributes) { - Timber.d("timber: pushEvent: %s", eventName); - Event event = new Event(sessionId, System.currentTimeMillis() * 1000, eventName, attributes); - events.add(event); - } - - public void pushEvent(String eventName) { - Timber.d("timber: pushEvent: %s", eventName); - Event event = new Event(sessionId, System.currentTimeMillis() * 1000, eventName, null); - events.add(event); - } - - @Override - public void onBackgrounded() { - Timber.d("timber: onBackgrounded: "); - } - - //returns the list of JSONArray which consist of Event arrays - private static List getEventsFromDisk(Context context) { - List result = new ArrayList<>(); - File dir = new File(context.getFilesDir().getAbsolutePath() + "/events/"); - if (dir.listFiles() == null) return result; - for (File f : dir.listFiles()) { - if (f.isFile()) { - String name = f.getName(); - Timber.d("timber: getEventsFromDisk: name:%s", name); - try { - JSONArray arr = new JSONArray(readFile(name)); - result.add(arr); - } catch (JSONException e) { - Timber.e(e); - } - } else { - Timber.i("timber: getEventsFromDisk: Unexpected directory where file is expected: %s", f.getName()); - } - } - return result; - } - - private static String readFile(String fileName) { - try { - File f = new File(fileName); - //check whether file exists - FileInputStream is = new FileInputStream(f); - int size = is.available(); - byte[] buffer = new byte[size]; - is.read(buffer); - is.close(); - return new String(buffer); - } catch (IOException e) { - Timber.e(e, "timber:Error in Reading"); - return null; - } - } - - public class Event { - public String sessionId; - public long time; - public String eventName; - public Map attributes; - - public Event(String sessionId, long time, String eventName, Map attributes) { - this.sessionId = sessionId; - this.time = time; - this.eventName = eventName; - this.attributes = attributes; - } - } -} diff --git a/app/src/main/java/com/breadwallet/tools/manager/BRSharedPrefs.java b/app/src/main/java/com/breadwallet/tools/manager/BRSharedPrefs.java index 6832c9320..17287dcb0 100644 --- a/app/src/main/java/com/breadwallet/tools/manager/BRSharedPrefs.java +++ b/app/src/main/java/com/breadwallet/tools/manager/BRSharedPrefs.java @@ -75,6 +75,40 @@ public static void notifyIsoChanged(String iso) { } } + ////////////////////////////////////////////////////////////////////////////// + //////////////////// Active Shared Preferences /////////////////////////////// + public static void putLastSyncTimestamp(Context activity, long time) { + SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong("lastSyncTime", time); + editor.apply(); + } + public static long getLastSyncTimestamp(Context activity) { + SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + return prefs.getLong("lastSyncTime", 0L); + } + public static void putStartSyncTimestamp(Context activity, long time) { + SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong("startSyncTime", time); + editor.apply(); + } + public static long getStartSyncTimestamp(Context activity) { + SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + return prefs.getLong("startSyncTime", 0L); + } + + public static void putSyncTimeElapsed(Context activity, long time) { + SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + SharedPreferences.Editor editor = prefs.edit(); + editor.putLong("syncTimeElapsed", time); + editor.apply(); + } + public static long getSyncTimeElapsed(Context activity) { + SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); + return prefs.getLong("syncTimeElapsed", 0L); + } + public static boolean getPhraseWroteDown(Context context) { SharedPreferences prefs = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); return prefs.getBoolean(BRConstants.PHRASE_WRITTEN, false); @@ -134,30 +168,6 @@ public static void putFirstAddress(Context context, String firstAddress) { editor.apply(); } - public static long getFeePerKb(Context context) { - SharedPreferences prefs = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - return prefs.getLong(BRConstants.FEE_KB_PREFS, 0); - } - - public static void putFeePerKb(Context context, long fee) { - SharedPreferences prefs = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(BRConstants.FEE_KB_PREFS, fee); - editor.apply(); - } - - public static long getEconomyFeePerKb(Context context) { - SharedPreferences prefs = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - return prefs.getLong(BRConstants.ECONOMY_FEE_KB_PREFS, 0); - } - - public static void putEconomyFeePerKb(Context context, long fee) { - SharedPreferences prefs = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong(BRConstants.ECONOMY_FEE_KB_PREFS, fee); - editor.apply(); - } - public static long getCatchedBalance(Context context) { SharedPreferences prefs = context.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); return prefs.getLong("balance", 0); @@ -183,13 +193,6 @@ public static void putSecureTime(Context activity, long date) { editor.apply(); } - public static void putLastSyncTime(Context activity, long time) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - editor.putLong("lastSyncTime", time); - editor.apply(); - } - public static long getFeeTime(Context activity) { SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); return prefs.getLong("feeTime", 0); @@ -202,32 +205,6 @@ public static void putFeeTime(Context activity, long feeTime) { editor.apply(); } - public static List getBitIdNonces(Context activity, String key) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - String result = prefs.getString(key, null); - List list = new ArrayList<>(); - try { - JSONArray arr = new JSONArray(result); - for (int i = 0; i < arr.length(); i++) { - int a = arr.getInt(i); - Timber.d("timber: found a nonce: %s", a); - list.add(a); - } - } catch (Exception e) { - Timber.e(e); - } - return list; - } - - public static void putBitIdNonces(Context activity, List nonces, String key) { - JSONArray arr = new JSONArray(); - arr.put(nonces); - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - editor.putString(key, arr.toString()); - editor.apply(); - } - public static boolean getAllowSpend(Context activity) { SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); return prefs.getBoolean(BRConstants.ALLOW_SPEND, true); @@ -268,27 +245,9 @@ public static void putUseFingerprint(Context activity, boolean use) { editor.putBoolean("useFingerprint", use); editor.apply(); } - - public static boolean getFeatureEnabled(Context activity, String feature) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - return prefs.getBoolean(feature, false); - } - - public static boolean getGeoPermissionsRequested(Context activity) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - return prefs.getBoolean(GEO_PERMISSIONS_REQUESTED, false); - } - - public static void putGeoPermissionsRequested(Context activity, boolean requested) { - SharedPreferences prefs = activity.getSharedPreferences(BRConstants.PREFS_NAME, Context.MODE_PRIVATE); - SharedPreferences.Editor editor = prefs.edit(); - editor.putBoolean(GEO_PERMISSIONS_REQUESTED, requested); - editor.apply(); - } - public static int getStartHeight(Context context) { SharedPreferences settingsToGet = context.getSharedPreferences(BRConstants.PREFS_NAME, 0); - return settingsToGet.getInt(BRConstants.START_HEIGHT, 0); + return settingsToGet.getInt(BRConstants.START_HEIGHT, 0); } public static void putStartHeight(Context context, int startHeight) { @@ -338,13 +297,6 @@ public static void putCurrencyUnit(Context context, int unit) { editor.apply(); } - public static String getDeviceId(Context context) { - SharedPreferences settingsToGet = context.getSharedPreferences(BRConstants.PREFS_NAME, 0); - String deviceId = settingsToGet.getString(BRConstants.USER_ID, ""); - if (deviceId.isEmpty()) setDeviceId(context, UUID.randomUUID().toString()); - return (settingsToGet.getString(BRConstants.USER_ID, "")); - } - private static void setDeviceId(Context context, String uuid) { if (context == null) return; SharedPreferences settings = context.getSharedPreferences(BRConstants.PREFS_NAME, 0); diff --git a/app/src/main/java/com/breadwallet/tools/manager/InternetManager.java b/app/src/main/java/com/breadwallet/tools/manager/InternetManager.java index 6598d7356..9955a4622 100644 --- a/app/src/main/java/com/breadwallet/tools/manager/InternetManager.java +++ b/app/src/main/java/com/breadwallet/tools/manager/InternetManager.java @@ -34,7 +34,6 @@ public static void addConnectionListener(ConnectionReceiverListener listener) { if (!connectionReceiverListeners.contains(listener)) connectionReceiverListeners.add(listener); } - @Override public void onReceive(final Context context, final Intent intent) { boolean connected = false; @@ -48,7 +47,6 @@ public void onReceive(final Context context, final Intent intent) { connected = false; } - BREventManager.getInstance().pushEvent(connected ? "reachability.isReachble" : "reachability.isNotReachable"); for (ConnectionReceiverListener listener : connectionReceiverListeners) { listener.onConnectionChanged(connected); } diff --git a/app/src/main/java/com/breadwallet/tools/manager/SyncManager.java b/app/src/main/java/com/breadwallet/tools/manager/SyncManager.java index 86150e8bd..ee536be6b 100644 --- a/app/src/main/java/com/breadwallet/tools/manager/SyncManager.java +++ b/app/src/main/java/com/breadwallet/tools/manager/SyncManager.java @@ -5,10 +5,12 @@ import android.content.Context; import android.content.Intent; import android.os.Build; +import android.os.Bundle; import com.breadwallet.R; import com.breadwallet.presenter.activities.BreadActivity; import com.breadwallet.tools.listeners.SyncReceiver; +import com.breadwallet.tools.util.BRConstants; import com.breadwallet.tools.util.Utils; import com.breadwallet.wallet.BRPeerManager; @@ -30,28 +32,11 @@ public static SyncManager getInstance() { private SyncManager() { } - private void createAlarm(Context app, long time) { - //Add another flag - AlarmManager alarmManager = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE); - Intent intent = new Intent(app, SyncReceiver.class); - intent.setAction(SyncReceiver.SYNC_RECEIVER);//my custom string action name - int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? - PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT; - PendingIntent pendingIntent = PendingIntent.getService(app, 1001, intent, flags); - alarmManager.setWindow(AlarmManager.RTC_WAKEUP, time, time + TimeUnit.MINUTES.toMillis(1), pendingIntent);//first start will start asap - } - - public synchronized void updateAlarms(Context app) { - createAlarm(app, System.currentTimeMillis() + SYNC_PERIOD); - } - - public synchronized void startSyncingProgressThread() { - Timber.d("timber: startSyncingProgressThread:%s", Thread.currentThread().getName()); - + public synchronized void startSyncingProgressThread(Context app) { try { if (syncTask != null) { if (running) { - Timber.d("timber: startSyncingProgressThread: syncTask.running == true, returning"); + updateStartSyncData(app); return; } syncTask.interrupt(); @@ -59,27 +44,86 @@ public synchronized void startSyncingProgressThread() { } syncTask = new SyncProgressTask(); syncTask.start(); + BRSharedPrefs.putStartSyncTimestamp(app, System.currentTimeMillis()); + BRSharedPrefs.putSyncTimeElapsed(app, 0L); + updateStartSyncData(app); } catch (IllegalThreadStateException ex) { Timber.e(ex); } } - public synchronized void stopSyncingProgressThread() { - Timber.d("timber: stopSyncingProgressThread"); - final BreadActivity ctx = BreadActivity.getApp(); - if (ctx == null) { - Timber.i("timber: stopSyncingProgressThread: ctx is null"); + private synchronized void updateStartSyncData(Context app) { + final double progress = BRPeerManager.syncProgress(BRSharedPrefs.getStartHeight(app)); + long startSync = BRSharedPrefs.getStartSyncTimestamp(app); + long lastSync = BRSharedPrefs.getLastSyncTimestamp(app); + long elapsed = BRSharedPrefs.getSyncTimeElapsed(app); + + if (elapsed > 0L) { + elapsed = (System.currentTimeMillis() - lastSync) + elapsed; + } + else { + elapsed = 1L; + } + BRSharedPrefs.putLastSyncTimestamp(app, System.currentTimeMillis()); + BRSharedPrefs.putSyncTimeElapsed(app, elapsed); + double minutesValue = ((double) elapsed / 1_000.0 / 60.0); + String minutesString = String.format( "%3.2f mins", minutesValue); + String millisecString = String.format( "%5d msec", elapsed); + Timber.d("timber: ||\nprogress: %s\nThread: %s\nrunning lastSyncingTime: %s\nelapsed: %s | %s", String.format( "%.2f", progress * 100.00),Thread.currentThread().getName(),String.valueOf(BRSharedPrefs.getLastSyncTimestamp(app)), millisecString, minutesString); + + } + + private synchronized void markFinishedSyncData(Context app) { + Timber.d("timber: || markFinish threadname:%s", Thread.currentThread().getName()); + final double progress = BRPeerManager.syncProgress(BRSharedPrefs.getStartHeight(app)); + long startSync = BRSharedPrefs.getStartSyncTimestamp(app); + long lastSync = BRSharedPrefs.getLastSyncTimestamp(app); + long elapsed = BRSharedPrefs.getSyncTimeElapsed(app); + double minutesValue = ((double) elapsed / 1_000.0 / 60.0); + String minutesString = String.format( "%3.2f mins", minutesValue); + String millisecString = String.format( "%5d msec", elapsed); + Timber.d("timber: ||\ncompletedprogress: %s\nstartSyncTime: %s\nlastSyncingTime: %s\ntotalTimeelapsed: %s | %s", String.format( "%.2f", progress * 100.00),String.valueOf(startSync),String.valueOf(lastSync), millisecString, minutesString); + + Bundle params = new Bundle(); + params.putDouble("sync_time_elapsed", minutesValue); + params.putLong("sync_start_timestamp", startSync); + params.putLong("sync_last_timestamp", lastSync); + AnalyticsManager.logCustomEventWithParams(BRConstants._20230407_DCS, params); + } + + public synchronized void stopSyncingProgressThread(Context app) { + + if (app == null) { + Timber.i("timber: || stopSyncingProgressThread: ctx is null"); return; } try { if (syncTask != null) { syncTask.interrupt(); syncTask = null; + markFinishedSyncData(app); } } catch (Exception ex) { Timber.e(ex); } } + + private void createAlarm(Context app, long time) { + //Add another flag + AlarmManager alarmManager = (AlarmManager) app.getSystemService(Context.ALARM_SERVICE); + Intent intent = new Intent(app, SyncReceiver.class); + intent.setAction(SyncReceiver.SYNC_RECEIVER);//my custom string action name + int flags = Build.VERSION.SDK_INT >= Build.VERSION_CODES.M ? + PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE : PendingIntent.FLAG_UPDATE_CURRENT; + PendingIntent pendingIntent = PendingIntent.getService(app, 1001, intent, flags); + alarmManager.setWindow(AlarmManager.RTC_WAKEUP, time, time + TimeUnit.MINUTES.toMillis(1), pendingIntent);//first start will start asap + } + + public synchronized void updateAlarms(Context app) { + createAlarm(app, System.currentTimeMillis() + SYNC_PERIOD); + } + + private class SyncProgressTask extends Thread { public double progressStatus = 0; private BreadActivity app; @@ -102,11 +146,11 @@ public void run() { app.runOnUiThread(new Runnable() { @Override public void run() { - if (TxManager.getInstance().syncingHolder != null) - TxManager.getInstance().syncingHolder.progress.setProgress((int) (progressStatus * 100)); - if (TxManager.getInstance().syncingHolder != null) { - TxManager.getInstance().syncingHolder.date.setText(Utils.formatTimeStamp(lastBlockTimeStamp, "MMM. dd, yyyy ha")); - TxManager.getInstance().syncingHolder.label.setText(BreadActivity.getApp().getString(R.string.SyncingView_header)); + if (TxManager.getInstance().syncingProgressViewHolder != null) + TxManager.getInstance().syncingProgressViewHolder.progress.setProgress((int) (progressStatus * 100)); + if (TxManager.getInstance().syncingProgressViewHolder != null) { + TxManager.getInstance().syncingProgressViewHolder.date.setText(Utils.formatTimeStamp(lastBlockTimeStamp, "MMM. dd, yyyy ha")); + TxManager.getInstance().syncingProgressViewHolder.label.setText(BreadActivity.getApp().getString(R.string.SyncingView_header)); } } }); @@ -121,6 +165,7 @@ public void run() { continue; } final long lastBlockTimeStamp = BRPeerManager.getInstance().getLastBlockTimestamp() * 1000; + final int currentBlockHeight = BRPeerManager.getCurrentBlockHeight(); app.runOnUiThread(new Runnable() { @Override public void run() { @@ -128,13 +173,12 @@ public void run() { Timber.d("timber: run: currentPrompt != SYNCING, showPrompt(SYNCING) ...."); TxManager.getInstance().showPrompt(app, PromptManager.PromptItem.SYNCING); } - if (TxManager.getInstance().syncingHolder != null) - TxManager.getInstance().syncingHolder.progress.setProgress((int) (progressStatus * 100)); - if (TxManager.getInstance().syncingHolder != null) { - TxManager.getInstance().syncingHolder.date.setText(Utils.formatTimeStamp(lastBlockTimeStamp, "MMM. dd, yyyy ha")); - TxManager.getInstance().syncingHolder.label.setText(BreadActivity.getApp().getString(R.string.SyncingView_header)); + if (TxManager.getInstance().syncingProgressViewHolder != null) { + TxManager.getInstance().syncingProgressViewHolder.progress.setProgress((int) (progressStatus * 100)); + TxManager.getInstance().syncingProgressViewHolder.date.setText(Utils.formatTimeStamp(lastBlockTimeStamp, "MMM. dd, yyyy ha")); + String progressString = String.format("%3.2f%%", progressStatus * 100); + TxManager.getInstance().syncingProgressViewHolder.label.setText(String.format("%s %s - %d",BreadActivity.getApp().getString(R.string.SyncingView_header),progressString, currentBlockHeight)); } - } }); @@ -142,10 +186,15 @@ public void run() { app = BreadActivity.getApp(); } + ///DEV kcw-grunt 26-10-24 + /// DUMB sleep was slowing sync dramatically + /// Why is this here? + /// Reduced it from 500msec to 100msec until refactor + /// Poor control flow, loop should continue for the next task try { - Thread.sleep(500); + Thread.sleep(100); } catch (InterruptedException e) { - Timber.e(e, "timber:run: Thread.sleep was Interrupted:%s", Thread.currentThread().getName()); + Timber.e(e, "timber:run: SyncManager.run Thread.sleep was Interrupted:%s", Thread.currentThread().getName()); } } Timber.d("timber: run: SyncProgress task finished:%s", Thread.currentThread().getName()); @@ -156,7 +205,7 @@ public void run() { app.runOnUiThread(new Runnable() { @Override public void run() { - TxManager.getInstance().hidePrompt(app, PromptManager.PromptItem.SYNCING); + TxManager.getInstance().hidePrompt(app, PromptManager.PromptItem.SYNCING); } }); } diff --git a/app/src/main/java/com/breadwallet/tools/manager/TxManager.java b/app/src/main/java/com/breadwallet/tools/manager/TxManager.java index ebf2fcfd8..942e8be5c 100644 --- a/app/src/main/java/com/breadwallet/tools/manager/TxManager.java +++ b/app/src/main/java/com/breadwallet/tools/manager/TxManager.java @@ -4,6 +4,7 @@ import android.animation.AnimatorListenerAdapter; import android.app.Activity; import android.content.Context; +import android.os.Bundle; import android.os.Looper; import android.util.AttributeSet; import android.view.View; @@ -20,6 +21,7 @@ import com.breadwallet.tools.animation.BRAnimator; import com.breadwallet.tools.listeners.RecyclerItemClickListener; import com.breadwallet.tools.threads.BRExecutor; +import com.breadwallet.tools.util.BRConstants; import com.breadwallet.wallet.BRPeerManager; import com.breadwallet.wallet.BRWalletManager; @@ -36,7 +38,7 @@ public class TxManager { public TransactionListAdapter adapter; public PromptManager.PromptItem currentPrompt; public PromptManager.PromptInfo promptInfo; - public TransactionListAdapter.SyncingHolder syncingHolder; + public TransactionListAdapter.SyncingProgressViewHolder syncingProgressViewHolder; public static TxManager getInstance() { if (instance == null) instance = new TxManager(); @@ -64,7 +66,6 @@ public void onAnimationEnd(Animator animation) { }); } else { //clicked on the prompt - BREventManager.getInstance().pushEvent("prompt." + PromptManager.getInstance().getPromptName(currentPrompt) + ".trigger"); if (currentPrompt != PromptManager.PromptItem.SYNCING) { PromptManager.PromptInfo info = PromptManager.getInstance().promptInfo(app, currentPrompt); if (info != null) @@ -114,7 +115,6 @@ public void run() { void showPrompt(Activity app, PromptManager.PromptItem item) { crashIfNotMain(); if (item == null) throw new RuntimeException("can't be null"); - BREventManager.getInstance().pushEvent("prompt." + PromptManager.getInstance().getPromptName(item) + ".displayed"); if (currentPrompt != PromptManager.PromptItem.SYNCING) { currentPrompt = item; } @@ -132,9 +132,6 @@ void hidePrompt(final Activity app, final PromptManager.PromptItem item) { if (item == PromptManager.PromptItem.SYNCING) { showNextPrompt(app); updateCard(app); - } else { - if (item != null) - BREventManager.getInstance().pushEvent("prompt." + PromptManager.getInstance().getPromptName(item) + ".dismissed"); } } diff --git a/app/src/main/java/com/breadwallet/tools/security/AuthManager.java b/app/src/main/java/com/breadwallet/tools/security/AuthManager.java index 3d0d2ac28..a81a58e5c 100644 --- a/app/src/main/java/com/breadwallet/tools/security/AuthManager.java +++ b/app/src/main/java/com/breadwallet/tools/security/AuthManager.java @@ -1,7 +1,6 @@ package com.breadwallet.tools.security; import android.app.Activity; -import android.app.FragmentTransaction; import android.app.KeyguardManager; import android.content.Context; import android.content.DialogInterface; @@ -9,6 +8,9 @@ import android.os.Handler; import android.view.View; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentTransaction; + import com.breadwallet.R; import com.breadwallet.presenter.activities.DisabledActivity; import com.breadwallet.presenter.activities.util.ActivityUTILS; @@ -197,7 +199,7 @@ public void authPrompt(final Context context, String title, String message, bool if (forcePin) useFingerPrint = false; - final Activity app = (Activity) context; + final FragmentActivity app = (FragmentActivity) context; FragmentFingerprint fingerprintFragment; FragmentPin breadPin; @@ -210,7 +212,7 @@ public void authPrompt(final Context context, String title, String message, bool args.putString("message", message); fingerprintFragment.setArguments(args); fingerprintFragment.setCompletion(completion); - FragmentTransaction transaction = app.getFragmentManager().beginTransaction(); + androidx.fragment.app.FragmentTransaction transaction = app.getSupportFragmentManager().beginTransaction(); transaction.setCustomAnimations(0, 0, 0, R.animator.plain_300); transaction.add(android.R.id.content, fingerprintFragment, FragmentFingerprint.class.getName()); transaction.addToBackStack(null); @@ -223,7 +225,7 @@ public void authPrompt(final Context context, String title, String message, bool args.putString("message", message); breadPin.setArguments(args); breadPin.setCompletion(completion); - FragmentTransaction transaction = app.getFragmentManager().beginTransaction(); + FragmentTransaction transaction = app.getSupportFragmentManager().beginTransaction(); transaction.setCustomAnimations(0, 0, 0, R.animator.plain_300); transaction.add(android.R.id.content, breadPin, breadPin.getClass().getName()); transaction.addToBackStack(null); diff --git a/app/src/main/java/com/breadwallet/tools/security/BRKeyStore.java b/app/src/main/java/com/breadwallet/tools/security/BRKeyStore.java index ceb33ed9b..c8c7a7fcc 100644 --- a/app/src/main/java/com/breadwallet/tools/security/BRKeyStore.java +++ b/app/src/main/java/com/breadwallet/tools/security/BRKeyStore.java @@ -20,11 +20,9 @@ import com.breadwallet.R; import com.breadwallet.exceptions.BRKeystoreErrorException; import com.breadwallet.presenter.customviews.BRDialogView; -import com.breadwallet.tools.animation.BRAnimator; import com.breadwallet.tools.animation.BRDialog; import com.breadwallet.tools.manager.BRSharedPrefs; import com.breadwallet.tools.threads.BRExecutor; -import com.breadwallet.tools.util.BRConstants; import com.breadwallet.tools.util.BytesUtil; import com.breadwallet.tools.util.TypesConverter; import com.breadwallet.tools.util.Utils; @@ -333,7 +331,8 @@ private synchronized static byte[] _getData(final Context context, String alias, Timber.e(e); throw new RuntimeException(e.getMessage()); } - } catch (UnrecoverableKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException e) { + } catch (UnrecoverableKeyException | NoSuchAlgorithmException | NoSuchPaddingException | + InvalidAlgorithmParameterException e) { /** if for any other reason the keystore fails, crash! */ Timber.e(e, "timber:getData: error"); throw new RuntimeException(e.getMessage()); @@ -715,8 +714,15 @@ public synchronized static boolean resetWalletKeyStore(Context context) { public synchronized static void removeAliasAndFiles(KeyStore keyStore, String alias, Context context) { try { keyStore.deleteEntry(alias); - boolean b1 = new File(getFilePath(aliasObjectMap.get(alias).datafileName, context)).delete(); - boolean b2 = new File(getFilePath(aliasObjectMap.get(alias).ivFileName, context)).delete(); + + AliasObject aliasObject = aliasObjectMap.get(alias); + if (aliasObject == null) { + Timber.w("aliasObject for alias: %s is null, skipping deletion", alias); + return; + } + + boolean b1 = new File(getFilePath(aliasObject.datafileName, context)).delete(); + boolean b2 = new File(getFilePath(aliasObject.ivFileName, context)).delete(); } catch (KeyStoreException e) { Timber.e(e); } @@ -930,7 +936,8 @@ public synchronized static byte[] _getOldData(final Context context, String alia /** keyStore.load(null) threw the Exception, meaning the keystore is unavailable */ Timber.e(e, "timber:_getOldData: keyStore.load(null) threw the Exception, meaning the keystore is unavailable"); return null; - } catch (UnrecoverableKeyException | NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException e) { + } catch (UnrecoverableKeyException | NoSuchAlgorithmException | NoSuchPaddingException | + InvalidAlgorithmParameterException e) { /** if for any other reason the keystore fails, crash! */ Timber.e(e, "timber:getData: error"); return null; diff --git a/app/src/main/java/com/breadwallet/tools/security/BRSender.java b/app/src/main/java/com/breadwallet/tools/security/BRSender.java index 56736968c..be4af0f36 100644 --- a/app/src/main/java/com/breadwallet/tools/security/BRSender.java +++ b/app/src/main/java/com/breadwallet/tools/security/BRSender.java @@ -3,6 +3,8 @@ import android.app.Activity; import android.content.Context; +import androidx.fragment.app.FragmentActivity; + import com.breadwallet.R; import com.breadwallet.presenter.customviews.BRDialogView; import com.breadwallet.presenter.entities.TransactionItem; @@ -87,7 +89,7 @@ public void run() { long minAmount = BRWalletManager.getInstance().getMinOutputAmountRequested(); errTitle[0] = app.getString(R.string.Alerts_sendFailure); errMessage[0] = String.format(Locale.getDefault(), app.getString(R.string.PaymentProtocol_Errors_smallPayment), - BRConstants.bitcoinLowercase + new BigDecimal(minAmount).divide(new BigDecimal(100), BRConstants.ROUNDING_MODE)); + BRConstants.litecoinLowercase + new BigDecimal(minAmount).divide(new BigDecimal(100), BRConstants.ROUNDING_MODE)); } catch (SpendingNotAllowed spendingNotAllowed) { showSpendNotAllowed(app); return; @@ -254,7 +256,7 @@ private void confirmPay(final Context ctx, final TransactionItem transactionItem //amount can't be less than the min if (transactionItem.sendAmount < minOutput) { final String bitcoinMinMessage = String.format(Locale.getDefault(), ctx.getString(R.string.PaymentProtocol_Errors_smallTransaction), - BRConstants.bitcoinLowercase + new BigDecimal(minOutput).divide(new BigDecimal("100"))); + BRConstants.litecoinLowercase + new BigDecimal(minOutput).divide(new BigDecimal("100"))); ((Activity) ctx).runOnUiThread(new Runnable() { @Override @@ -289,7 +291,7 @@ public void run() { BRExecutor.getInstance().forMainThreadTasks().execute(new Runnable() { @Override public void run() { - BRAnimator.killAllFragments((Activity) ctx); + BRAnimator.killAllFragments((FragmentActivity) ctx); BRAnimator.startBreadIfNotStarted((Activity) ctx); } }); diff --git a/app/src/main/java/com/breadwallet/tools/security/BitcoinUrlHandler.java b/app/src/main/java/com/breadwallet/tools/security/BitcoinUrlHandler.java index 597daee07..b4ce90008 100644 --- a/app/src/main/java/com/breadwallet/tools/security/BitcoinUrlHandler.java +++ b/app/src/main/java/com/breadwallet/tools/security/BitcoinUrlHandler.java @@ -10,7 +10,6 @@ import com.breadwallet.presenter.entities.RequestObject; import com.breadwallet.tools.animation.BRAnimator; import com.breadwallet.tools.animation.BRDialog; -import com.breadwallet.tools.manager.BREventManager; import com.breadwallet.tools.threads.PaymentProtocolTask; import com.breadwallet.tools.util.Utils; import com.breadwallet.wallet.BRWalletManager; @@ -35,18 +34,6 @@ public static synchronized boolean processRequest(FragmentActivity app, String u return false; } - Map attr = new HashMap<>(); - URI uri = null; - try { - uri = new URI(url); - } catch (URISyntaxException e) { - Timber.e(e); - } - attr.put("scheme", uri == null ? "null" : uri.getScheme()); - attr.put("host", uri == null ? "null" : uri.getHost()); - attr.put("path", uri == null ? "null" : uri.getPath()); - BREventManager.getInstance().pushEvent("send.handleURL", attr); - RequestObject requestObject = getRequestFromString(url); if (BRWalletManager.getInstance().confirmSweep(app, url)) { return true; diff --git a/app/src/main/java/com/breadwallet/tools/threads/PaymentProtocolTask.java b/app/src/main/java/com/breadwallet/tools/threads/PaymentProtocolTask.java index cc1d4abdd..8664a8ca3 100644 --- a/app/src/main/java/com/breadwallet/tools/threads/PaymentProtocolTask.java +++ b/app/src/main/java/com/breadwallet/tools/threads/PaymentProtocolTask.java @@ -293,7 +293,7 @@ public void run() { double minOutput = BRWalletManager.getInstance().getMinOutputAmount(); if (paymentRequest.amount < minOutput) { final String bitcoinMinMessage = String.format(Locale.getDefault(), app.getString(R.string.PaymentProtocol_Errors_smallTransaction), - BRConstants.bitcoinLowercase + new BigDecimal(minOutput).divide(new BigDecimal("100"))); + BRConstants.litecoinLowercase + new BigDecimal(minOutput).divide(new BigDecimal("100"))); app.runOnUiThread(new Runnable() { @Override public void run() { diff --git a/app/src/main/java/com/breadwallet/tools/util/BRConstants.java b/app/src/main/java/com/breadwallet/tools/util/BRConstants.java index a931d1da6..8699519a9 100644 --- a/app/src/main/java/com/breadwallet/tools/util/BRConstants.java +++ b/app/src/main/java/com/breadwallet/tools/util/BRConstants.java @@ -78,8 +78,8 @@ private BRConstants() { public static final int CURRENT_UNIT_LITES = 1; // formerly CURRENT_UNIT_MBITS public static final int CURRENT_UNIT_LITECOINS = 2; - public static final String bitcoinLowercase = "\u0142"; - public static final String bitcoinUppercase = "\u0141"; + public static final String litecoinLowercase = "\u0142"; + public static final String litecoinUppercase = "\u0141"; public static boolean PLATFORM_ON = true; public static final RoundingMode ROUNDING_MODE = RoundingMode.HALF_EVEN; @@ -87,20 +87,12 @@ private BRConstants() { public static final String loopBug = "android-loop-bug"; - /** - * Donation - */ - public static final String DONATION_ADDRESS = "MJ4W7NZya4SzE7R6xpEVdamGCimaQYPiWu"; - public static final long DONATION_AMOUNT = 1_400_000; - /** * App External URLs */ public static final String TWITTER_LINK = "https://twitter.com/Litewallet_App"; - public static final String INSTAGRAM_LINK = "https://www.instagram.com/litewallet.app"; - public static final String WEB_LINK = "https://litewallet.io"; public static final String TOS_LINK = "https://litewallet.io/privacy"; public static String CUSTOMER_SUPPORT_LINK = "https://support.litewallet.io/hc/en-us/requests/new"; @@ -125,14 +117,12 @@ private BRConstants() { public static final String _20200111_WNI = "wallet_not_initialized"; public static final String _20200111_PNI = "phrase_not_initialized"; public static final String _20200111_UTST = "unable_to_sign_transaction"; - public static final String _20200112_ERR = "error"; + public static final String _20200112_ERR = "lwa_error"; public static final String _20200112_DSR = "did_start_resync"; public static final String _20200125_DSRR = "did_show_review_request"; public static final String _20201118_DTGS = "did_tap_get_support"; public static final String _20200217_DUWP = "did_unlock_with_pin"; public static final String _20200217_DUWB = "did_unlock_with_biometrics"; - public static final String _20200223_DD = "did_donate"; - public static final String _20200225_DCD = "did_cancel_donate"; public static final String _20200301_DUDFPK = "did_use_default_fee_per_kb"; public static final String _20201121_SIL = "started_IFPS_lookup"; public static final String _20201121_DRIA = "did_resolve_IPFS_address"; @@ -169,8 +159,6 @@ private BRConstants() { _20201118_DTGS, _20200217_DUWP, _20200217_DUWB, - _20200223_DD, - _20200225_DCD, _20200301_DUDFPK, _20201121_SIL, _20201121_DRIA, diff --git a/app/src/main/java/com/breadwallet/tools/util/BRCurrency.java b/app/src/main/java/com/breadwallet/tools/util/BRCurrency.java index 25b858e75..9d42bccaf 100644 --- a/app/src/main/java/com/breadwallet/tools/util/BRCurrency.java +++ b/app/src/main/java/com/breadwallet/tools/util/BRCurrency.java @@ -48,18 +48,18 @@ public static String getFormattedCurrencyString(Context app, String isoCurrencyC public static String getSymbolByIso(Context app, String iso) { String symbol; if (Objects.equals(iso, "LTC")) { - String currencySymbolString = BRConstants.bitcoinLowercase; + String currencySymbolString = BRConstants.litecoinLowercase; if (app != null) { int unit = BRSharedPrefs.getCurrencyUnit(app); switch (unit) { case CURRENT_UNIT_PHOTONS: - currencySymbolString = BRConstants.bitcoinLowercase; + currencySymbolString = BRConstants.litecoinLowercase; break; case BRConstants.CURRENT_UNIT_LITES: - currencySymbolString = "m" + BRConstants.bitcoinUppercase; + currencySymbolString = "m" + BRConstants.litecoinUppercase; break; case BRConstants.CURRENT_UNIT_LITECOINS: - currencySymbolString = BRConstants.bitcoinUppercase; + currencySymbolString = BRConstants.litecoinUppercase; break; } } diff --git a/app/src/main/java/com/breadwallet/tools/util/BRExchange.java b/app/src/main/java/com/breadwallet/tools/util/BRExchange.java index 7c892ece9..60fa4977a 100644 --- a/app/src/main/java/com/breadwallet/tools/util/BRExchange.java +++ b/app/src/main/java/com/breadwallet/tools/util/BRExchange.java @@ -76,18 +76,18 @@ public static BigDecimal getAmountFromLitoshis(Context app, String iso, BigDecim } public static String getLitecoinSymbol(Context app) { - String currencySymbolString = BRConstants.bitcoinLowercase; + String currencySymbolString = BRConstants.litecoinLowercase; if (app != null) { int unit = BRSharedPrefs.getCurrencyUnit(app); switch (unit) { case CURRENT_UNIT_PHOTONS: - currencySymbolString = "m" + BRConstants.bitcoinLowercase; + currencySymbolString = "m" + BRConstants.litecoinLowercase; break; case BRConstants.CURRENT_UNIT_LITES: - currencySymbolString = BRConstants.bitcoinLowercase; + currencySymbolString = BRConstants.litecoinLowercase; break; case BRConstants.CURRENT_UNIT_LITECOINS: - currencySymbolString = BRConstants.bitcoinUppercase; + currencySymbolString = BRConstants.litecoinUppercase; break; } } diff --git a/app/src/main/java/com/breadwallet/tools/util/PushNotificationService.kt b/app/src/main/java/com/breadwallet/tools/util/PushNotificationService.kt new file mode 100644 index 000000000..58cceee56 --- /dev/null +++ b/app/src/main/java/com/breadwallet/tools/util/PushNotificationService.kt @@ -0,0 +1,34 @@ +package com.breadwallet.tools.util +import android.R +import android.app.Notification +import android.app.NotificationManager +import android.app.PendingIntent +import android.content.Intent +import android.media.RingtoneManager +import android.util.Log +import androidx.core.app.NotificationCompat +import com.google.android.gms.tasks.OnCompleteListener +import com.google.firebase.messaging.FirebaseMessaging +import com.google.firebase.messaging.FirebaseMessagingService +import com.google.firebase.messaging.RemoteMessage + + +class PushNotificationService : FirebaseMessagingService() { + override fun onNewToken(token: String) { + Log.d("push", "Refreshed token: $token") + sendRegistrationToServer(token) + } + + private fun sendRegistrationToServer(token: String) { + FirebaseMessaging.getInstance().token.addOnCompleteListener(OnCompleteListener { task -> + if (!task.isSuccessful) { + Log.w("fetch token", "Fetching FCM registration token failed", task.exception) + return@OnCompleteListener + } + + // Get new FCM registration token + val token = task.result + + }) + } +} diff --git a/app/src/main/java/com/breadwallet/tools/util/Utils.java b/app/src/main/java/com/breadwallet/tools/util/Utils.java index 4018fb757..4c7087547 100644 --- a/app/src/main/java/com/breadwallet/tools/util/Utils.java +++ b/app/src/main/java/com/breadwallet/tools/util/Utils.java @@ -255,7 +255,7 @@ public static String fetchPartnerKey(Context app, PartnerNames name) { else if (name == PartnerNames.OPSALL) { JSONArray opsArray = new JSONArray(keyObject.get(name.getKey()).toString()); - if (opsArray != null) { + if (opsArray.length() > 0) { for (int i=0;i lastHeight) BRSharedPrefs.putStartHeight(ctx, lastHeight); - SyncManager.getInstance().startSyncingProgressThread(); + SyncManager.getInstance().startSyncingProgressThread(ctx); } public static void syncSucceeded() { - syncCompletedDate = new Date().getTime(); - Timber.d("timber: sync started(unix epoch ms): %s, completed(unix epoch ms): %s", syncStartDate, syncCompletedDate); - final Context app = BreadApp.getBreadContext(); - if (app == null) return; - BRSharedPrefs.putLastSyncTime(app, System.currentTimeMillis()); - SyncManager.getInstance().updateAlarms(app); - BRSharedPrefs.putAllowSpend(app, true); - SyncManager.getInstance().stopSyncingProgressThread(); - - long syncTimeElapsed = abs(syncCompletedDate - syncStartDate) / 1000; - float userFalsePositiveRate = BRSharedPrefs.getFalsePositivesRate(app); - Timber.d("timber: syncTimeElapsed duration (seconds): %s", syncTimeElapsed); - - /// Need to filter partial syncs to properly track averages - /// this will filter out any syncs from 19 minutes to 120 minutes - /// The assumption is daily normal syncs are not problematic and quick - /// and any syncs past 120 minutes are errorneous in terms of data collection and testing - if (syncTimeElapsed > 19 * 60 && syncTimeElapsed > 120 * 60 ) { - Bundle params = new Bundle(); - params.putLong("sync_time_elapsed", syncTimeElapsed); - params.putFloat("user_preferred_fprate", userFalsePositiveRate); - AnalyticsManager.logCustomEventWithParams(BRConstants._20230407_DCS, params); - } + Context ctx = BreadApp.getBreadContext(); + if (ctx == null) return; + BRSharedPrefs.putLastSyncTimestamp(ctx, System.currentTimeMillis()); + SyncManager.getInstance().updateAlarms(ctx); + BRSharedPrefs.putAllowSpend(ctx, true); + SyncManager.getInstance().stopSyncingProgressThread(ctx); BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { @Override public void run() { - BRSharedPrefs.putStartHeight(app, getCurrentBlockHeight()); + BRSharedPrefs.putStartHeight(ctx, getCurrentBlockHeight()); } }); if (onSyncFinished != null) onSyncFinished.onFinished(); } public static void syncFailed() { - Timber.d("timber: syncFailed"); - SyncManager.getInstance().stopSyncingProgressThread(); Context ctx = BreadApp.getBreadContext(); + SyncManager.getInstance().stopSyncingProgressThread(ctx); + if (ctx == null) return; Timber.d("timber: Network Not Available, showing not connected bar"); - SyncManager.getInstance().stopSyncingProgressThread(); + SyncManager.getInstance().stopSyncingProgressThread(ctx); if (onSyncFinished != null) onSyncFinished.onFinished(); } diff --git a/app/src/main/java/com/breadwallet/wallet/BRWalletManager.java b/app/src/main/java/com/breadwallet/wallet/BRWalletManager.java index a07f3d1f5..914edbf33 100644 --- a/app/src/main/java/com/breadwallet/wallet/BRWalletManager.java +++ b/app/src/main/java/com/breadwallet/wallet/BRWalletManager.java @@ -370,7 +370,9 @@ public static void onBalanceChanged(final long balance) { } public static void onTxAdded(byte[] tx, int blockHeight, long timestamp, final long amount, String hash) { - Timber.d("timber: onTxAdded: " + String.format("tx.length: %d, blockHeight: %d, timestamp: %d, amount: %d, hash: %s", tx.length, blockHeight, timestamp, amount, hash)); + + // DEV Uncomment to see values + // Timber.d("timber: onTxAdded: tx.length: %d, blockHeight: %d, timestamp: %d, amount: %d, hash: %s", tx.length, blockHeight, timestamp, amount, hash)); final Context ctx = BreadApp.getBreadContext(); if (amount > 0) { @@ -424,8 +426,10 @@ public void run() { } public static void onTxUpdated(String hash, int blockHeight, int timeStamp) { - Timber.d("timber: onTxUpdated: " + String.format("hash: %s, blockHeight: %d, timestamp: %d", hash, blockHeight, timeStamp)); - Context ctx = BreadApp.getBreadContext(); + // DEV Uncomment to see values + // Timber.d("timber: onTxUpdated: " + String.format("hash: %s, blockHeight: %d, timestamp: %d", hash, blockHeight, timeStamp)); + Context ctx; + ctx = BreadApp.getBreadContext(); if (ctx != null) { TransactionDataSource.getInstance(ctx).updateTxBlockHeight(hash, blockHeight, timeStamp); @@ -435,7 +439,8 @@ public static void onTxUpdated(String hash, int blockHeight, int timeStamp) { } public static void onTxDeleted(String hash, int notifyUser, final int recommendRescan) { - Timber.d("timber: onTxDeleted: " + String.format("hash: %s, notifyUser: %d, recommendRescan: %d", hash, notifyUser, recommendRescan)); + // DEV Uncomment to see values + // Timber.d("timber: onTxDeleted: " + String.format("hash: %s, notifyUser: %d, recommendRescan: %d", hash, notifyUser, recommendRescan)); final Context ctx = BreadApp.getBreadContext(); if (ctx != null) { BRSharedPrefs.putScanRecommended(ctx, true); @@ -444,7 +449,6 @@ public static void onTxDeleted(String hash, int notifyUser, final int recommendR } } - public void startTheWalletIfExists(final Activity app) { final BRWalletManager m = BRWalletManager.getInstance(); if (!m.isPasscodeEnabled(app)) { diff --git a/app/src/main/java/com/platform/APIClient.java b/app/src/main/java/com/platform/APIClient.java index 620698567..5b032fa84 100644 --- a/app/src/main/java/com/platform/APIClient.java +++ b/app/src/main/java/com/platform/APIClient.java @@ -114,7 +114,10 @@ public Response sendRequest(Request locRequest, boolean needsAuth, int retryCoun byte[] data = new byte[0]; try { OkHttpClient client = new OkHttpClient.Builder().followRedirects(false).connectTimeout(60, TimeUnit.SECONDS)/*.addInterceptor(new LoggingInterceptor())*/.build(); - Timber.d("timber: sendRequest: headers for : %s \n %s", request.url(), request.headers()); + + // DEV Uncomment to see values + // Timber.d("timber: sendRequest: headers for : %s \n %s", request.url(), request.headers()); + String agent = Utils.getAgentString(ctx, "OkHttp/3.4.1"); request = request.newBuilder().header("User-agent", agent).build(); response = client.newCall(request).execute(); @@ -148,16 +151,20 @@ public Response sendRequest(Request locRequest, boolean needsAuth, int retryCoun byte[] decompressed = gZipExtract(data); postReqBody = ResponseBody.create(null, decompressed); try { - Timber.d("timber: sendRequest: (%s)%s, code (%d), mess (%s), body (%s)", request.method(), - request.url(), response.code(), response.message(), new String(decompressed, "utf-8")); + if (response.code() != 200) { + Timber.d("timber: sendRequest: (%s)%s, code (%d), mess (%s), body (%s)", request.method(), + request.url(), response.code(), response.message(), new String(decompressed, "utf-8")); + } } catch (UnsupportedEncodingException e) { Timber.e(e); } return response.newBuilder().body(postReqBody).build(); } else { try { - Timber.d("timber: sendRequest: (%s)%s, code (%d), mess (%s), body (%s)", request.method(), - request.url(), response.code(), response.message(), new String(data, "utf-8")); + if (response.code() != 200) { + Timber.d("timber: sendRequest: (%s)%s, code (%d), mess (%s), body (%s)", request.method(), + request.url(), response.code(), response.message(), new String(data, "utf-8")); + } } catch (UnsupportedEncodingException e) { Timber.e(e); } diff --git a/app/src/main/java/com/platform/tools/KVStoreManager.java b/app/src/main/java/com/platform/tools/KVStoreManager.java index 61866336f..323720d87 100644 --- a/app/src/main/java/com/platform/tools/KVStoreManager.java +++ b/app/src/main/java/com/platform/tools/KVStoreManager.java @@ -48,9 +48,10 @@ public TxMetaData getTxMetaData(Context app, byte[] txHash) { RemoteKVStore remoteKVStore = RemoteKVStore.getInstance(APIClient.getInstance(app)); ReplicatedKVStore kvStore = ReplicatedKVStore.getInstance(app, remoteKVStore); long ver = kvStore.localVersion(key).version; - Timber.d("timber: remoteKVStore: %s",remoteKVStore.toString()); - Timber.d("timber: ReplicatedKVStore: %s",kvStore.toString()); - Timber.d("timber: ver: %s",ver); + // DEV Uncomment to see values + // Timber.d("timber: remoteKVStore: %s",remoteKVStore.toString()); + // Timber.d("timber: ReplicatedKVStore: %s",kvStore.toString()); + // Timber.d("timber: ver: %s",ver); CompletionObject obj = kvStore.get(key, ver); diff --git a/app/src/main/res/drawable/litewallet_fin_icon_200.jpg b/app/src/main/res/drawable/litewallet_fin_icon_200.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a0d2a29f012a83a4bc032a6d4dbe7338d690cea6 GIT binary patch literal 12328 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 X9XKg&@$9qk$wLI;>;bEGx^VkH@v}-! literal 0 HcmV?d00001 diff --git a/app/src/main/res/layout/fragment_dynamic_donation.xml b/app/src/main/res/layout/fragment_dynamic_donation.xml deleted file mode 100644 index 6a382c0eb..000000000 --- a/app/src/main/res/layout/fragment_dynamic_donation.xml +++ /dev/null @@ -1,266 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_send.xml b/app/src/main/res/layout/fragment_send.xml index 39922b3e6..68ccce021 100644 --- a/app/src/main/res/layout/fragment_send.xml +++ b/app/src/main/res/layout/fragment_send.xml @@ -341,32 +341,6 @@ android:background="@color/extra_light_gray" /> - - - - - - diff --git a/app/src/test/resources/es-BIP39Words.txt b/app/src/test/resources/es-BIP39Words.txt deleted file mode 100644 index 12557a260..000000000 --- a/app/src/test/resources/es-BIP39Words.txt +++ /dev/null @@ -1,2048 +0,0 @@ -ábaco -abdomen -abeja -abierto -abogado -abono -aborto -abrazo -abrir -abuelo -abuso -acabar -academia -acceso -acción -aceite -acelga -acento -aceptar -ácido -aclarar -acné -acoger -acoso -activo -acto -actriz -actuar -acudir -acuerdo -acusar -adicto -admitir -adoptar -adorno -aduana -adulto -aéreo -afectar -afición -afinar -afirmar -ágil -agitar -agonía -agosto -agotar -agregar -agrio -agua -agudo -águila -aguja -ahogo -ahorro -aire -aislar -ajedrez -ajeno -ajuste -alacrán -alambre -alarma -alba -álbum -alcalde -aldea -alegre -alejar -alerta -aleta -alfiler -alga -algodón -aliado -aliento -alivio -alma -almeja -almíbar -altar -alteza -altivo -alto -altura -alumno -alzar -amable -amante -amapola -amargo -amasar -ámbar -ámbito -ameno -amigo -amistad -amor -amparo -amplio -ancho -anciano -ancla -andar -andén -anemia -ángulo -anillo -ánimo -anís -anotar -antena -antiguo -antojo -anual -anular -anuncio -añadir -añejo -año -apagar -aparato -apetito -apio -aplicar -apodo -aporte -apoyo -aprender -aprobar -apuesta -apuro -arado -araña -arar -árbitro -árbol -arbusto -archivo -arco -arder -ardilla -arduo -área -árido -aries -armonía -arnés -aroma -arpa -arpón -arreglo -arroz -arruga -arte -artista -asa -asado -asalto -ascenso -asegurar -aseo -asesor -asiento -asilo -asistir -asno -asombro -áspero -astilla -astro -astuto -asumir -asunto -atajo -ataque -atar -atento -ateo -ático -atleta -átomo -atraer -atroz -atún -audaz -audio -auge -aula -aumento -ausente -autor -aval -avance -avaro -ave -avellana -avena -avestruz -avión -aviso -ayer -ayuda -ayuno -azafrán -azar -azote -azúcar -azufre -azul -baba -babor -bache -bahía -baile -bajar -balanza -balcón -balde -bambú -banco -banda -baño -barba -barco -barniz -barro -báscula -bastón -basura -batalla -batería -batir -batuta -baúl -bazar -bebé -bebida -bello -besar -beso -bestia -bicho -bien -bingo -blanco -bloque -blusa -boa -bobina -bobo -boca -bocina -boda -bodega -boina -bola -bolero -bolsa -bomba -bondad -bonito -bono -bonsái -borde -borrar -bosque -bote -botín -bóveda -bozal -bravo -brazo -brecha -breve -brillo -brinco -brisa -broca -broma -bronce -brote -bruja -brusco -bruto -buceo -bucle -bueno -buey -bufanda -bufón -búho -buitre -bulto -burbuja -burla -burro -buscar -butaca -buzón -caballo -cabeza -cabina -cabra -cacao -cadáver -cadena -caer -café -caída -caimán -caja -cajón -cal -calamar -calcio -caldo -calidad -calle -calma -calor -calvo -cama -cambio -camello -camino -campo -cáncer -candil -canela -canguro -canica -canto -caña -cañón -caoba -caos -capaz -capitán -capote -captar -capucha -cara -carbón -cárcel -careta -carga -cariño -carne -carpeta -carro -carta -casa -casco -casero -caspa -castor -catorce -catre -caudal -causa -cazo -cebolla -ceder -cedro -celda -célebre -celoso -célula -cemento -ceniza -centro -cerca -cerdo -cereza -cero -cerrar -certeza -césped -cetro -chacal -chaleco -champú -chancla -chapa -charla -chico -chiste -chivo -choque -choza -chuleta -chupar -ciclón -ciego -cielo -cien -cierto -cifra -cigarro -cima -cinco -cine -cinta -ciprés -circo -ciruela -cisne -cita -ciudad -clamor -clan -claro -clase -clave -cliente -clima -clínica -cobre -cocción -cochino -cocina -coco -código -codo -cofre -coger -cohete -cojín -cojo -cola -colcha -colegio -colgar -colina -collar -colmo -columna -combate -comer -comida -cómodo -compra -conde -conejo -conga -conocer -consejo -contar -copa -copia -corazón -corbata -corcho -cordón -corona -correr -coser -cosmos -costa -cráneo -cráter -crear -crecer -creído -crema -cría -crimen -cripta -crisis -cromo -crónica -croqueta -crudo -cruz -cuadro -cuarto -cuatro -cubo -cubrir -cuchara -cuello -cuento -cuerda -cuesta -cueva -cuidar -culebra -culpa -culto -cumbre -cumplir -cuna -cuneta -cuota -cupón -cúpula -curar -curioso -curso -curva -cutis -dama -danza -dar -dardo -dátil -deber -débil -década -decir -dedo -defensa -definir -dejar -delfín -delgado -delito -demora -denso -dental -deporte -derecho -derrota -desayuno -deseo -desfile -desnudo -destino -desvío -detalle -detener -deuda -día -diablo -diadema -diamante -diana -diario -dibujo -dictar -diente -dieta -diez -difícil -digno -dilema -diluir -dinero -directo -dirigir -disco -diseño -disfraz -diva -divino -doble -doce -dolor -domingo -don -donar -dorado -dormir -dorso -dos -dosis -dragón -droga -ducha -duda -duelo -dueño -dulce -dúo -duque -durar -dureza -duro -ébano -ebrio -echar -eco -ecuador -edad -edición -edificio -editor -educar -efecto -eficaz -eje -ejemplo -elefante -elegir -elemento -elevar -elipse -élite -elixir -elogio -eludir -embudo -emitir -emoción -empate -empeño -empleo -empresa -enano -encargo -enchufe -encía -enemigo -enero -enfado -enfermo -engaño -enigma -enlace -enorme -enredo -ensayo -enseñar -entero -entrar -envase -envío -época -equipo -erizo -escala -escena -escolar -escribir -escudo -esencia -esfera -esfuerzo -espada -espejo -espía -esposa -espuma -esquí -estar -este -estilo -estufa -etapa -eterno -ética -etnia -evadir -evaluar -evento -evitar -exacto -examen -exceso -excusa -exento -exigir -exilio -existir -éxito -experto -explicar -exponer -extremo -fábrica -fábula -fachada -fácil -factor -faena -faja -falda -fallo -falso -faltar -fama -familia -famoso -faraón -farmacia -farol -farsa -fase -fatiga -fauna -favor -fax -febrero -fecha -feliz -feo -feria -feroz -fértil -fervor -festín -fiable -fianza -fiar -fibra -ficción -ficha -fideo -fiebre -fiel -fiera -fiesta -figura -fijar -fijo -fila -filete -filial -filtro -fin -finca -fingir -finito -firma -flaco -flauta -flecha -flor -flota -fluir -flujo -flúor -fobia -foca -fogata -fogón -folio -folleto -fondo -forma -forro -fortuna -forzar -fosa -foto -fracaso -frágil -franja -frase -fraude -freír -freno -fresa -frío -frito -fruta -fuego -fuente -fuerza -fuga -fumar -función -funda -furgón -furia -fusil -fútbol -futuro -gacela -gafas -gaita -gajo -gala -galería -gallo -gamba -ganar -gancho -ganga -ganso -garaje -garza -gasolina -gastar -gato -gavilán -gemelo -gemir -gen -género -genio -gente -geranio -gerente -germen -gesto -gigante -gimnasio -girar -giro -glaciar -globo -gloria -gol -golfo -goloso -golpe -goma -gordo -gorila -gorra -gota -goteo -gozar -grada -gráfico -grano -grasa -gratis -grave -grieta -grillo -gripe -gris -grito -grosor -grúa -grueso -grumo -grupo -guante -guapo -guardia -guerra -guía -guiño -guion -guiso -guitarra -gusano -gustar -haber -hábil -hablar -hacer -hacha -hada -hallar -hamaca -harina -haz -hazaña -hebilla -hebra -hecho -helado -helio -hembra -herir -hermano -héroe -hervir -hielo -hierro -hígado -higiene -hijo -himno -historia -hocico -hogar -hoguera -hoja -hombre -hongo -honor -honra -hora -hormiga -horno -hostil -hoyo -hueco -huelga -huerta -hueso -huevo -huida -huir -humano -húmedo -humilde -humo -hundir -huracán -hurto -icono -ideal -idioma -ídolo -iglesia -iglú -igual -ilegal -ilusión -imagen -imán -imitar -impar -imperio -imponer -impulso -incapaz -índice -inerte -infiel -informe -ingenio -inicio -inmenso -inmune -innato -insecto -instante -interés -íntimo -intuir -inútil -invierno -ira -iris -ironía -isla -islote -jabalí -jabón -jamón -jarabe -jardín -jarra -jaula -jazmín -jefe -jeringa -jinete -jornada -joroba -joven -joya -juerga -jueves -juez -jugador -jugo -juguete -juicio -junco -jungla -junio -juntar -júpiter -jurar -justo -juvenil -juzgar -kilo -koala -labio -lacio -lacra -lado -ladrón -lagarto -lágrima -laguna -laico -lamer -lámina -lámpara -lana -lancha -langosta -lanza -lápiz -largo -larva -lástima -lata -látex -latir -laurel -lavar -lazo -leal -lección -leche -lector -leer -legión -legumbre -lejano -lengua -lento -leña -león -leopardo -lesión -letal -letra -leve -leyenda -libertad -libro -licor -líder -lidiar -lienzo -liga -ligero -lima -límite -limón -limpio -lince -lindo -línea -lingote -lino -linterna -líquido -liso -lista -litera -litio -litro -llaga -llama -llanto -llave -llegar -llenar -llevar -llorar -llover -lluvia -lobo -loción -loco -locura -lógica -logro -lombriz -lomo -lonja -lote -lucha -lucir -lugar -lujo -luna -lunes -lupa -lustro -luto -luz -maceta -macho -madera -madre -maduro -maestro -mafia -magia -mago -maíz -maldad -maleta -malla -malo -mamá -mambo -mamut -manco -mando -manejar -manga -maniquí -manjar -mano -manso -manta -mañana -mapa -máquina -mar -marco -marea -marfil -margen -marido -mármol -marrón -martes -marzo -masa -máscara -masivo -matar -materia -matiz -matriz -máximo -mayor -mazorca -mecha -medalla -medio -médula -mejilla -mejor -melena -melón -memoria -menor -mensaje -mente -menú -mercado -merengue -mérito -mes -mesón -meta -meter -método -metro -mezcla -miedo -miel -miembro -miga -mil -milagro -militar -millón -mimo -mina -minero -mínimo -minuto -miope -mirar -misa -miseria -misil -mismo -mitad -mito -mochila -moción -moda -modelo -moho -mojar -molde -moler -molino -momento -momia -monarca -moneda -monja -monto -moño -morada -morder -moreno -morir -morro -morsa -mortal -mosca -mostrar -motivo -mover -móvil -mozo -mucho -mudar -mueble -muela -muerte -muestra -mugre -mujer -mula -muleta -multa -mundo -muñeca -mural -muro -músculo -museo -musgo -música -muslo -nácar -nación -nadar -naipe -naranja -nariz -narrar -nasal -natal -nativo -natural -náusea -naval -nave -navidad -necio -néctar -negar -negocio -negro -neón -nervio -neto -neutro -nevar -nevera -nicho -nido -niebla -nieto -niñez -niño -nítido -nivel -nobleza -noche -nómina -noria -norma -norte -nota -noticia -novato -novela -novio -nube -nuca -núcleo -nudillo -nudo -nuera -nueve -nuez -nulo -número -nutria -oasis -obeso -obispo -objeto -obra -obrero -observar -obtener -obvio -oca -ocaso -océano -ochenta -ocho -ocio -ocre -octavo -octubre -oculto -ocupar -ocurrir -odiar -odio -odisea -oeste -ofensa -oferta -oficio -ofrecer -ogro -oído -oír -ojo -ola -oleada -olfato -olivo -olla -olmo -olor -olvido -ombligo -onda -onza -opaco -opción -ópera -opinar -oponer -optar -óptica -opuesto -oración -orador -oral -órbita -orca -orden -oreja -órgano -orgía -orgullo -oriente -origen -orilla -oro -orquesta -oruga -osadía -oscuro -osezno -oso -ostra -otoño -otro -oveja -óvulo -óxido -oxígeno -oyente -ozono -pacto -padre -paella -página -pago -país -pájaro -palabra -palco -paleta -pálido -palma -paloma -palpar -pan -panal -pánico -pantera -pañuelo -papá -papel -papilla -paquete -parar -parcela -pared -parir -paro -párpado -parque -párrafo -parte -pasar -paseo -pasión -paso -pasta -pata -patio -patria -pausa -pauta -pavo -payaso -peatón -pecado -pecera -pecho -pedal -pedir -pegar -peine -pelar -peldaño -pelea -peligro -pellejo -pelo -peluca -pena -pensar -peñón -peón -peor -pepino -pequeño -pera -percha -perder -pereza -perfil -perico -perla -permiso -perro -persona -pesa -pesca -pésimo -pestaña -pétalo -petróleo -pez -pezuña -picar -pichón -pie -piedra -pierna -pieza -pijama -pilar -piloto -pimienta -pino -pintor -pinza -piña -piojo -pipa -pirata -pisar -piscina -piso -pista -pitón -pizca -placa -plan -plata -playa -plaza -pleito -pleno -plomo -pluma -plural -pobre -poco -poder -podio -poema -poesía -poeta -polen -policía -pollo -polvo -pomada -pomelo -pomo -pompa -poner -porción -portal -posada -poseer -posible -poste -potencia -potro -pozo -prado -precoz -pregunta -premio -prensa -preso -previo -primo -príncipe -prisión -privar -proa -probar -proceso -producto -proeza -profesor -programa -prole -promesa -pronto -propio -próximo -prueba -público -puchero -pudor -pueblo -puerta -puesto -pulga -pulir -pulmón -pulpo -pulso -puma -punto -puñal -puño -pupa -pupila -puré -quedar -queja -quemar -querer -queso -quieto -química -quince -quitar -rábano -rabia -rabo -ración -radical -raíz -rama -rampa -rancho -rango -rapaz -rápido -rapto -rasgo -raspa -rato -rayo -raza -razón -reacción -realidad -rebaño -rebote -recaer -receta -rechazo -recoger -recreo -recto -recurso -red -redondo -reducir -reflejo -reforma -refrán -refugio -regalo -regir -regla -regreso -rehén -reino -reír -reja -relato -relevo -relieve -relleno -reloj -remar -remedio -remo -rencor -rendir -renta -reparto -repetir -reposo -reptil -res -rescate -resina -respeto -resto -resumen -retiro -retorno -retrato -reunir -revés -revista -rey -rezar -rico -riego -rienda -riesgo -rifa -rígido -rigor -rincón -riñón -río -riqueza -risa -ritmo -rito -rizo -roble -roce -rociar -rodar -rodeo -rodilla -roer -rojizo -rojo -romero -romper -ron -ronco -ronda -ropa -ropero -rosa -rosca -rostro -rotar -rubí -rubor -rudo -rueda -rugir -ruido -ruina -ruleta -rulo -rumbo -rumor -ruptura -ruta -rutina -sábado -saber -sabio -sable -sacar -sagaz -sagrado -sala -saldo -salero -salir -salmón -salón -salsa -salto -salud -salvar -samba -sanción -sandía -sanear -sangre -sanidad -sano -santo -sapo -saque -sardina -sartén -sastre -satán -sauna -saxofón -sección -seco -secreto -secta -sed -seguir -seis -sello -selva -semana -semilla -senda -sensor -señal -señor -separar -sepia -sequía -ser -serie -sermón -servir -sesenta -sesión -seta -setenta -severo -sexo -sexto -sidra -siesta -siete -siglo -signo -sílaba -silbar -silencio -silla -símbolo -simio -sirena -sistema -sitio -situar -sobre -socio -sodio -sol -solapa -soldado -soledad -sólido -soltar -solución -sombra -sondeo -sonido -sonoro -sonrisa -sopa -soplar -soporte -sordo -sorpresa -sorteo -sostén -sótano -suave -subir -suceso -sudor -suegra -suelo -sueño -suerte -sufrir -sujeto -sultán -sumar -superar -suplir -suponer -supremo -sur -surco -sureño -surgir -susto -sutil -tabaco -tabique -tabla -tabú -taco -tacto -tajo -talar -talco -talento -talla -talón -tamaño -tambor -tango -tanque -tapa -tapete -tapia -tapón -taquilla -tarde -tarea -tarifa -tarjeta -tarot -tarro -tarta -tatuaje -tauro -taza -tazón -teatro -techo -tecla -técnica -tejado -tejer -tejido -tela -teléfono -tema -temor -templo -tenaz -tender -tener -tenis -tenso -teoría -terapia -terco -término -ternura -terror -tesis -tesoro -testigo -tetera -texto -tez -tibio -tiburón -tiempo -tienda -tierra -tieso -tigre -tijera -tilde -timbre -tímido -timo -tinta -tío -típico -tipo -tira -tirón -titán -títere -título -tiza -toalla -tobillo -tocar -tocino -todo -toga -toldo -tomar -tono -tonto -topar -tope -toque -tórax -torero -tormenta -torneo -toro -torpedo -torre -torso -tortuga -tos -tosco -toser -tóxico -trabajo -tractor -traer -tráfico -trago -traje -tramo -trance -trato -trauma -trazar -trébol -tregua -treinta -tren -trepar -tres -tribu -trigo -tripa -triste -triunfo -trofeo -trompa -tronco -tropa -trote -trozo -truco -trueno -trufa -tubería -tubo -tuerto -tumba -tumor -túnel -túnica -turbina -turismo -turno -tutor -ubicar -úlcera -umbral -unidad -unir -universo -uno -untar -uña -urbano -urbe -urgente -urna -usar -usuario -útil -utopía -uva -vaca -vacío -vacuna -vagar -vago -vaina -vajilla -vale -válido -valle -valor -válvula -vampiro -vara -variar -varón -vaso -vecino -vector -vehículo -veinte -vejez -vela -velero -veloz -vena -vencer -venda -veneno -vengar -venir -venta -venus -ver -verano -verbo -verde -vereda -verja -verso -verter -vía -viaje -vibrar -vicio -víctima -vida -vídeo -vidrio -viejo -viernes -vigor -vil -villa -vinagre -vino -viñedo -violín -viral -virgo -virtud -visor -víspera -vista -vitamina -viudo -vivaz -vivero -vivir -vivo -volcán -volumen -volver -voraz -votar -voto -voz -vuelo -vulgar -yacer -yate -yegua -yema -yerno -yeso -yodo -yoga -yogur -zafiro -zanja -zapato -zarza -zona -zorro -zumo -zurdo \ No newline at end of file diff --git a/app/src/test/resources/fr-BIP39Words.txt b/app/src/test/resources/fr-BIP39Words.txt deleted file mode 100644 index 8600949dd..000000000 --- a/app/src/test/resources/fr-BIP39Words.txt +++ /dev/null @@ -1,2048 +0,0 @@ -abaisser -abandon -abdiquer -abeille -abolir -aborder -aboutir -aboyer -abrasif -abreuver -abriter -abroger -abrupt -absence -absolu -absurde -abusif -abyssal -académie -acajou -acarien -accabler -accepter -acclamer -accolade -accroche -accuser -acerbe -achat -acheter -aciduler -acier -acompte -acquérir -acronyme -acteur -actif -actuel -adepte -adéquat -adhésif -adjectif -adjuger -admettre -admirer -adopter -adorer -adoucir -adresse -adroit -adulte -adverbe -aérer -aéronef -affaire -affecter -affiche -affreux -affubler -agacer -agencer -agile -agiter -agrafer -agréable -agrume -aider -aiguille -ailier -aimable -aisance -ajouter -ajuster -alarmer -alchimie -alerte -algèbre -algue -aliéner -aliment -alléger -alliage -allouer -allumer -alourdir -alpaga -altesse -alvéole -amateur -ambigu -ambre -aménager -amertume -amidon -amiral -amorcer -amour -amovible -amphibie -ampleur -amusant -analyse -anaphore -anarchie -anatomie -ancien -anéantir -angle -angoisse -anguleux -animal -annexer -annonce -annuel -anodin -anomalie -anonyme -anormal -antenne -antidote -anxieux -apaiser -apéritif -aplanir -apologie -appareil -appeler -apporter -appuyer -aquarium -aqueduc -arbitre -arbuste -ardeur -ardoise -argent -arlequin -armature -armement -armoire -armure -arpenter -arracher -arriver -arroser -arsenic -artériel -article -aspect -asphalte -aspirer -assaut -asservir -assiette -associer -assurer -asticot -astre -astuce -atelier -atome -atrium -atroce -attaque -attentif -attirer -attraper -aubaine -auberge -audace -audible -augurer -aurore -automne -autruche -avaler -avancer -avarice -avenir -averse -aveugle -aviateur -avide -avion -aviser -avoine -avouer -avril -axial -axiome -badge -bafouer -bagage -baguette -baignade -balancer -balcon -baleine -balisage -bambin -bancaire -bandage -banlieue -bannière -banquier -barbier -baril -baron -barque -barrage -bassin -bastion -bataille -bateau -batterie -baudrier -bavarder -belette -bélier -belote -bénéfice -berceau -berger -berline -bermuda -besace -besogne -bétail -beurre -biberon -bicycle -bidule -bijou -bilan -bilingue -billard -binaire -biologie -biopsie -biotype -biscuit -bison -bistouri -bitume -bizarre -blafard -blague -blanchir -blessant -blinder -blond -bloquer -blouson -bobard -bobine -boire -boiser -bolide -bonbon -bondir -bonheur -bonifier -bonus -bordure -borne -botte -boucle -boueux -bougie -boulon -bouquin -bourse -boussole -boutique -boxeur -branche -brasier -brave -brebis -brèche -breuvage -bricoler -brigade -brillant -brioche -brique -brochure -broder -bronzer -brousse -broyeur -brume -brusque -brutal -bruyant -buffle -buisson -bulletin -bureau -burin -bustier -butiner -butoir -buvable -buvette -cabanon -cabine -cachette -cadeau -cadre -caféine -caillou -caisson -calculer -calepin -calibre -calmer -calomnie -calvaire -camarade -caméra -camion -campagne -canal -caneton -canon -cantine -canular -capable -caporal -caprice -capsule -capter -capuche -carabine -carbone -caresser -caribou -carnage -carotte -carreau -carton -cascade -casier -casque -cassure -causer -caution -cavalier -caverne -caviar -cédille -ceinture -céleste -cellule -cendrier -censurer -central -cercle -cérébral -cerise -cerner -cerveau -cesser -chagrin -chaise -chaleur -chambre -chance -chapitre -charbon -chasseur -chaton -chausson -chavirer -chemise -chenille -chéquier -chercher -cheval -chien -chiffre -chignon -chimère -chiot -chlorure -chocolat -choisir -chose -chouette -chrome -chute -cigare -cigogne -cimenter -cinéma -cintrer -circuler -cirer -cirque -citerne -citoyen -citron -civil -clairon -clameur -claquer -classe -clavier -client -cligner -climat -clivage -cloche -clonage -cloporte -cobalt -cobra -cocasse -cocotier -coder -codifier -coffre -cogner -cohésion -coiffer -coincer -colère -colibri -colline -colmater -colonel -combat -comédie -commande -compact -concert -conduire -confier -congeler -connoter -consonne -contact -convexe -copain -copie -corail -corbeau -cordage -corniche -corpus -correct -cortège -cosmique -costume -coton -coude -coupure -courage -couteau -couvrir -coyote -crabe -crainte -cravate -crayon -créature -créditer -crémeux -creuser -crevette -cribler -crier -cristal -critère -croire -croquer -crotale -crucial -cruel -crypter -cubique -cueillir -cuillère -cuisine -cuivre -culminer -cultiver -cumuler -cupide -curatif -curseur -cyanure -cycle -cylindre -cynique -daigner -damier -danger -danseur -dauphin -débattre -débiter -déborder -débrider -débutant -décaler -décembre -déchirer -décider -déclarer -décorer -décrire -décupler -dédale -déductif -déesse -défensif -défiler -défrayer -dégager -dégivrer -déglutir -dégrafer -déjeuner -délice -déloger -demander -demeurer -démolir -dénicher -dénouer -dentelle -dénuder -départ -dépenser -déphaser -déplacer -déposer -déranger -dérober -désastre -descente -désert -désigner -désobéir -dessiner -destrier -détacher -détester -détourer -détresse -devancer -devenir -deviner -devoir -diable -dialogue -diamant -dicter -différer -digérer -digital -digne -diluer -dimanche -diminuer -dioxyde -directif -diriger -discuter -disposer -dissiper -distance -divertir -diviser -docile -docteur -dogme -doigt -domaine -domicile -dompter -donateur -donjon -donner -dopamine -dortoir -dorure -dosage -doseur -dossier -dotation -douanier -double -douceur -douter -doyen -dragon -draper -dresser -dribbler -droiture -duperie -duplexe -durable -durcir -dynastie -éblouir -écarter -écharpe -échelle -éclairer -éclipse -éclore -écluse -école -économie -écorce -écouter -écraser -écrémer -écrivain -écrou -écume -écureuil -édifier -éduquer -effacer -effectif -effigie -effort -effrayer -effusion -égaliser -égarer -éjecter -élaborer -élargir -électron -élégant -éléphant -élève -éligible -élitisme -éloge -élucider -éluder -emballer -embellir -embryon -émeraude -émission -emmener -émotion -émouvoir -empereur -employer -emporter -emprise -émulsion -encadrer -enchère -enclave -encoche -endiguer -endosser -endroit -enduire -énergie -enfance -enfermer -enfouir -engager -engin -englober -énigme -enjamber -enjeu -enlever -ennemi -ennuyeux -enrichir -enrobage -enseigne -entasser -entendre -entier -entourer -entraver -énumérer -envahir -enviable -envoyer -enzyme -éolien -épaissir -épargne -épatant -épaule -épicerie -épidémie -épier -épilogue -épine -épisode -épitaphe -époque -épreuve -éprouver -épuisant -équerre -équipe -ériger -érosion -erreur -éruption -escalier -espadon -espèce -espiègle -espoir -esprit -esquiver -essayer -essence -essieu -essorer -estime -estomac -estrade -étagère -étaler -étanche -étatique -éteindre -étendoir -éternel -éthanol -éthique -ethnie -étirer -étoffer -étoile -étonnant -étourdir -étrange -étroit -étude -euphorie -évaluer -évasion -éventail -évidence -éviter -évolutif -évoquer -exact -exagérer -exaucer -exceller -excitant -exclusif -excuse -exécuter -exemple -exercer -exhaler -exhorter -exigence -exiler -exister -exotique -expédier -explorer -exposer -exprimer -exquis -extensif -extraire -exulter -fable -fabuleux -facette -facile -facture -faiblir -falaise -fameux -famille -farceur -farfelu -farine -farouche -fasciner -fatal -fatigue -faucon -fautif -faveur -favori -fébrile -féconder -fédérer -félin -femme -fémur -fendoir -féodal -fermer -féroce -ferveur -festival -feuille -feutre -février -fiasco -ficeler -fictif -fidèle -figure -filature -filetage -filière -filleul -filmer -filou -filtrer -financer -finir -fiole -firme -fissure -fixer -flairer -flamme -flasque -flatteur -fléau -flèche -fleur -flexion -flocon -flore -fluctuer -fluide -fluvial -folie -fonderie -fongible -fontaine -forcer -forgeron -formuler -fortune -fossile -foudre -fougère -fouiller -foulure -fourmi -fragile -fraise -franchir -frapper -frayeur -frégate -freiner -frelon -frémir -frénésie -frère -friable -friction -frisson -frivole -froid -fromage -frontal -frotter -fruit -fugitif -fuite -fureur -furieux -furtif -fusion -futur -gagner -galaxie -galerie -gambader -garantir -gardien -garnir -garrigue -gazelle -gazon -géant -gélatine -gélule -gendarme -général -génie -genou -gentil -géologie -géomètre -géranium -germe -gestuel -geyser -gibier -gicler -girafe -givre -glace -glaive -glisser -globe -gloire -glorieux -golfeur -gomme -gonfler -gorge -gorille -goudron -gouffre -goulot -goupille -gourmand -goutte -graduel -graffiti -graine -grand -grappin -gratuit -gravir -grenat -griffure -griller -grimper -grogner -gronder -grotte -groupe -gruger -grutier -gruyère -guépard -guerrier -guide -guimauve -guitare -gustatif -gymnaste -gyrostat -habitude -hachoir -halte -hameau -hangar -hanneton -haricot -harmonie -harpon -hasard -hélium -hématome -herbe -hérisson -hermine -héron -hésiter -heureux -hiberner -hibou -hilarant -histoire -hiver -homard -hommage -homogène -honneur -honorer -honteux -horde -horizon -horloge -hormone -horrible -houleux -housse -hublot -huileux -humain -humble -humide -humour -hurler -hydromel -hygiène -hymne -hypnose -idylle -ignorer -iguane -illicite -illusion -image -imbiber -imiter -immense -immobile -immuable -impact -impérial -implorer -imposer -imprimer -imputer -incarner -incendie -incident -incliner -incolore -indexer -indice -inductif -inédit -ineptie -inexact -infini -infliger -informer -infusion -ingérer -inhaler -inhiber -injecter -injure -innocent -inoculer -inonder -inscrire -insecte -insigne -insolite -inspirer -instinct -insulter -intact -intense -intime -intrigue -intuitif -inutile -invasion -inventer -inviter -invoquer -ironique -irradier -irréel -irriter -isoler -ivoire -ivresse -jaguar -jaillir -jambe -janvier -jardin -jauger -jaune -javelot -jetable -jeton -jeudi -jeunesse -joindre -joncher -jongler -joueur -jouissif -journal -jovial -joyau -joyeux -jubiler -jugement -junior -jupon -juriste -justice -juteux -juvénile -kayak -kimono -kiosque -label -labial -labourer -lacérer -lactose -lagune -laine -laisser -laitier -lambeau -lamelle -lampe -lanceur -langage -lanterne -lapin -largeur -larme -laurier -lavabo -lavoir -lecture -légal -léger -légume -lessive -lettre -levier -lexique -lézard -liasse -libérer -libre -licence -licorne -liège -lièvre -ligature -ligoter -ligue -limer -limite -limonade -limpide -linéaire -lingot -lionceau -liquide -lisière -lister -lithium -litige -littoral -livreur -logique -lointain -loisir -lombric -loterie -louer -lourd -loutre -louve -loyal -lubie -lucide -lucratif -lueur -lugubre -luisant -lumière -lunaire -lundi -luron -lutter -luxueux -machine -magasin -magenta -magique -maigre -maillon -maintien -mairie -maison -majorer -malaxer -maléfice -malheur -malice -mallette -mammouth -mandater -maniable -manquant -manteau -manuel -marathon -marbre -marchand -mardi -maritime -marqueur -marron -marteler -mascotte -massif -matériel -matière -matraque -maudire -maussade -mauve -maximal -méchant -méconnu -médaille -médecin -méditer -méduse -meilleur -mélange -mélodie -membre -mémoire -menacer -mener -menhir -mensonge -mentor -mercredi -mérite -merle -messager -mesure -métal -météore -méthode -métier -meuble -miauler -microbe -miette -mignon -migrer -milieu -million -mimique -mince -minéral -minimal -minorer -minute -miracle -miroiter -missile -mixte -mobile -moderne -moelleux -mondial -moniteur -monnaie -monotone -monstre -montagne -monument -moqueur -morceau -morsure -mortier -moteur -motif -mouche -moufle -moulin -mousson -mouton -mouvant -multiple -munition -muraille -murène -murmure -muscle -muséum -musicien -mutation -muter -mutuel -myriade -myrtille -mystère -mythique -nageur -nappe -narquois -narrer -natation -nation -nature -naufrage -nautique -navire -nébuleux -nectar -néfaste -négation -négliger -négocier -neige -nerveux -nettoyer -neurone -neutron -neveu -niche -nickel -nitrate -niveau -noble -nocif -nocturne -noirceur -noisette -nomade -nombreux -nommer -normatif -notable -notifier -notoire -nourrir -nouveau -novateur -novembre -novice -nuage -nuancer -nuire -nuisible -numéro -nuptial -nuque -nutritif -obéir -objectif -obliger -obscur -observer -obstacle -obtenir -obturer -occasion -occuper -océan -octobre -octroyer -octupler -oculaire -odeur -odorant -offenser -officier -offrir -ogive -oiseau -oisillon -olfactif -olivier -ombrage -omettre -onctueux -onduler -onéreux -onirique -opale -opaque -opérer -opinion -opportun -opprimer -opter -optique -orageux -orange -orbite -ordonner -oreille -organe -orgueil -orifice -ornement -orque -ortie -osciller -osmose -ossature -otarie -ouragan -ourson -outil -outrager -ouvrage -ovation -oxyde -oxygène -ozone -paisible -palace -palmarès -palourde -palper -panache -panda -pangolin -paniquer -panneau -panorama -pantalon -papaye -papier -papoter -papyrus -paradoxe -parcelle -paresse -parfumer -parler -parole -parrain -parsemer -partager -parure -parvenir -passion -pastèque -paternel -patience -patron -pavillon -pavoiser -payer -paysage -peigne -peintre -pelage -pélican -pelle -pelouse -peluche -pendule -pénétrer -pénible -pensif -pénurie -pépite -péplum -perdrix -perforer -période -permuter -perplexe -persil -perte -peser -pétale -petit -pétrir -peuple -pharaon -phobie -phoque -photon -phrase -physique -piano -pictural -pièce -pierre -pieuvre -pilote -pinceau -pipette -piquer -pirogue -piscine -piston -pivoter -pixel -pizza -placard -plafond -plaisir -planer -plaque -plastron -plateau -pleurer -plexus -pliage -plomb -plonger -pluie -plumage -pochette -poésie -poète -pointe -poirier -poisson -poivre -polaire -policier -pollen -polygone -pommade -pompier -ponctuel -pondérer -poney -portique -position -posséder -posture -potager -poteau -potion -pouce -poulain -poumon -pourpre -poussin -pouvoir -prairie -pratique -précieux -prédire -préfixe -prélude -prénom -présence -prétexte -prévoir -primitif -prince -prison -priver -problème -procéder -prodige -profond -progrès -proie -projeter -prologue -promener -propre -prospère -protéger -prouesse -proverbe -prudence -pruneau -psychose -public -puceron -puiser -pulpe -pulsar -punaise -punitif -pupitre -purifier -puzzle -pyramide -quasar -querelle -question -quiétude -quitter -quotient -racine -raconter -radieux -ragondin -raideur -raisin -ralentir -rallonge -ramasser -rapide -rasage -ratisser -ravager -ravin -rayonner -réactif -réagir -réaliser -réanimer -recevoir -réciter -réclamer -récolter -recruter -reculer -recycler -rédiger -redouter -refaire -réflexe -réformer -refrain -refuge -régalien -région -réglage -régulier -réitérer -rejeter -rejouer -relatif -relever -relief -remarque -remède -remise -remonter -remplir -remuer -renard -renfort -renifler -renoncer -rentrer -renvoi -replier -reporter -reprise -reptile -requin -réserve -résineux -résoudre -respect -rester -résultat -rétablir -retenir -réticule -retomber -retracer -réunion -réussir -revanche -revivre -révolte -révulsif -richesse -rideau -rieur -rigide -rigoler -rincer -riposter -risible -risque -rituel -rival -rivière -rocheux -romance -rompre -ronce -rondin -roseau -rosier -rotatif -rotor -rotule -rouge -rouille -rouleau -routine -royaume -ruban -rubis -ruche -ruelle -rugueux -ruiner -ruisseau -ruser -rustique -rythme -sabler -saboter -sabre -sacoche -safari -sagesse -saisir -salade -salive -salon -saluer -samedi -sanction -sanglier -sarcasme -sardine -saturer -saugrenu -saumon -sauter -sauvage -savant -savonner -scalpel -scandale -scélérat -scénario -sceptre -schéma -science -scinder -score -scrutin -sculpter -séance -sécable -sécher -secouer -sécréter -sédatif -séduire -seigneur -séjour -sélectif -semaine -sembler -semence -séminal -sénateur -sensible -sentence -séparer -séquence -serein -sergent -sérieux -serrure -sérum -service -sésame -sévir -sevrage -sextuple -sidéral -siècle -siéger -siffler -sigle -signal -silence -silicium -simple -sincère -sinistre -siphon -sirop -sismique -situer -skier -social -socle -sodium -soigneux -soldat -soleil -solitude -soluble -sombre -sommeil -somnoler -sonde -songeur -sonnette -sonore -sorcier -sortir -sosie -sottise -soucieux -soudure -souffle -soulever -soupape -source -soutirer -souvenir -spacieux -spatial -spécial -sphère -spiral -stable -station -sternum -stimulus -stipuler -strict -studieux -stupeur -styliste -sublime -substrat -subtil -subvenir -succès -sucre -suffixe -suggérer -suiveur -sulfate -superbe -supplier -surface -suricate -surmener -surprise -sursaut -survie -suspect -syllabe -symbole -symétrie -synapse -syntaxe -système -tabac -tablier -tactile -tailler -talent -talisman -talonner -tambour -tamiser -tangible -tapis -taquiner -tarder -tarif -tartine -tasse -tatami -tatouage -taupe -taureau -taxer -témoin -temporel -tenaille -tendre -teneur -tenir -tension -terminer -terne -terrible -tétine -texte -thème -théorie -thérapie -thorax -tibia -tiède -timide -tirelire -tiroir -tissu -titane -titre -tituber -toboggan -tolérant -tomate -tonique -tonneau -toponyme -torche -tordre -tornade -torpille -torrent -torse -tortue -totem -toucher -tournage -tousser -toxine -traction -trafic -tragique -trahir -train -trancher -travail -trèfle -tremper -trésor -treuil -triage -tribunal -tricoter -trilogie -triomphe -tripler -triturer -trivial -trombone -tronc -tropical -troupeau -tuile -tulipe -tumulte -tunnel -turbine -tuteur -tutoyer -tuyau -tympan -typhon -typique -tyran -ubuesque -ultime -ultrason -unanime -unifier -union -unique -unitaire -univers -uranium -urbain -urticant -usage -usine -usuel -usure -utile -utopie -vacarme -vaccin -vagabond -vague -vaillant -vaincre -vaisseau -valable -valise -vallon -valve -vampire -vanille -vapeur -varier -vaseux -vassal -vaste -vecteur -vedette -végétal -véhicule -veinard -véloce -vendredi -vénérer -venger -venimeux -ventouse -verdure -vérin -vernir -verrou -verser -vertu -veston -vétéran -vétuste -vexant -vexer -viaduc -viande -victoire -vidange -vidéo -vignette -vigueur -vilain -village -vinaigre -violon -vipère -virement -virtuose -virus -visage -viseur -vision -visqueux -visuel -vital -vitesse -viticole -vitrine -vivace -vivipare -vocation -voguer -voile -voisin -voiture -volaille -volcan -voltiger -volume -vorace -vortex -voter -vouloir -voyage -voyelle -wagon -xénon -yacht -zèbre -zénith -zeste -zoologie \ No newline at end of file diff --git a/app/src/test/resources/ja-BIP39Words.txt b/app/src/test/resources/ja-BIP39Words.txt deleted file mode 100644 index 82f355f24..000000000 --- a/app/src/test/resources/ja-BIP39Words.txt +++ /dev/null @@ -1,2048 +0,0 @@ -あいこくしん -あいさつ -あいだ -あおぞら -あかちゃん -あきる -あけがた -あける -あこがれる -あさい -あさひ -あしあと -あじわう -あずかる -あずき -あそぶ -あたえる -あたためる -あたりまえ -あたる -あつい -あつかう -あっしゅく -あつまり -あつめる -あてな -あてはまる -あひる -あぶら -あぶる -あふれる -あまい -あまど -あまやかす -あまり -あみもの -あめりか -あやまる -あゆむ -あらいぐま -あらし -あらすじ -あらためる -あらゆる -あらわす -ありがとう -あわせる -あわてる -あんい -あんがい -あんこ -あんぜん -あんてい -あんない -あんまり -いいだす -いおん -いがい -いがく -いきおい -いきなり -いきもの -いきる -いくじ -いくぶん -いけばな -いけん -いこう -いこく -いこつ -いさましい -いさん -いしき -いじゅう -いじょう -いじわる -いずみ -いずれ -いせい -いせえび -いせかい -いせき -いぜん -いそうろう -いそがしい -いだい -いだく -いたずら -いたみ -いたりあ -いちおう -いちじ -いちど -いちば -いちぶ -いちりゅう -いつか -いっしゅん -いっせい -いっそう -いったん -いっち -いってい -いっぽう -いてざ -いてん -いどう -いとこ -いない -いなか -いねむり -いのち -いのる -いはつ -いばる -いはん -いびき -いひん -いふく -いへん -いほう -いみん -いもうと -いもたれ -いもり -いやがる -いやす -いよかん -いよく -いらい -いらすと -いりぐち -いりょう -いれい -いれもの -いれる -いろえんぴつ -いわい -いわう -いわかん -いわば -いわゆる -いんげんまめ -いんさつ -いんしょう -いんよう -うえき -うえる -うおざ -うがい -うかぶ -うかべる -うきわ -うくらいな -うくれれ -うけたまわる -うけつけ -うけとる -うけもつ -うける -うごかす -うごく -うこん -うさぎ -うしなう -うしろがみ -うすい -うすぎ -うすぐらい -うすめる -うせつ -うちあわせ -うちがわ -うちき -うちゅう -うっかり -うつくしい -うったえる -うつる -うどん -うなぎ -うなじ -うなずく -うなる -うねる -うのう -うぶげ -うぶごえ -うまれる -うめる -うもう -うやまう -うよく -うらがえす -うらぐち -うらない -うりあげ -うりきれ -うるさい -うれしい -うれゆき -うれる -うろこ -うわき -うわさ -うんこう -うんちん -うんてん -うんどう -えいえん -えいが -えいきょう -えいご -えいせい -えいぶん -えいよう -えいわ -えおり -えがお -えがく -えきたい -えくせる -えしゃく -えすて -えつらん -えのぐ -えほうまき -えほん -えまき -えもじ -えもの -えらい -えらぶ -えりあ -えんえん -えんかい -えんぎ -えんげき -えんしゅう -えんぜつ -えんそく -えんちょう -えんとつ -おいかける -おいこす -おいしい -おいつく -おうえん -おうさま -おうじ -おうせつ -おうたい -おうふく -おうべい -おうよう -おえる -おおい -おおう -おおどおり -おおや -おおよそ -おかえり -おかず -おがむ -おかわり -おぎなう -おきる -おくさま -おくじょう -おくりがな -おくる -おくれる -おこす -おこなう -おこる -おさえる -おさない -おさめる -おしいれ -おしえる -おじぎ -おじさん -おしゃれ -おそらく -おそわる -おたがい -おたく -おだやか -おちつく -おっと -おつり -おでかけ -おとしもの -おとなしい -おどり -おどろかす -おばさん -おまいり -おめでとう -おもいで -おもう -おもたい -おもちゃ -おやつ -おやゆび -およぼす -おらんだ -おろす -おんがく -おんけい -おんしゃ -おんせん -おんだん -おんちゅう -おんどけい -かあつ -かいが -がいき -がいけん -がいこう -かいさつ -かいしゃ -かいすいよく -かいぜん -かいぞうど -かいつう -かいてん -かいとう -かいふく -がいへき -かいほう -かいよう -がいらい -かいわ -かえる -かおり -かかえる -かがく -かがし -かがみ -かくご -かくとく -かざる -がぞう -かたい -かたち -がちょう -がっきゅう -がっこう -がっさん -がっしょう -かなざわし -かのう -がはく -かぶか -かほう -かほご -かまう -かまぼこ -かめれおん -かゆい -かようび -からい -かるい -かろう -かわく -かわら -がんか -かんけい -かんこう -かんしゃ -かんそう -かんたん -かんち -がんばる -きあい -きあつ -きいろ -ぎいん -きうい -きうん -きえる -きおう -きおく -きおち -きおん -きかい -きかく -きかんしゃ -ききて -きくばり -きくらげ -きけんせい -きこう -きこえる -きこく -きさい -きさく -きさま -きさらぎ -ぎじかがく -ぎしき -ぎじたいけん -ぎじにってい -ぎじゅつしゃ -きすう -きせい -きせき -きせつ -きそう -きぞく -きぞん -きたえる -きちょう -きつえん -ぎっちり -きつつき -きつね -きてい -きどう -きどく -きない -きなが -きなこ -きぬごし -きねん -きのう -きのした -きはく -きびしい -きひん -きふく -きぶん -きぼう -きほん -きまる -きみつ -きむずかしい -きめる -きもだめし -きもち -きもの -きゃく -きやく -ぎゅうにく -きよう -きょうりゅう -きらい -きらく -きりん -きれい -きれつ -きろく -ぎろん -きわめる -ぎんいろ -きんかくじ -きんじょ -きんようび -ぐあい -くいず -くうかん -くうき -くうぐん -くうこう -ぐうせい -くうそう -ぐうたら -くうふく -くうぼ -くかん -くきょう -くげん -ぐこう -くさい -くさき -くさばな -くさる -くしゃみ -くしょう -くすのき -くすりゆび -くせげ -くせん -ぐたいてき -くださる -くたびれる -くちこみ -くちさき -くつした -ぐっすり -くつろぐ -くとうてん -くどく -くなん -くねくね -くのう -くふう -くみあわせ -くみたてる -くめる -くやくしょ -くらす -くらべる -くるま -くれる -くろう -くわしい -ぐんかん -ぐんしょく -ぐんたい -ぐんて -けあな -けいかく -けいけん -けいこ -けいさつ -げいじゅつ -けいたい -げいのうじん -けいれき -けいろ -けおとす -けおりもの -げきか -げきげん -げきだん -げきちん -げきとつ -げきは -げきやく -げこう -げこくじょう -げざい -けさき -げざん -けしき -けしごむ -けしょう -げすと -けたば -けちゃっぷ -けちらす -けつあつ -けつい -けつえき -けっこん -けつじょ -けっせき -けってい -けつまつ -げつようび -げつれい -けつろん -げどく -けとばす -けとる -けなげ -けなす -けなみ -けぬき -げねつ -けねん -けはい -げひん -けぶかい -げぼく -けまり -けみかる -けむし -けむり -けもの -けらい -けろけろ -けわしい -けんい -けんえつ -けんお -けんか -げんき -けんげん -けんこう -けんさく -けんしゅう -けんすう -げんそう -けんちく -けんてい -けんとう -けんない -けんにん -げんぶつ -けんま -けんみん -けんめい -けんらん -けんり -こあくま -こいぬ -こいびと -ごうい -こうえん -こうおん -こうかん -ごうきゅう -ごうけい -こうこう -こうさい -こうじ -こうすい -ごうせい -こうそく -こうたい -こうちゃ -こうつう -こうてい -こうどう -こうない -こうはい -ごうほう -ごうまん -こうもく -こうりつ -こえる -こおり -ごかい -ごがつ -ごかん -こくご -こくさい -こくとう -こくない -こくはく -こぐま -こけい -こける -ここのか -こころ -こさめ -こしつ -こすう -こせい -こせき -こぜん -こそだて -こたい -こたえる -こたつ -こちょう -こっか -こつこつ -こつばん -こつぶ -こてい -こてん -ことがら -ことし -ことば -ことり -こなごな -こねこね -このまま -このみ -このよ -ごはん -こひつじ -こふう -こふん -こぼれる -ごまあぶら -こまかい -ごますり -こまつな -こまる -こむぎこ -こもじ -こもち -こもの -こもん -こやく -こやま -こゆう -こゆび -こよい -こよう -こりる -これくしょん -ころっけ -こわもて -こわれる -こんいん -こんかい -こんき -こんしゅう -こんすい -こんだて -こんとん -こんなん -こんびに -こんぽん -こんまけ -こんや -こんれい -こんわく -ざいえき -さいかい -さいきん -ざいげん -ざいこ -さいしょ -さいせい -ざいたく -ざいちゅう -さいてき -ざいりょう -さうな -さかいし -さがす -さかな -さかみち -さがる -さぎょう -さくし -さくひん -さくら -さこく -さこつ -さずかる -ざせき -さたん -さつえい -ざつおん -ざっか -ざつがく -さっきょく -ざっし -さつじん -ざっそう -さつたば -さつまいも -さてい -さといも -さとう -さとおや -さとし -さとる -さのう -さばく -さびしい -さべつ -さほう -さほど -さます -さみしい -さみだれ -さむけ -さめる -さやえんどう -さゆう -さよう -さよく -さらだ -ざるそば -さわやか -さわる -さんいん -さんか -さんきゃく -さんこう -さんさい -ざんしょ -さんすう -さんせい -さんそ -さんち -さんま -さんみ -さんらん -しあい -しあげ -しあさって -しあわせ -しいく -しいん -しうち -しえい -しおけ -しかい -しかく -じかん -しごと -しすう -じだい -したうけ -したぎ -したて -したみ -しちょう -しちりん -しっかり -しつじ -しつもん -してい -してき -してつ -じてん -じどう -しなぎれ -しなもの -しなん -しねま -しねん -しのぐ -しのぶ -しはい -しばかり -しはつ -しはらい -しはん -しひょう -しふく -じぶん -しへい -しほう -しほん -しまう -しまる -しみん -しむける -じむしょ -しめい -しめる -しもん -しゃいん -しゃうん -しゃおん -じゃがいも -しやくしょ -しゃくほう -しゃけん -しゃこ -しゃざい -しゃしん -しゃせん -しゃそう -しゃたい -しゃちょう -しゃっきん -じゃま -しゃりん -しゃれい -じゆう -じゅうしょ -しゅくはく -じゅしん -しゅっせき -しゅみ -しゅらば -じゅんばん -しょうかい -しょくたく -しょっけん -しょどう -しょもつ -しらせる -しらべる -しんか -しんこう -じんじゃ -しんせいじ -しんちく -しんりん -すあげ -すあし -すあな -ずあん -すいえい -すいか -すいとう -ずいぶん -すいようび -すうがく -すうじつ -すうせん -すおどり -すきま -すくう -すくない -すける -すごい -すこし -ずさん -すずしい -すすむ -すすめる -すっかり -ずっしり -ずっと -すてき -すてる -すねる -すのこ -すはだ -すばらしい -ずひょう -ずぶぬれ -すぶり -すふれ -すべて -すべる -ずほう -すぼん -すまい -すめし -すもう -すやき -すらすら -するめ -すれちがう -すろっと -すわる -すんぜん -すんぽう -せあぶら -せいかつ -せいげん -せいじ -せいよう -せおう -せかいかん -せきにん -せきむ -せきゆ -せきらんうん -せけん -せこう -せすじ -せたい -せたけ -せっかく -せっきゃく -ぜっく -せっけん -せっこつ -せっさたくま -せつぞく -せつだん -せつでん -せっぱん -せつび -せつぶん -せつめい -せつりつ -せなか -せのび -せはば -せびろ -せぼね -せまい -せまる -せめる -せもたれ -せりふ -ぜんあく -せんい -せんえい -せんか -せんきょ -せんく -せんげん -ぜんご -せんさい -せんしゅ -せんすい -せんせい -せんぞ -せんたく -せんちょう -せんてい -せんとう -せんぬき -せんねん -せんぱい -ぜんぶ -ぜんぽう -せんむ -せんめんじょ -せんもん -せんやく -せんゆう -せんよう -ぜんら -ぜんりゃく -せんれい -せんろ -そあく -そいとげる -そいね -そうがんきょう -そうき -そうご -そうしん -そうだん -そうなん -そうび -そうめん -そうり -そえもの -そえん -そがい -そげき -そこう -そこそこ -そざい -そしな -そせい -そせん -そそぐ -そだてる -そつう -そつえん -そっかん -そつぎょう -そっけつ -そっこう -そっせん -そっと -そとがわ -そとづら -そなえる -そなた -そふぼ -そぼく -そぼろ -そまつ -そまる -そむく -そむりえ -そめる -そもそも -そよかぜ -そらまめ -そろう -そんかい -そんけい -そんざい -そんしつ -そんぞく -そんちょう -ぞんび -ぞんぶん -そんみん -たあい -たいいん -たいうん -たいえき -たいおう -だいがく -たいき -たいぐう -たいけん -たいこ -たいざい -だいじょうぶ -だいすき -たいせつ -たいそう -だいたい -たいちょう -たいてい -だいどころ -たいない -たいねつ -たいのう -たいはん -だいひょう -たいふう -たいへん -たいほ -たいまつばな -たいみんぐ -たいむ -たいめん -たいやき -たいよう -たいら -たいりょく -たいる -たいわん -たうえ -たえる -たおす -たおる -たおれる -たかい -たかね -たきび -たくさん -たこく -たこやき -たさい -たしざん -だじゃれ -たすける -たずさわる -たそがれ -たたかう -たたく -ただしい -たたみ -たちばな -だっかい -だっきゃく -だっこ -だっしゅつ -だったい -たてる -たとえる -たなばた -たにん -たぬき -たのしみ -たはつ -たぶん -たべる -たぼう -たまご -たまる -だむる -ためいき -ためす -ためる -たもつ -たやすい -たよる -たらす -たりきほんがん -たりょう -たりる -たると -たれる -たれんと -たろっと -たわむれる -だんあつ -たんい -たんおん -たんか -たんき -たんけん -たんご -たんさん -たんじょうび -だんせい -たんそく -たんたい -だんち -たんてい -たんとう -だんな -たんにん -だんねつ -たんのう -たんぴん -だんぼう -たんまつ -たんめい -だんれつ -だんろ -だんわ -ちあい -ちあん -ちいき -ちいさい -ちえん -ちかい -ちから -ちきゅう -ちきん -ちけいず -ちけん -ちこく -ちさい -ちしき -ちしりょう -ちせい -ちそう -ちたい -ちたん -ちちおや -ちつじょ -ちてき -ちてん -ちぬき -ちぬり -ちのう -ちひょう -ちへいせん -ちほう -ちまた -ちみつ -ちみどろ -ちめいど -ちゃんこなべ -ちゅうい -ちゆりょく -ちょうし -ちょさくけん -ちらし -ちらみ -ちりがみ -ちりょう -ちるど -ちわわ -ちんたい -ちんもく -ついか -ついたち -つうか -つうじょう -つうはん -つうわ -つかう -つかれる -つくね -つくる -つけね -つける -つごう -つたえる -つづく -つつじ -つつむ -つとめる -つながる -つなみ -つねづね -つのる -つぶす -つまらない -つまる -つみき -つめたい -つもり -つもる -つよい -つるぼ -つるみく -つわもの -つわり -てあし -てあて -てあみ -ていおん -ていか -ていき -ていけい -ていこく -ていさつ -ていし -ていせい -ていたい -ていど -ていねい -ていひょう -ていへん -ていぼう -てうち -ておくれ -てきとう -てくび -でこぼこ -てさぎょう -てさげ -てすり -てそう -てちがい -てちょう -てつがく -てつづき -でっぱ -てつぼう -てつや -でぬかえ -てぬき -てぬぐい -てのひら -てはい -てぶくろ -てふだ -てほどき -てほん -てまえ -てまきずし -てみじか -てみやげ -てらす -てれび -てわけ -てわたし -でんあつ -てんいん -てんかい -てんき -てんぐ -てんけん -てんごく -てんさい -てんし -てんすう -でんち -てんてき -てんとう -てんない -てんぷら -てんぼうだい -てんめつ -てんらんかい -でんりょく -でんわ -どあい -といれ -どうかん -とうきゅう -どうぐ -とうし -とうむぎ -とおい -とおか -とおく -とおす -とおる -とかい -とかす -ときおり -ときどき -とくい -とくしゅう -とくてん -とくに -とくべつ -とけい -とける -とこや -とさか -としょかん -とそう -とたん -とちゅう -とっきゅう -とっくん -とつぜん -とつにゅう -とどける -ととのえる -とない -となえる -となり -とのさま -とばす -どぶがわ -とほう -とまる -とめる -ともだち -ともる -どようび -とらえる -とんかつ -どんぶり -ないかく -ないこう -ないしょ -ないす -ないせん -ないそう -なおす -ながい -なくす -なげる -なこうど -なさけ -なたでここ -なっとう -なつやすみ -ななおし -なにごと -なにもの -なにわ -なのか -なふだ -なまいき -なまえ -なまみ -なみだ -なめらか -なめる -なやむ -ならう -ならび -ならぶ -なれる -なわとび -なわばり -にあう -にいがた -にうけ -におい -にかい -にがて -にきび -にくしみ -にくまん -にげる -にさんかたんそ -にしき -にせもの -にちじょう -にちようび -にっか -にっき -にっけい -にっこう -にっさん -にっしょく -にっすう -にっせき -にってい -になう -にほん -にまめ -にもつ -にやり -にゅういん -にりんしゃ -にわとり -にんい -にんか -にんき -にんげん -にんしき -にんずう -にんそう -にんたい -にんち -にんてい -にんにく -にんぷ -にんまり -にんむ -にんめい -にんよう -ぬいくぎ -ぬかす -ぬぐいとる -ぬぐう -ぬくもり -ぬすむ -ぬまえび -ぬめり -ぬらす -ぬんちゃく -ねあげ -ねいき -ねいる -ねいろ -ねぐせ -ねくたい -ねくら -ねこぜ -ねこむ -ねさげ -ねすごす -ねそべる -ねだん -ねつい -ねっしん -ねつぞう -ねったいぎょ -ねぶそく -ねふだ -ねぼう -ねほりはほり -ねまき -ねまわし -ねみみ -ねむい -ねむたい -ねもと -ねらう -ねわざ -ねんいり -ねんおし -ねんかん -ねんきん -ねんぐ -ねんざ -ねんし -ねんちゃく -ねんど -ねんぴ -ねんぶつ -ねんまつ -ねんりょう -ねんれい -のいず -のおづま -のがす -のきなみ -のこぎり -のこす -のこる -のせる -のぞく -のぞむ -のたまう -のちほど -のっく -のばす -のはら -のべる -のぼる -のみもの -のやま -のらいぬ -のらねこ -のりもの -のりゆき -のれん -のんき -ばあい -はあく -ばあさん -ばいか -ばいく -はいけん -はいご -はいしん -はいすい -はいせん -はいそう -はいち -ばいばい -はいれつ -はえる -はおる -はかい -ばかり -はかる -はくしゅ -はけん -はこぶ -はさみ -はさん -はしご -ばしょ -はしる -はせる -ぱそこん -はそん -はたん -はちみつ -はつおん -はっかく -はづき -はっきり -はっくつ -はっけん -はっこう -はっさん -はっしん -はったつ -はっちゅう -はってん -はっぴょう -はっぽう -はなす -はなび -はにかむ -はぶらし -はみがき -はむかう -はめつ -はやい -はやし -はらう -はろうぃん -はわい -はんい -はんえい -はんおん -はんかく -はんきょう -ばんぐみ -はんこ -はんしゃ -はんすう -はんだん -ぱんち -ぱんつ -はんてい -はんとし -はんのう -はんぱ -はんぶん -はんぺん -はんぼうき -はんめい -はんらん -はんろん -ひいき -ひうん -ひえる -ひかく -ひかり -ひかる -ひかん -ひくい -ひけつ -ひこうき -ひこく -ひさい -ひさしぶり -ひさん -びじゅつかん -ひしょ -ひそか -ひそむ -ひたむき -ひだり -ひたる -ひつぎ -ひっこし -ひっし -ひつじゅひん -ひっす -ひつぜん -ぴったり -ぴっちり -ひつよう -ひてい -ひとごみ -ひなまつり -ひなん -ひねる -ひはん -ひびく -ひひょう -ひほう -ひまわり -ひまん -ひみつ -ひめい -ひめじし -ひやけ -ひやす -ひよう -びょうき -ひらがな -ひらく -ひりつ -ひりょう -ひるま -ひるやすみ -ひれい -ひろい -ひろう -ひろき -ひろゆき -ひんかく -ひんけつ -ひんこん -ひんしゅ -ひんそう -ぴんち -ひんぱん -びんぼう -ふあん -ふいうち -ふうけい -ふうせん -ぷうたろう -ふうとう -ふうふ -ふえる -ふおん -ふかい -ふきん -ふくざつ -ふくぶくろ -ふこう -ふさい -ふしぎ -ふじみ -ふすま -ふせい -ふせぐ -ふそく -ぶたにく -ふたん -ふちょう -ふつう -ふつか -ふっかつ -ふっき -ふっこく -ぶどう -ふとる -ふとん -ふのう -ふはい -ふひょう -ふへん -ふまん -ふみん -ふめつ -ふめん -ふよう -ふりこ -ふりる -ふるい -ふんいき -ぶんがく -ぶんぐ -ふんしつ -ぶんせき -ふんそう -ぶんぽう -へいあん -へいおん -へいがい -へいき -へいげん -へいこう -へいさ -へいしゃ -へいせつ -へいそ -へいたく -へいてん -へいねつ -へいわ -へきが -へこむ -べにいろ -べにしょうが -へらす -へんかん -べんきょう -べんごし -へんさい -へんたい -べんり -ほあん -ほいく -ぼうぎょ -ほうこく -ほうそう -ほうほう -ほうもん -ほうりつ -ほえる -ほおん -ほかん -ほきょう -ぼきん -ほくろ -ほけつ -ほけん -ほこう -ほこる -ほしい -ほしつ -ほしゅ -ほしょう -ほせい -ほそい -ほそく -ほたて -ほたる -ぽちぶくろ -ほっきょく -ほっさ -ほったん -ほとんど -ほめる -ほんい -ほんき -ほんけ -ほんしつ -ほんやく -まいにち -まかい -まかせる -まがる -まける -まこと -まさつ -まじめ -ますく -まぜる -まつり -まとめ -まなぶ -まぬけ -まねく -まほう -まもる -まゆげ -まよう -まろやか -まわす -まわり -まわる -まんが -まんきつ -まんぞく -まんなか -みいら -みうち -みえる -みがく -みかた -みかん -みけん -みこん -みじかい -みすい -みすえる -みせる -みっか -みつかる -みつける -みてい -みとめる -みなと -みなみかさい -みねらる -みのう -みのがす -みほん -みもと -みやげ -みらい -みりょく -みわく -みんか -みんぞく -むいか -むえき -むえん -むかい -むかう -むかえ -むかし -むぎちゃ -むける -むげん -むさぼる -むしあつい -むしば -むじゅん -むしろ -むすう -むすこ -むすぶ -むすめ -むせる -むせん -むちゅう -むなしい -むのう -むやみ -むよう -むらさき -むりょう -むろん -めいあん -めいうん -めいえん -めいかく -めいきょく -めいさい -めいし -めいそう -めいぶつ -めいれい -めいわく -めぐまれる -めざす -めした -めずらしい -めだつ -めまい -めやす -めんきょ -めんせき -めんどう -もうしあげる -もうどうけん -もえる -もくし -もくてき -もくようび -もちろん -もどる -もらう -もんく -もんだい -やおや -やける -やさい -やさしい -やすい -やすたろう -やすみ -やせる -やそう -やたい -やちん -やっと -やっぱり -やぶる -やめる -ややこしい -やよい -やわらかい -ゆうき -ゆうびんきょく -ゆうべ -ゆうめい -ゆけつ -ゆしゅつ -ゆせん -ゆそう -ゆたか -ゆちゃく -ゆでる -ゆにゅう -ゆびわ -ゆらい -ゆれる -ようい -ようか -ようきゅう -ようじ -ようす -ようちえん -よかぜ -よかん -よきん -よくせい -よくぼう -よけい -よごれる -よさん -よしゅう -よそう -よそく -よっか -よてい -よどがわく -よねつ -よやく -よゆう -よろこぶ -よろしい -らいう -らくがき -らくご -らくさつ -らくだ -らしんばん -らせん -らぞく -らたい -らっか -られつ -りえき -りかい -りきさく -りきせつ -りくぐん -りくつ -りけん -りこう -りせい -りそう -りそく -りてん -りねん -りゆう -りゅうがく -りよう -りょうり -りょかん -りょくちゃ -りょこう -りりく -りれき -りろん -りんご -るいけい -るいさい -るいじ -るいせき -るすばん -るりがわら -れいかん -れいぎ -れいせい -れいぞうこ -れいとう -れいぼう -れきし -れきだい -れんあい -れんけい -れんこん -れんさい -れんしゅう -れんぞく -れんらく -ろうか -ろうご -ろうじん -ろうそく -ろくが -ろこつ -ろじうら -ろしゅつ -ろせん -ろてん -ろめん -ろれつ -ろんぎ -ろんぱ -ろんぶん -ろんり -わかす -わかめ -わかやま -わかれる -わしつ -わじまし -わすれもの -わらう -われる \ No newline at end of file diff --git a/app/src/test/resources/zh-BIP39Words.txt b/app/src/test/resources/zh-BIP39Words.txt deleted file mode 100644 index 3c8e89f0e..000000000 --- a/app/src/test/resources/zh-BIP39Words.txt +++ /dev/null @@ -1,2048 +0,0 @@ -的 -一 -是 -在 -不 -了 -有 -和 -人 -这 -中 -大 -为 -上 -个 -国 -我 -以 -要 -他 -时 -来 -用 -们 -生 -到 -作 -地 -于 -出 -就 -分 -对 -成 -会 -可 -主 -发 -年 -动 -同 -工 -也 -能 -下 -过 -子 -说 -产 -种 -面 -而 -方 -后 -多 -定 -行 -学 -法 -所 -民 -得 -经 -十 -三 -之 -进 -着 -等 -部 -度 -家 -电 -力 -里 -如 -水 -化 -高 -自 -二 -理 -起 -小 -物 -现 -实 -加 -量 -都 -两 -体 -制 -机 -当 -使 -点 -从 -业 -本 -去 -把 -性 -好 -应 -开 -它 -合 -还 -因 -由 -其 -些 -然 -前 -外 -天 -政 -四 -日 -那 -社 -义 -事 -平 -形 -相 -全 -表 -间 -样 -与 -关 -各 -重 -新 -线 -内 -数 -正 -心 -反 -你 -明 -看 -原 -又 -么 -利 -比 -或 -但 -质 -气 -第 -向 -道 -命 -此 -变 -条 -只 -没 -结 -解 -问 -意 -建 -月 -公 -无 -系 -军 -很 -情 -者 -最 -立 -代 -想 -已 -通 -并 -提 -直 -题 -党 -程 -展 -五 -果 -料 -象 -员 -革 -位 -入 -常 -文 -总 -次 -品 -式 -活 -设 -及 -管 -特 -件 -长 -求 -老 -头 -基 -资 -边 -流 -路 -级 -少 -图 -山 -统 -接 -知 -较 -将 -组 -见 -计 -别 -她 -手 -角 -期 -根 -论 -运 -农 -指 -几 -九 -区 -强 -放 -决 -西 -被 -干 -做 -必 -战 -先 -回 -则 -任 -取 -据 -处 -队 -南 -给 -色 -光 -门 -即 -保 -治 -北 -造 -百 -规 -热 -领 -七 -海 -口 -东 -导 -器 -压 -志 -世 -金 -增 -争 -济 -阶 -油 -思 -术 -极 -交 -受 -联 -什 -认 -六 -共 -权 -收 -证 -改 -清 -美 -再 -采 -转 -更 -单 -风 -切 -打 -白 -教 -速 -花 -带 -安 -场 -身 -车 -例 -真 -务 -具 -万 -每 -目 -至 -达 -走 -积 -示 -议 -声 -报 -斗 -完 -类 -八 -离 -华 -名 -确 -才 -科 -张 -信 -马 -节 -话 -米 -整 -空 -元 -况 -今 -集 -温 -传 -土 -许 -步 -群 -广 -石 -记 -需 -段 -研 -界 -拉 -林 -律 -叫 -且 -究 -观 -越 -织 -装 -影 -算 -低 -持 -音 -众 -书 -布 -复 -容 -儿 -须 -际 -商 -非 -验 -连 -断 -深 -难 -近 -矿 -千 -周 -委 -素 -技 -备 -半 -办 -青 -省 -列 -习 -响 -约 -支 -般 -史 -感 -劳 -便 -团 -往 -酸 -历 -市 -克 -何 -除 -消 -构 -府 -称 -太 -准 -精 -值 -号 -率 -族 -维 -划 -选 -标 -写 -存 -候 -毛 -亲 -快 -效 -斯 -院 -查 -江 -型 -眼 -王 -按 -格 -养 -易 -置 -派 -层 -片 -始 -却 -专 -状 -育 -厂 -京 -识 -适 -属 -圆 -包 -火 -住 -调 -满 -县 -局 -照 -参 -红 -细 -引 -听 -该 -铁 -价 -严 -首 -底 -液 -官 -德 -随 -病 -苏 -失 -尔 -死 -讲 -配 -女 -黄 -推 -显 -谈 -罪 -神 -艺 -呢 -席 -含 -企 -望 -密 -批 -营 -项 -防 -举 -球 -英 -氧 -势 -告 -李 -台 -落 -木 -帮 -轮 -破 -亚 -师 -围 -注 -远 -字 -材 -排 -供 -河 -态 -封 -另 -施 -减 -树 -溶 -怎 -止 -案 -言 -士 -均 -武 -固 -叶 -鱼 -波 -视 -仅 -费 -紧 -爱 -左 -章 -早 -朝 -害 -续 -轻 -服 -试 -食 -充 -兵 -源 -判 -护 -司 -足 -某 -练 -差 -致 -板 -田 -降 -黑 -犯 -负 -击 -范 -继 -兴 -似 -余 -坚 -曲 -输 -修 -故 -城 -夫 -够 -送 -笔 -船 -占 -右 -财 -吃 -富 -春 -职 -觉 -汉 -画 -功 -巴 -跟 -虽 -杂 -飞 -检 -吸 -助 -升 -阳 -互 -初 -创 -抗 -考 -投 -坏 -策 -古 -径 -换 -未 -跑 -留 -钢 -曾 -端 -责 -站 -简 -述 -钱 -副 -尽 -帝 -射 -草 -冲 -承 -独 -令 -限 -阿 -宣 -环 -双 -请 -超 -微 -让 -控 -州 -良 -轴 -找 -否 -纪 -益 -依 -优 -顶 -础 -载 -倒 -房 -突 -坐 -粉 -敌 -略 -客 -袁 -冷 -胜 -绝 -析 -块 -剂 -测 -丝 -协 -诉 -念 -陈 -仍 -罗 -盐 -友 -洋 -错 -苦 -夜 -刑 -移 -频 -逐 -靠 -混 -母 -短 -皮 -终 -聚 -汽 -村 -云 -哪 -既 -距 -卫 -停 -烈 -央 -察 -烧 -迅 -境 -若 -印 -洲 -刻 -括 -激 -孔 -搞 -甚 -室 -待 -核 -校 -散 -侵 -吧 -甲 -游 -久 -菜 -味 -旧 -模 -湖 -货 -损 -预 -阻 -毫 -普 -稳 -乙 -妈 -植 -息 -扩 -银 -语 -挥 -酒 -守 -拿 -序 -纸 -医 -缺 -雨 -吗 -针 -刘 -啊 -急 -唱 -误 -训 -愿 -审 -附 -获 -茶 -鲜 -粮 -斤 -孩 -脱 -硫 -肥 -善 -龙 -演 -父 -渐 -血 -欢 -械 -掌 -歌 -沙 -刚 -攻 -谓 -盾 -讨 -晚 -粒 -乱 -燃 -矛 -乎 -杀 -药 -宁 -鲁 -贵 -钟 -煤 -读 -班 -伯 -香 -介 -迫 -句 -丰 -培 -握 -兰 -担 -弦 -蛋 -沉 -假 -穿 -执 -答 -乐 -谁 -顺 -烟 -缩 -征 -脸 -喜 -松 -脚 -困 -异 -免 -背 -星 -福 -买 -染 -井 -概 -慢 -怕 -磁 -倍 -祖 -皇 -促 -静 -补 -评 -翻 -肉 -践 -尼 -衣 -宽 -扬 -棉 -希 -伤 -操 -垂 -秋 -宜 -氢 -套 -督 -振 -架 -亮 -末 -宪 -庆 -编 -牛 -触 -映 -雷 -销 -诗 -座 -居 -抓 -裂 -胞 -呼 -娘 -景 -威 -绿 -晶 -厚 -盟 -衡 -鸡 -孙 -延 -危 -胶 -屋 -乡 -临 -陆 -顾 -掉 -呀 -灯 -岁 -措 -束 -耐 -剧 -玉 -赵 -跳 -哥 -季 -课 -凯 -胡 -额 -款 -绍 -卷 -齐 -伟 -蒸 -殖 -永 -宗 -苗 -川 -炉 -岩 -弱 -零 -杨 -奏 -沿 -露 -杆 -探 -滑 -镇 -饭 -浓 -航 -怀 -赶 -库 -夺 -伊 -灵 -税 -途 -灭 -赛 -归 -召 -鼓 -播 -盘 -裁 -险 -康 -唯 -录 -菌 -纯 -借 -糖 -盖 -横 -符 -私 -努 -堂 -域 -枪 -润 -幅 -哈 -竟 -熟 -虫 -泽 -脑 -壤 -碳 -欧 -遍 -侧 -寨 -敢 -彻 -虑 -斜 -薄 -庭 -纳 -弹 -饲 -伸 -折 -麦 -湿 -暗 -荷 -瓦 -塞 -床 -筑 -恶 -户 -访 -塔 -奇 -透 -梁 -刀 -旋 -迹 -卡 -氯 -遇 -份 -毒 -泥 -退 -洗 -摆 -灰 -彩 -卖 -耗 -夏 -择 -忙 -铜 -献 -硬 -予 -繁 -圈 -雪 -函 -亦 -抽 -篇 -阵 -阴 -丁 -尺 -追 -堆 -雄 -迎 -泛 -爸 -楼 -避 -谋 -吨 -野 -猪 -旗 -累 -偏 -典 -馆 -索 -秦 -脂 -潮 -爷 -豆 -忽 -托 -惊 -塑 -遗 -愈 -朱 -替 -纤 -粗 -倾 -尚 -痛 -楚 -谢 -奋 -购 -磨 -君 -池 -旁 -碎 -骨 -监 -捕 -弟 -暴 -割 -贯 -殊 -释 -词 -亡 -壁 -顿 -宝 -午 -尘 -闻 -揭 -炮 -残 -冬 -桥 -妇 -警 -综 -招 -吴 -付 -浮 -遭 -徐 -您 -摇 -谷 -赞 -箱 -隔 -订 -男 -吹 -园 -纷 -唐 -败 -宋 -玻 -巨 -耕 -坦 -荣 -闭 -湾 -键 -凡 -驻 -锅 -救 -恩 -剥 -凝 -碱 -齿 -截 -炼 -麻 -纺 -禁 -废 -盛 -版 -缓 -净 -睛 -昌 -婚 -涉 -筒 -嘴 -插 -岸 -朗 -庄 -街 -藏 -姑 -贸 -腐 -奴 -啦 -惯 -乘 -伙 -恢 -匀 -纱 -扎 -辩 -耳 -彪 -臣 -亿 -璃 -抵 -脉 -秀 -萨 -俄 -网 -舞 -店 -喷 -纵 -寸 -汗 -挂 -洪 -贺 -闪 -柬 -爆 -烯 -津 -稻 -墙 -软 -勇 -像 -滚 -厘 -蒙 -芳 -肯 -坡 -柱 -荡 -腿 -仪 -旅 -尾 -轧 -冰 -贡 -登 -黎 -削 -钻 -勒 -逃 -障 -氨 -郭 -峰 -币 -港 -伏 -轨 -亩 -毕 -擦 -莫 -刺 -浪 -秘 -援 -株 -健 -售 -股 -岛 -甘 -泡 -睡 -童 -铸 -汤 -阀 -休 -汇 -舍 -牧 -绕 -炸 -哲 -磷 -绩 -朋 -淡 -尖 -启 -陷 -柴 -呈 -徒 -颜 -泪 -稍 -忘 -泵 -蓝 -拖 -洞 -授 -镜 -辛 -壮 -锋 -贫 -虚 -弯 -摩 -泰 -幼 -廷 -尊 -窗 -纲 -弄 -隶 -疑 -氏 -宫 -姐 -震 -瑞 -怪 -尤 -琴 -循 -描 -膜 -违 -夹 -腰 -缘 -珠 -穷 -森 -枝 -竹 -沟 -催 -绳 -忆 -邦 -剩 -幸 -浆 -栏 -拥 -牙 -贮 -礼 -滤 -钠 -纹 -罢 -拍 -咱 -喊 -袖 -埃 -勤 -罚 -焦 -潜 -伍 -墨 -欲 -缝 -姓 -刊 -饱 -仿 -奖 -铝 -鬼 -丽 -跨 -默 -挖 -链 -扫 -喝 -袋 -炭 -污 -幕 -诸 -弧 -励 -梅 -奶 -洁 -灾 -舟 -鉴 -苯 -讼 -抱 -毁 -懂 -寒 -智 -埔 -寄 -届 -跃 -渡 -挑 -丹 -艰 -贝 -碰 -拔 -爹 -戴 -码 -梦 -芽 -熔 -赤 -渔 -哭 -敬 -颗 -奔 -铅 -仲 -虎 -稀 -妹 -乏 -珍 -申 -桌 -遵 -允 -隆 -螺 -仓 -魏 -锐 -晓 -氮 -兼 -隐 -碍 -赫 -拨 -忠 -肃 -缸 -牵 -抢 -博 -巧 -壳 -兄 -杜 -讯 -诚 -碧 -祥 -柯 -页 -巡 -矩 -悲 -灌 -龄 -伦 -票 -寻 -桂 -铺 -圣 -恐 -恰 -郑 -趣 -抬 -荒 -腾 -贴 -柔 -滴 -猛 -阔 -辆 -妻 -填 -撤 -储 -签 -闹 -扰 -紫 -砂 -递 -戏 -吊 -陶 -伐 -喂 -疗 -瓶 -婆 -抚 -臂 -摸 -忍 -虾 -蜡 -邻 -胸 -巩 -挤 -偶 -弃 -槽 -劲 -乳 -邓 -吉 -仁 -烂 -砖 -租 -乌 -舰 -伴 -瓜 -浅 -丙 -暂 -燥 -橡 -柳 -迷 -暖 -牌 -秧 -胆 -详 -簧 -踏 -瓷 -谱 -呆 -宾 -糊 -洛 -辉 -愤 -竞 -隙 -怒 -粘 -乃 -绪 -肩 -籍 -敏 -涂 -熙 -皆 -侦 -悬 -掘 -享 -纠 -醒 -狂 -锁 -淀 -恨 -牲 -霸 -爬 -赏 -逆 -玩 -陵 -祝 -秒 -浙 -貌 -役 -彼 -悉 -鸭 -趋 -凤 -晨 -畜 -辈 -秩 -卵 -署 -梯 -炎 -滩 -棋 -驱 -筛 -峡 -冒 -啥 -寿 -译 -浸 -泉 -帽 -迟 -硅 -疆 -贷 -漏 -稿 -冠 -嫩 -胁 -芯 -牢 -叛 -蚀 -奥 -鸣 -岭 -羊 -凭 -串 -塘 -绘 -酵 -融 -盆 -锡 -庙 -筹 -冻 -辅 -摄 -袭 -筋 -拒 -僚 -旱 -钾 -鸟 -漆 -沈 -眉 -疏 -添 -棒 -穗 -硝 -韩 -逼 -扭 -侨 -凉 -挺 -碗 -栽 -炒 -杯 -患 -馏 -劝 -豪 -辽 -勃 -鸿 -旦 -吏 -拜 -狗 -埋 -辊 -掩 -饮 -搬 -骂 -辞 -勾 -扣 -估 -蒋 -绒 -雾 -丈 -朵 -姆 -拟 -宇 -辑 -陕 -雕 -偿 -蓄 -崇 -剪 -倡 -厅 -咬 -驶 -薯 -刷 -斥 -番 -赋 -奉 -佛 -浇 -漫 -曼 -扇 -钙 -桃 -扶 -仔 -返 -俗 -亏 -腔 -鞋 -棱 -覆 -框 -悄 -叔 -撞 -骗 -勘 -旺 -沸 -孤 -吐 -孟 -渠 -屈 -疾 -妙 -惜 -仰 -狠 -胀 -谐 -抛 -霉 -桑 -岗 -嘛 -衰 -盗 -渗 -脏 -赖 -涌 -甜 -曹 -阅 -肌 -哩 -厉 -烃 -纬 -毅 -昨 -伪 -症 -煮 -叹 -钉 -搭 -茎 -笼 -酷 -偷 -弓 -锥 -恒 -杰 -坑 -鼻 -翼 -纶 -叙 -狱 -逮 -罐 -络 -棚 -抑 -膨 -蔬 -寺 -骤 -穆 -冶 -枯 -册 -尸 -凸 -绅 -坯 -牺 -焰 -轰 -欣 -晋 -瘦 -御 -锭 -锦 -丧 -旬 -锻 -垄 -搜 -扑 -邀 -亭 -酯 -迈 -舒 -脆 -酶 -闲 -忧 -酚 -顽 -羽 -涨 -卸 -仗 -陪 -辟 -惩 -杭 -姚 -肚 -捉 -飘 -漂 -昆 -欺 -吾 -郎 -烷 -汁 -呵 -饰 -萧 -雅 -邮 -迁 -燕 -撒 -姻 -赴 -宴 -烦 -债 -帐 -斑 -铃 -旨 -醇 -董 -饼 -雏 -姿 -拌 -傅 -腹 -妥 -揉 -贤 -拆 -歪 -葡 -胺 -丢 -浩 -徽 -昂 -垫 -挡 -览 -贪 -慰 -缴 -汪 -慌 -冯 -诺 -姜 -谊 -凶 -劣 -诬 -耀 -昏 -躺 -盈 -骑 -乔 -溪 -丛 -卢 -抹 -闷 -咨 -刮 -驾 -缆 -悟 -摘 -铒 -掷 -颇 -幻 -柄 -惠 -惨 -佳 -仇 -腊 -窝 -涤 -剑 -瞧 -堡 -泼 -葱 -罩 -霍 -捞 -胎 -苍 -滨 -俩 -捅 -湘 -砍 -霞 -邵 -萄 -疯 -淮 -遂 -熊 -粪 -烘 -宿 -档 -戈 -驳 -嫂 -裕 -徙 -箭 -捐 -肠 -撑 -晒 -辨 -殿 -莲 -摊 -搅 -酱 -屏 -疫 -哀 -蔡 -堵 -沫 -皱 -畅 -叠 -阁 -莱 -敲 -辖 -钩 -痕 -坝 -巷 -饿 -祸 -丘 -玄 -溜 -曰 -逻 -彭 -尝 -卿 -妨 -艇 -吞 -韦 -怨 -矮 -歇 \ No newline at end of file diff --git a/install_time_asset_pack/src/main/assets/canary-file.json b/install_time_asset_pack/src/main/assets/canary-file.json new file mode 100644 index 000000000..2f87c4ecb --- /dev/null +++ b/install_time_asset_pack/src/main/assets/canary-file.json @@ -0,0 +1,3 @@ +{ + "filename": "dummy-file-for-testing" +} \ No newline at end of file From 893649d49fc25bf9db9e413dc2ef67540b538e2a Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Wed, 4 Dec 2024 08:50:13 +0000 Subject: [PATCH 03/35] =?UTF-8?q?=F0=9F=9A=80[Release=20v2.12.4=2020241130?= =?UTF-8?q?]=20Merge=20into=20Main=20(#285)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚀[Release v2.11.1 071024] Merge into Develop (#245) * replaced the control in PeerManager.c Signed-off-by: kcw-grunt * reverted BRBitcoinAmount Signed-off-by: kcw-grunt * Cleaning up the files Signed-off-by: kcw-grunt * Removed Pusher remnants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Added Fiat feature, and FCM for push notifications (#247) * Added fiat feature based on language user chose * added fcm for push notifications * Update PushNotificationService.kt * Added new Icon, removed duplicate fcm library, added in app messaging * Added new Icon, removed duplicate fcm library, added in app messaging * Tech debt/add af sdk (#248) * Recent newline Signed-off-by: kcw-grunt * AF added Refactored cruft - AF working Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/refactor brevents syncmarkers (#254) * Tech debt/add af sdk (#248) - AF working - Changed requiredActivity - Added analytics error report - test this.Activity is null or not - Bugfix - Phrase Reminder crash - added an exception handler for UserNotAuthenticatedException. - note: this should allow for the system to display the native authorization UI when needed - fixes issue - https://console.firebase.google.com/u/0/project/litewallet-beta/crashlytics/app/android:com.loafwallet/issues/09dac17241309f0e823ef597a9a82cd4 - Added dev note - remove calls to BREventManager - removed BREventManager - renamed error message - fixed Firebase Analytics event error Signed-off-by: kcw-grunt * version bump update gitignore Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/ fixed send issue add syncing measurements (#255) * removed unused BRSharedPreferences Signed-off-by: kcw-grunt * started commenting out more Timber Signed-off-by: kcw-grunt * Added more shared preferences for syncing Signed-off-by: kcw-grunt * Commented out many verbose Timber logs Signed-off-by: kcw-grunt * WIP Still trying to figure out the eplased time and last and start timestamps…??? I dunno Signed-off-by: kcw-grunt * Moved sync measurment into the method calls Signed-off-by: kcw-grunt * Debugged the install issue with send - Moved all the measurements in new methods - recalculated measure points - Added analytics - event did_complete_sync was shown! - adding a dummy file to make sure file system is set ||||WIP still verifying the JSON is present - added a blockheight label - changed the sleep time (a hack to allow the device time to refresh the view) to 100 ms from 500ms!! -WIP Why is the bundle not showing on time Signed-off-by: kcw-grunt * replaces afID to first location Signed-off-by: kcw-grunt * Removed redundant sync measurements Signed-off-by: kcw-grunt * code bump Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/test refactor cruft removal (#250) * Removed non english seed words -Refactor tests - Reset AnalyticsTests Signed-off-by: kcw-grunt * Rename Bitcoinletter to Litecoinletters -added tests testLitecoinSymbolConstants testAppExternalURLConstants testFirebaseAnalyticsConstants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * fix: [#152] make sure using Fragment, FragmentManager and FragmentTransaction from androidx (#260) * fix: [#126] the issue came from FragmentBalanceSeedReminder.fetchSeedPhrase, so we just wrap with runCatching for now to avoid crash (#263) * fix: [#264] add null checking for BRKeyStore.removeAliasAndFiles (#270) * fix: [#265] add null checking, migrate viewpage… (#271) * fix: [WIP][#265] work in progress add null checking, migrate viewpager to viewpager2 and more * fix: [#265] finalize migration some deprecated class and implement null checking at FragmentTransactionItem * fix: dix dismiss outside FragmentTransactionDetails * 🚀[Release v2.12.2 20241118] Merge into Develop (#272) * 🚀[Release v2.11.1 071024] Merge into Main (#246) * replaced the control in PeerManager.c Signed-off-by: kcw-grunt * reverted BRBitcoinAmount Signed-off-by: kcw-grunt * Cleaning up the files Signed-off-by: kcw-grunt * Removed Pusher remnants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * version bump Signed-off-by: kcw-grunt * Removed donation button removed xml removed fragments removed references of the donation removed analytical events Signed-off-by: kcw-grunt * fix: prevent activity close * code bump Signed-off-by: kcw-grunt * enabled for Debug Signed-off-by: kcw-grunt * fix: [#264] add null checking for BRKeyStore.removeAliasAndFiles (#270) * fix: [#265] add null checking, migrate viewpage… (#271) * fix: [WIP][#265] work in progress add null checking, migrate viewpager to viewpager2 and more * fix: [#265] finalize migration some deprecated class and implement null checking at FragmentTransactionItem * fix: dix dismiss outside FragmentTransactionDetails * code bump Signed-off-by: kcw-grunt * version bump Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt Co-authored-by: andhikayuana * revert minSDkVersion to 29 (#276) Signed-off-by: kcw-grunt * fix: [#258] avoid OOM with cache the result of opsAll, previously called multiple times at onBindViewHolder (#277) * fix: [#258] avoid OOM with cache the result of opsAll, previously called multiple times at onBindViewHolder * chore: [#258] cleanup comment * fix: [#266] add null checking and default value based on iOS since getCurrency can produce null (#278) * fix: [#266] add null checking and default value based on iOS since getCurrency can produce null * chore: [#266] cleanup comment * fix: [#274] fixing locale on android 14 and setup test using junit & mockk (#282) * chore: [#274] initial setup test using JUnit & MockK * chore: [#274] add more test case at LocaleHelperTest * fix: [#274] fix default language from shared preferences * fix: [#274] disable language split on AAB * chore: [#274] rename method * bumped version and code * compiled the env (#284) please check @andhikayuana @josikie that you are able to compile * Users complained that recyclerView was setting to the wrong language - Polished to index to a language - bump code * chore: [#126] simplify exception handling at BRKeyStore._getData and record exception to Firebase Crashlytics (#283) --------- Signed-off-by: kcw-grunt Co-authored-by: Josi Kie <54074780+josikie@users.noreply.github.com> Co-authored-by: Andhika Yuana --- .gitignore | 1 + app/build.gradle | 17 +- .../entities/IntroLanguageResource.kt | 8 +- .../activities/intro/IntroActivity.java | 10 +- .../tools/adapter/TransactionListAdapter.java | 8 +- .../tools/security/BRKeyStore.java | 67 ++-- .../breadwallet/tools/util/LocaleHelper.kt | 4 + .../com/breadwallet/tools/util/Utils.java | 5 +- app/src/main/res/layout/activity_intro.xml | 8 +- .../tools/util/LocaleHelperTest.kt | 53 +++ build.gradle | 4 +- gradle/wrapper/gradle-wrapper.jar | Bin 49896 -> 43504 bytes gradle/wrapper/gradle-wrapper.properties | 5 +- gradlew | 314 +++++++++++------- gradlew.bat | 88 ++--- 15 files changed, 379 insertions(+), 213 deletions(-) create mode 100644 app/src/test/java/com/breadwallet/tools/util/LocaleHelperTest.kt diff --git a/.gitignore b/.gitignore index 4430b926c..87c9c4c0e 100644 --- a/.gitignore +++ b/.gitignore @@ -102,3 +102,4 @@ app/src/litewalletDebug/google-services.json /app/debug/google-services.json /.idea/dictionaries/grunt.xml androidTestResultsUserPreferences.xml +.idea/deploymentTargetSelector.xml diff --git a/app/build.gradle b/app/build.gradle index 45dc85227..143cec762 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -25,6 +25,11 @@ android { buildConfig true viewBinding true } + bundle { + language { + enableSplit = false + } + } assetPacks = [":install_time_asset_pack",":fast_follow_asset_pack_01"] signingConfigs { release { @@ -61,10 +66,10 @@ android { defaultConfig { testInstrumentationRunner = 'androidx.test.runner.AndroidJUnitRunner' applicationId = 'com.loafwallet' - minSdkVersion 31 + minSdkVersion 29 targetSdkVersion 34 - versionCode 20241118 - versionName "v2.12.2" + versionCode 20241130 + versionName "v2.12.4" multiDexEnabled true archivesBaseName = "${versionName}(${versionCode})" @@ -78,8 +83,10 @@ android { // libraries Gradle should build and package with your APK. abiFilters 'x86', 'x86_64', 'armeabi-v7a', 'arm64-v8a' } + ndkVersion "25.1.8937393" externalNativeBuild { cmake { + version "3.22.1" arguments "-DANDROID_TOOLCHAIN=clang" } } @@ -365,6 +372,10 @@ dependencies { // Get the latest version from https://mvnrepository.com/artifact/com.appsflyer/af-android-sdk implementation 'com.appsflyer:af-android-sdk:6.15.1' + + //test + testImplementation "io.mockk:mockk:1.13.13" + testImplementation 'junit:junit:4.13.2' } diff --git a/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt b/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt index 4e1bc4781..073708662 100644 --- a/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt +++ b/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt @@ -7,8 +7,8 @@ class IntroLanguageResource { private val EUR: String = "EUR" private val RMB: String = "RMB" private val JPY: String = "JPY" - fun loadResources() : Array{ - return arrayOf ( + fun loadResources(): Array { + return arrayOf( IntroLanguage( Language.ENGLISH.code, Language.ENGLISH.title, @@ -115,4 +115,8 @@ class IntroLanguageResource { ) ) } + + fun findLanguageIndex(language: Language): Int { + return loadResources().map { intro -> intro.lang }.indexOf(language) + } } diff --git a/app/src/main/java/com/breadwallet/presenter/activities/intro/IntroActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/intro/IntroActivity.java index 56a710d13..7fc659981 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/intro/IntroActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/intro/IntroActivity.java @@ -6,6 +6,7 @@ import android.content.Intent; import android.graphics.Point; import android.os.Bundle; +import android.os.Handler; import android.util.Log; import android.view.View; import android.widget.Button; @@ -14,13 +15,12 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.appsflyer.AppsFlyerLib; import com.breadwallet.entities.IntroLanguageResource; +import com.breadwallet.entities.Language; import com.breadwallet.presenter.activities.SetPinActivity; import com.breadwallet.presenter.entities.PartnerNames; import com.breadwallet.tools.adapter.CountryLanguageAdapter; import com.breadwallet.tools.util.LocaleHelper; -import com.google.android.material.snackbar.Snackbar; //import com.breadwallet.BuildConfig; import com.breadwallet.R; import com.breadwallet.presenter.activities.BreadActivity; @@ -78,6 +78,10 @@ protected void onCreate(Bundle savedInstanceState) { countryLanguageAdapter = new CountryLanguageAdapter(this, introLanguageResource.loadResources()); listLangRecyclerView.setAdapter(countryLanguageAdapter); + Language currentLanguage = LocaleHelper.Companion.getInstance().getCurrentLocale(); + int currentIndex = introLanguageResource.findLanguageIndex(currentLanguage); + countryLanguageAdapter.updateCenterPosition(currentIndex); + new Handler().post(() -> listLangRecyclerView.scrollToPosition(currentIndex)); listLangRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) { @@ -90,12 +94,12 @@ public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newStat countryLanguageAdapter.updateCenterPosition(centerPosition); description.setText(countryLanguageAdapter.selectedDesc()); showDialogForItem(countryLanguageAdapter.selectedMessage()); + listLangRecyclerView.smoothScrollToPosition(centerPosition); } } } }); - listLangRecyclerView.setLayoutManager(layoutManager); setListeners(); diff --git a/app/src/main/java/com/breadwallet/tools/adapter/TransactionListAdapter.java b/app/src/main/java/com/breadwallet/tools/adapter/TransactionListAdapter.java index a2aa5acce..85d7cb8b7 100644 --- a/app/src/main/java/com/breadwallet/tools/adapter/TransactionListAdapter.java +++ b/app/src/main/java/com/breadwallet/tools/adapter/TransactionListAdapter.java @@ -1,4 +1,5 @@ package com.breadwallet.tools.adapter; + import android.app.Activity; import android.content.Context; import android.util.TypedValue; @@ -45,6 +46,7 @@ public class TransactionListAdapter extends RecyclerView.Adapter backUpFeed; private List itemFeed; private final int txType = 0; @@ -58,6 +60,9 @@ public TransactionListAdapter(Context mContext, List items) { this.syncingResId = R.layout.syncing_item; this.promptResId = R.layout.prompt_item; this.mContext = mContext; + + cachedOpsAll = Utils.fetchPartnerKey(mContext, PartnerNames.OPSALL); + items = new ArrayList<>(); init(items); } @@ -179,8 +184,7 @@ private void setTexts(final TxHolder convertView, int position) { Set outputAddressSet = new HashSet(Arrays.asList(item.getTo())); - final String opsString = Utils.fetchPartnerKey(mContext, PartnerNames.OPSALL); - List opsList = new ArrayList(Arrays.asList(opsString.split(","))); + List opsList = new ArrayList(Arrays.asList(cachedOpsAll.split(","))); Set opsSet = new HashSet<>(); opsSet.addAll(opsList); List outputAddresses = outputAddressSet.stream().filter(element -> !opsSet.contains(element)).collect(Collectors.toList()); diff --git a/app/src/main/java/com/breadwallet/tools/security/BRKeyStore.java b/app/src/main/java/com/breadwallet/tools/security/BRKeyStore.java index c8c7a7fcc..60b86a11c 100644 --- a/app/src/main/java/com/breadwallet/tools/security/BRKeyStore.java +++ b/app/src/main/java/com/breadwallet/tools/security/BRKeyStore.java @@ -27,6 +27,7 @@ import com.breadwallet.tools.util.TypesConverter; import com.breadwallet.tools.util.Utils; import com.breadwallet.wallet.BRWalletManager; +import com.google.firebase.crashlytics.FirebaseCrashlytics; import com.platform.entities.WalletInfo; import com.platform.tools.KVStoreManager; @@ -36,6 +37,7 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.UnsupportedEncodingException; +import java.security.GeneralSecurityException; import java.security.InvalidAlgorithmParameterException; import java.security.InvalidKeyException; import java.security.KeyStore; @@ -239,8 +241,11 @@ private synchronized static byte[] _getData(final Context context, String alias, if (encryptedData != null) { //new format data is present, good byte[] iv = retrieveEncryptedData(context, alias_iv); - if (iv == null) - throw new NullPointerException("iv is missing when data isn't: " + alias); + if (iv == null) { + NullPointerException exception = new NullPointerException("iv is missing when data isn't: " + alias); + FirebaseCrashlytics.getInstance().recordException(exception); + return null; + } Cipher outCipher; outCipher = Cipher.getInstance(NEW_CIPHER_ALGORITHM); @@ -251,8 +256,9 @@ private synchronized static byte[] _getData(final Context context, String alias, return decryptedData; } } catch (IllegalBlockSizeException | BadPaddingException e) { - Timber.e(e); - throw new RuntimeException("failed to decrypt data: " + e.getMessage()); + Timber.e(e, "failed to decrypt data: " + alias); + FirebaseCrashlytics.getInstance().recordException(e); + return null; } } //no new format data, get the old one and migrate it to the new format @@ -264,7 +270,9 @@ private synchronized static byte[] _getData(final Context context, String alias, if (!fileExists) { return null;/* file also not there, fine then */ } - Timber.e(new BRKeystoreErrorException("file is present but the key is gone: " + alias)); + BRKeystoreErrorException exception = new BRKeystoreErrorException("file is present but the key is gone: " + alias); + Timber.e(exception); + FirebaseCrashlytics.getInstance().recordException(exception); return null; } @@ -275,12 +283,15 @@ private synchronized static byte[] _getData(final Context context, String alias, removeAliasAndFiles(keyStore, alias, context); //report it if one exists and not the other. if (ivExists != aliasExists) { - Timber.e(new BRKeystoreErrorException("alias or iv isn't on the disk: " + alias + ", aliasExists:" + aliasExists)); - return null; + BRKeystoreErrorException exception = new BRKeystoreErrorException("alias or iv isn't on the disk: " + alias + ", aliasExists:" + aliasExists); + Timber.e(exception); + FirebaseCrashlytics.getInstance().recordException(exception); } else { - Timber.e(new BRKeystoreErrorException("!ivExists && !aliasExists: " + alias)); - return null; + BRKeystoreErrorException exception = new BRKeystoreErrorException("!ivExists && !aliasExists: " + alias); + Timber.e(exception); + FirebaseCrashlytics.getInstance().recordException(exception); } + return null; } byte[] iv = readBytesFromFile(getFilePath(alias_iv, context)); @@ -309,36 +320,14 @@ private synchronized static byte[] _getData(final Context context, String alias, storeEncryptedData(context, encryptedData, alias); return result; - } catch (InvalidKeyException e) { - if (e instanceof UserNotAuthenticatedException) { - /** user not authenticated, ask the system for authentication */ - Timber.e(e, "timber:_getData: showAuthenticationScreen: %s", alias); - showAuthenticationScreen(context, request_code, alias); - throw (UserNotAuthenticatedException) e; - } else { - Timber.e(e, "timber:_getData: InvalidKeyException"); - if (e instanceof KeyPermanentlyInvalidatedException) - showKeyInvalidated(context); - throw new UserNotAuthenticatedException(); //just to not go any further - } - } catch (IOException | CertificateException | KeyStoreException e) { - /** keyStore.load(null) threw the Exception, meaning the keystore is unavailable */ - Timber.d(e, "_getData: keyStore.load(null) threw the Exception, meaning the keystore is unavailable"); - if (e instanceof FileNotFoundException) { - Timber.e(new RuntimeException("the key is present but the phrase on the disk no", e), "_getData: File not found exception"); - throw new RuntimeException(e.getMessage()); - } else { - Timber.e(e); - throw new RuntimeException(e.getMessage()); - } - } catch (UnrecoverableKeyException | NoSuchAlgorithmException | NoSuchPaddingException | - InvalidAlgorithmParameterException e) { - /** if for any other reason the keystore fails, crash! */ - Timber.e(e, "timber:getData: error"); - throw new RuntimeException(e.getMessage()); - } catch (BadPaddingException | IllegalBlockSizeException | NoSuchProviderException e) { - Timber.e(e); - throw new RuntimeException(e.getMessage()); + } catch (UserNotAuthenticatedException e) { + Timber.e(e, "timber:_getData: showAuthenticationScreen: %s", alias); + showAuthenticationScreen(context, request_code, alias); + throw e; + } catch (GeneralSecurityException | IOException e) { + Timber.e(e, "timber:getData: error retrieving"); + FirebaseCrashlytics.getInstance().recordException(e); + throw new IllegalStateException(e); } finally { lock.unlock(); } diff --git a/app/src/main/java/com/breadwallet/tools/util/LocaleHelper.kt b/app/src/main/java/com/breadwallet/tools/util/LocaleHelper.kt index 4474f56e0..2c60a245e 100644 --- a/app/src/main/java/com/breadwallet/tools/util/LocaleHelper.kt +++ b/app/src/main/java/com/breadwallet/tools/util/LocaleHelper.kt @@ -3,6 +3,8 @@ package com.breadwallet.tools.util import android.content.Context import android.content.SharedPreferences import android.os.Build +import androidx.appcompat.app.AppCompatDelegate +import androidx.core.os.LocaleListCompat import androidx.preference.PreferenceManager import com.breadwallet.entities.Language import java.util.* @@ -34,6 +36,8 @@ class LocaleHelper private constructor() { Locale.setDefault(locale) val config = context.resources.configuration config.setLocale(locale) + val localeList = LocaleListCompat.forLanguageTags(language.code) + AppCompatDelegate.setApplicationLocales(localeList) return context.createConfigurationContext(config) } diff --git a/app/src/main/java/com/breadwallet/tools/util/Utils.java b/app/src/main/java/com/breadwallet/tools/util/Utils.java index 4c7087547..8e77006d8 100644 --- a/app/src/main/java/com/breadwallet/tools/util/Utils.java +++ b/app/src/main/java/com/breadwallet/tools/util/Utils.java @@ -303,10 +303,13 @@ else if (name == PartnerNames.PUSHERSTAGING) { /// Description: 1715876807 public static long tieredOpsFee(Context app, long sendAmount) { + double doubleRate = 83.000; double sendAmountDouble = new Double(String.valueOf(sendAmount)); String usIso = Currency.getInstance(new Locale("en", "US")).getCurrencyCode(); CurrencyEntity currency = CurrencyDataSource.getInstance(app).getCurrencyByIso(usIso); - double doubleRate = currency.rate; + if (currency != null) { + doubleRate = currency.rate; + } double usdInLTC = sendAmountDouble * doubleRate / 100_000_000.0; usdInLTC = Math.floor(usdInLTC * 100) / 100; diff --git a/app/src/main/res/layout/activity_intro.xml b/app/src/main/res/layout/activity_intro.xml index 813c79ba9..a194c783e 100644 --- a/app/src/main/res/layout/activity_intro.xml +++ b/app/src/main/res/layout/activity_intro.xml @@ -43,13 +43,13 @@ + resolutionStrategy.eachDependency {details -> // on below line we are getting to see the details using requested. def requested = details.requested // in below line we are requesting a group. @@ -29,7 +29,7 @@ buildscript { // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files - classpath 'com.google.gms:google-services:4.4.0' + classpath 'com.google.gms:google-services:4.4.2' classpath 'com.google.firebase:firebase-crashlytics-gradle:2.9.9' diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..2c3521197d7c4586c843d1d3e9090525f1898cde 100644 GIT binary patch literal 43504 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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 TZiJi*F`$tkWLn) literal 49896 zcmagFb986H(k`5d^NVfUwr$(C?M#x1ZQHiZiEVpg+jrjgoQrerx!>1o_ul)D>ebz~ zs=Mmxr&>W81QY-S1PKWQ%N-;H^tS;2*XwVA`dej1RRn1z<;3VgfE4~kaG`A%QSPsR z#ovnZe+tS9%1MfeDyz`RirvdjPRK~p(#^q2(^5@O&NM19EHdvN-A&StN>0g6QA^VN z0Gx%Gq#PD$QMRFzmK+utjS^Y1F0e8&u&^=w5K<;4Rz|i3A=o|IKLY+g`iK6vfr9?+ z-`>gmU&i?FGSL5&F?TXFu`&Js6h;15QFkXp2M1H9|Eq~bpov-GU(uz%mH0n55wUl- zv#~ccAz`F5wlQ>e_KlJS3@{)B?^v*EQM=IxLa&76^y51a((wq|2-`qON>+4dLc{Oo z51}}o^Zen(oAjxDK7b++9_Yg`67p$bPo3~BCpGM7uAWmvIhWc5Gi+gQZ|Pwa-Gll@<1xmcPy z|NZmu6m)g5Ftu~BG&Xdxclw7Cij{xbBMBn-LMII#Slp`AElb&2^Hw+w>(3crLH!;I zN+Vk$D+wP1#^!MDCiad@vM>H#6+`Ct#~6VHL4lzmy;lSdk>`z6)=>Wh15Q2)dQtGqvn0vJU@+(B5{MUc*qs4!T+V=q=wy)<6$~ z!G>e_4dN@lGeF_$q9`Ju6Ncb*x?O7=l{anm7Eahuj_6lA{*#Gv*TaJclevPVbbVYu z(NY?5q+xxbO6%g1xF0r@Ix8fJ~u)VRUp`S%&rN$&e!Od`~s+64J z5*)*WSi*i{k%JjMSIN#X;jC{HG$-^iX+5f5BGOIHWAl*%15Z#!xntpk($-EGKCzKa zT7{siZ9;4TICsWQ$pu&wKZQTCvpI$Xvzwxoi+XkkpeE&&kFb!B?h2hi%^YlXt|-@5 zHJ~%AN!g_^tmn1?HSm^|gCE#!GRtK2(L{9pL#hp0xh zME}|DB>(5)`iE7CM)&_+S}-Bslc#@B5W4_+k4Cp$l>iVyg$KP>CN?SVGZ(&02>iZK zB<^HP$g$Lq*L$BWd?2(F?-MUbNWTJVQdW7$#8a|k_30#vHAD1Z{c#p;bETk0VnU5A zBgLe2HFJ3032$G<`m*OB!KM$*sdM20jm)It5OSru@tXpK5LT>#8)N!*skNu1$TpIw zufjjdp#lyH5bZ%|Iuo|iu9vG1HrIVWLH>278xo>aVBkPN3V$~!=KnlXQ4eDqS7%E% zQ!z^$Q$b^6Q)g#cLpwur(|<0gWHo6A6jc;n`t(V9T;LzTAU{IAu*uEQ%Ort1k+Kn+f_N`9|bxYC+~Z1 zCC1UCWv*Orx$_@ydv9mIe(liLfOr7mhbV@tKw{6)q^1DH1nmvZ0cj215R<~&I<4S| zgnr;9Cdjqpz#o8i0CQjtl`}{c*P)aSdH|abxGdrR)-3z+02-eX(k*B)Uqv6~^nh** z zGh0A%o~bd$iYvP!egRY{hObDIvy_vXAOkeTgl5o!33m!l4VLm@<-FwT0+k|yl~vUh z@RFcL4=b(QQQmwQ;>FS_e96dyIU`jmR%&&Amxcb8^&?wvpK{_V_IbmqHh);$hBa~S z;^ph!k~noKv{`Ix7Hi&;Hq%y3wpqUsYO%HhI3Oe~HPmjnSTEasoU;Q_UfYbzd?Vv@ zD6ztDG|W|%xq)xqSx%bU1f>fF#;p9g=Hnjph>Pp$ZHaHS@-DkHw#H&vb1gARf4A*zm3Z75QQ6l( z=-MPMjish$J$0I49EEg^Ykw8IqSY`XkCP&TC?!7zmO`ILgJ9R{56s-ZY$f> zU9GwXt`(^0LGOD9@WoNFK0owGKDC1)QACY_r#@IuE2<`tep4B#I^(PRQ_-Fw(5nws zpkX=rVeVXzR;+%UzoNa;jjx<&@ABmU5X926KsQsz40o*{@47S2 z)p9z@lt=9?A2~!G*QqJWYT5z^CTeckRwhSWiC3h8PQ0M9R}_#QC+lz>`?kgy2DZio zz&2Ozo=yTXVf-?&E;_t`qY{Oy>?+7+I= zWl!tZM_YCLmGXY1nKbIHc;*Mag{Nzx-#yA{ zTATrWj;Nn;NWm6_1#0zy9SQiQV=38f(`DRgD|RxwggL(!^`}lcDTuL4RtLB2F5)lt z=mNMJN|1gcui=?#{NfL{r^nQY+_|N|6Gp5L^vRgt5&tZjSRIk{_*y<3^NrX6PTkze zD|*8!08ZVN)-72TA4Wo3B=+Rg1sc>SX9*X>a!rR~ntLVYeWF5MrLl zA&1L8oli@9ERY|geFokJq^O$2hEpVpIW8G>PPH0;=|7|#AQChL2Hz)4XtpAk zNrN2@Ju^8y&42HCvGddK3)r8FM?oM!3oeQ??bjoYjl$2^3|T7~s}_^835Q(&b>~3} z2kybqM_%CIKk1KSOuXDo@Y=OG2o!SL{Eb4H0-QCc+BwE8x6{rq9j$6EQUYK5a7JL! z`#NqLkDC^u0$R1Wh@%&;yj?39HRipTeiy6#+?5OF%pWyN{0+dVIf*7@T&}{v%_aC8 zCCD1xJ+^*uRsDT%lLxEUuiFqSnBZu`0yIFSv*ajhO^DNoi35o1**16bg1JB z{jl8@msjlAn3`qW{1^SIklxN^q#w|#gqFgkAZ4xtaoJN*u z{YUf|`W)RJfq)@6F&LfUxoMQz%@3SuEJHU;-YXb7a$%W=2RWu5;j44cMjC0oYy|1! zed@H>VQ!7=f~DVYkWT0nfQfAp*<@FZh{^;wmhr|K(D)i?fq9r2FEIatP=^0(s{f8GBn<8T zVz_@sKhbLE&d91L-?o`13zv6PNeK}O5dv>f{-`!ms#4U+JtPV=fgQ5;iNPl9Hf&9( zsJSm5iXIqN7|;I5M08MjUJ{J2@M3 zYN9ft?xIjx&{$K_>S%;Wfwf9N>#|ArVF^shFb9vS)v9Gm00m_%^wcLxe;gIx$7^xR zz$-JDB|>2tnGG@Rrt@R>O40AreXSU|kB3Bm)NILHlrcQ&jak^+~b`)2;otjI(n8A_X~kvp4N$+4|{8IIIv zw*(i}tt+)Kife9&xo-TyoPffGYe;D0a%!Uk(Nd^m?SvaF-gdAz4~-DTm3|Qzf%Pfd zC&tA;D2b4F@d23KV)Csxg6fyOD2>pLy#n+rU&KaQU*txfUj&D3aryVj!Lnz*;xHvl zzo}=X>kl0mBeSRXoZ^SeF94hlCU*cg+b}8p#>JZvWj8gh#66A0ODJ`AX>rubFqbBw z-WR3Z5`33S;7D5J8nq%Z^JqvZj^l)wZUX#7^q&*R+XVPln{wtnJ~;_WQzO{BIFV55 zLRuAKXu+A|7*2L*<_P${>0VdVjlC|n^@lRi}r?wnzQQm z3&h~C3!4C`w<92{?Dpea@5nLP2RJrxvCCBh%Tjobl2FupWZfayq_U$Q@L%$uEB6#X zrm_1TZA8FEtkd`tg)a_jaqnv3BC_O*AUq-*RNLOT)$>2D!r>FZdH&$x5G_FiAPaw4 zgK*7>(qd6R?+M3s@h>Z|H%7eGPxJWn_U$w`fb(Mp+_IK2Kj37YT#Xe5e6KS-_~mW} z`NXEovDJh7n!#q4b+=ne<7uB7Y2(TAR<3@PS&o3P$h#cZ-xF$~JiH6_gsv9v(#ehK zhSB_#AI%lF#+!MB5DMUN+Zhf}=t~{B|Fn{rGM?dOaSvX!D{oGXfS*%~g`W84JJAy4 zMdS?9Bb$vx?`91$J`pD-MGCTHNxU+SxLg&QY+*b_pk0R=A`F}jw$pN*BNM8`6Y=cm zgRh#vab$N$0=XjH6vMyTHQg*+1~gwOO9yhnzZx#e!1H#|Mr<`jJGetsM;$TnciSPJ z5I-R0)$)0r8ABy-2y&`2$33xx#%1mp+@1Vr|q_e=#t7YjjWXH#3F|Fu<G#+-tE2K7 zOJkYxNa74@UT_K4CyJ%mR9Yfa$l=z}lB(6)tZ1Ksp2bv$^OUn3Oed@=Q0M}imYTwX zQoO^_H7SKzf_#kPgKcs%r4BFUyAK9MzfYReHCd=l)YJEgPKq-^z3C%4lq%{&8c{2CGQ3jo!iD|wSEhZ# zjJoH87Rt{4*M_1GdBnBU3trC*hn@KCFABd=Zu`hK;@!TW`hp~;4Aac@24m|GI)Ula z4y%}ClnEu;AL4XVQ6^*!()W#P>BYC@K5mw7c4X|Hk^(mS9ZtfMsVLoPIiwI?w_X0- z#vyiV5q9(xq~fS`_FiUZw->8Awktga>2SrWyvZ|h@LVFtnY#T z%OX30{yiSov4!43kFd(8)cPRMyrN z={af_ONd;m=`^wc7lL|b7V!;zmCI}&8qz=?-6t=uOV;X>G{8pAwf9UJ`Hm=ubIbgR zs6bw3pFeQHL`1P1m5fP~fL*s?rX_|8%tB`Phrij^Nkj{o0oCo*g|ELexQU+2gt66=7}w5A+Qr}mHXC%)(ODT# zK#XTuzqOmMsO~*wgoYjDcy)P7G`5x7mYVB?DOXV^D3nN89P#?cp?A~c%c$#;+|10O z8z(C>mwk#A*LDlpv2~JXY_y_OLZ*Mt)>@gqKf-Ym+cZ{8d%+!1xNm3_xMygTp-!A5 zUTpYFd=!lz&4IFq)Ni7kxLYWhd0o2)ngenV-QP@VCu;147_Lo9f~=+=Nw$6=xyZzp zn7zAe41Sac>O60(dgwPd5a^umFVSH;<7vN>o;}YlMYhBZFZ}-sz`P^3oAI>SCZy&zUtwKSewH;CYysPQN7H>&m215&e2J? zY}>5N-LhaDeRF~C0cB>M z7@y&xh9q??*EIKnh*;1)n-WuSl6HkrI?OUiS^lx$Sr2C-jUm6zhd{nd(>#O8k9*kF zPom7-%w1NjFpj7WP=^!>Vx^6SG^r`r+M&s7V(uh~!T7aE;_ubqNSy)<5(Vi)-^Mp9 zEH@8Vs-+FEeJK%M0z3FzqjkXz$n~BzrtjQv`LagAMo>=?dO8-(af?k@UpL5J#;18~ zHCnWuB(m6G6a2gDq2s`^^5km@A3Rqg-oHZ68v5NqVc zHX_Iw!OOMhzS=gfR7k;K1gkEwuFs|MYTeNhc0js>Wo#^=wX4T<`p zR2$8p6%A9ZTac;OvA4u#Oe3(OUep%&QgqpR8-&{0gjRE()!Ikc?ClygFmGa(7Z^9X zWzmV0$<8Uh)#qaH1`2YCV4Zu6@~*c*bhtHXw~1I6q4I>{92Eq+ZS@_nSQU43bZyidk@hd$j-_iL=^^2CwPcaXnBP;s;b zA4C!k+~rg4U)}=bZ2q*)c4BZ#a&o!uJo*6hK3JRBhOOUQ6fQI;dU#3v>_#yi62&Sp z-%9JJxwIfQ`@w(_qH0J0z~(lbh`P zHoyp2?Oppx^WXwD<~20v!lYm~n53G1w*Ej z9^B*j@lrd>XGW43ff)F;5k|HnGGRu=wmZG9c~#%vDWQHlOIA9(;&TBr#yza{(?k0> zcGF&nOI}JhuPl`kLViBEd)~p2nY9QLdX42u9C~EUWsl-@CE;05y@^V1^wM$ z&zemD1oZd$Z))kEw9)_Mf+X#nT?}n({(+aXHK2S@j$MDsdrw-iLb?#r{?Vud?I5+I zVQ8U?LXsQ}8-)JBGaoawyOsTTK_f8~gFFJ&lhDLs8@Rw$ey-wr&eqSEU^~1jtHmz6 z!D2g4Yh?3VE*W8=*r&G`?u?M~AdO;uTRPfE(@=Gkg z7gh=EGu!6VJJ?S_>|5ZwY?dGFBp3B9m4J1=7u=HcGjsCW+y6`W?OWxfH?S#X8&Zk& zvz6tWcnaS1@~3FTH}q_*$)AjYA_j;yl0H0{I(CW7Rq|;5Q2>Ngd(tmJDp+~qHe_8y zPU_fiCrn!SJ3x&>o6;WDnjUVEt`2fhc9+uLI>99(l$(>Tzwpbh>O775OA5i`jaBdp zXnCwUgomyF3K$0tXzgQhSAc!6nhyRh_$fP}Rd$|*Y7?ah(JrN=I7+)+Hp4BLJJ2P~ zFD!)H^uR2*m7GQZpLUVS#R3^?2wCd}(gcFcz!u5KN9ldNJdh@%onf06z9m~T0n;dqg6@?>G@S|rPO*Kj>{su+R|7bH>osA&uD4eqxtr**k($ii`uO? z7-&VkiL4Rp3S&e+T}2Z#;NtWHZco(v8O3QMvN0g7l8GV|U2>x-DbamkZo5)bjaSFR zr~Y9(EvF9{o*@|nBPj+e5o$_K`%TH1hD=|its}|qS^o6EQu_gOuDUH=Dtzik;P7G$ zq%_T<>9O}bGIB?;IQ*H`BJ5NWF6+XLv@G7aZwcy(&BoepG~u`aIcG>y+;J7+L=wTZ zB=%n@O}=+mjBO%1lMo6C0@1*+mhBqqY((%QMUBhyeC~r*5WVqzisOXFncr*5Lr0q6 zyPU&NOV}Vt2jl>&yig4I6j93?D>Ft=keRh=Y;3*^Z-I26nkZ#Jj5OJ89_?@#9lNjp z#gfAO6i937)~I|98P%xAWxwmk(F&@lTMx63*FZ~2b{NHU+}EV8+kMAB0bM*Zn#&7ubt98!PT^ZcMOfwMgkYz6+;?CKbvV zQ}Z@s_3JcMPhF&y1?}9uZFIBiPR3g7lf=+XEr9Bl%zRfGcaKb*ZQq5b35ZkR@=JEw zP#iqgh2^#@VA-h)>r`7R-$1_ddGr&oWWV$rx;pkG0Yohp9p@In_p)hKvMo@qIv zcN2t{23&^Nj=Y&gX;*vJ;kjM zHE2`jtjVRRn;=WqVAY&m$z=IoKa{>DgJ;To@OPqNbh=#jiS$WE+O4TZIOv?niWs47 zQfRBG&WGmU~>2O{}h17wXGEnigSIhCkg%N~|e?hG8a- zG!Wv&NMu5z!*80>;c^G9h3n#e>SBt5JpCm0o-03o2u=@v^n+#6Q^r#96J5Q=Dd=>s z(n0{v%yj)=j_Je2`DoyT#yykulwTB+@ejCB{dA7VUnG>4`oE?GFV4sx$5;%9&}yxfz<-wWk|IlA|g&! zN_Emw#w*2GT=f95(%Y1#Viop;Yro3SqUrW~2`Fl?Ten{jAt==a>hx$0$zXN`^7>V_ zG*o7iqeZV)txtHUU2#SDTyU#@paP;_yxp!SAG##cB= zr@LoQg4f~Uy5QM++W`WlbNrDa*U;54`3$T;^YVNSHX4?%z|`B~i7W+kl0wBB`8|(l zAyI6dXL&-Sei0=f#P^m`z=JJ`=W;PPX18HF;5AaB%Zlze`#pz;t#7Bzq0;k8IyvdK=R zBW+4GhjOv+oNq^~#!5(+pDz)Ku{u60bVjyym8Or8L;iqR|qTcxEKTRm^Y%QjFYU=ab+^a|!{!hYc+= z%Qc02=prKpzD+jiiOwzyb(dELO|-iyWzizeLugO!<1(j|3cbR!8Ty1$C|l@cWoi?v zLe<5+(Z-eH++=fX**O-I8^ceYZgiA!!dH+7zfoP-Q+@$>;ab&~cLFg!uOUX7h0r== z`@*QP9tnV1cu1!9pHc43C!{3?-GUBJEzI(&#~vY9MEUcRNR*61)mo!RG>_Yb^rNN7 zR9^bI45V?3Lq`^^BMD!GONuO4NH#v9OP3@s%6*Ha3#S*;f z6JEi)qW#Iq#5BtIXT9Gby|H?NJG}DN#Li82kZ_Rt1=T0Z@U6OAdyf}4OD|Sk^2%-1 zzgvqZ@b6~kL!^sZLO$r{s!3fQ5bHW}8r$uTVS*iw1u8^9{YlPp_^Xm5IN zF|@)ZOReX zB*#tEbWEX~@f)ST|s$oUKS@drycE1tYtdJ9b*(uFTxNZ{n3BI*kF7wXgT6+@PI@vwH7iQS{1T!Nauk>fm8gOLe`->Pi~ z8)3=UL_$OLl2n7QZlHt846nkYFu4V};3LpYA%5VaF#a2#d2g0&ZO~3WA%1XlerVpg zCAlM;(9OqH@`(>Tha{*@R%twB!}1ng4V=^+R`Q{#fkRk)C|suozf-uCXrkIH2SC^C z6wlxR`yS;-U#uu#`OnD%U<41%C4mp>LYLPIbgVO~WsT1if)Y)T*8nUB`2*(B;U_ha1NWv2`GqrZ z3MWWpT3tZ!*N@d*!j3=@K4>X*gX4A^@QPAz24?7u90AXaLiFq=Z$|5p$Ok2|YCX_Z zFgNPiY2r_Bg2BQE!0z=_N*G?%0cNITmAru*!Mws=F+F&Qw!&1?DBN{vSy%IvGRV@1 zS->PARgL^XS!-aZj zi@`~LhWfD!H-L0kNv=Jil9zR0>jZLqu)cLq?$yXVyk%EteKcWbe^qh#spHJPa#?92 za(N(Kw0se^$7nQUQZBet;C_Dj5(2_?TdrXFYwmebq}YGQbN5Ex7M zGSCX~Ey;5AqAzEDNr%p^!cuG?&wIeY&Bm5guVg>8F=!nT%7QZTGR(uGM&IZuMw0V_ zhPiIFWm?H?aw*(v6#uVT@NEzi2h5I$cZ-n0~m$tmwdMTjG*of^Y%1 zW?Y%o*-_iMqEJhXo^!Qo?tGFUn1Mb|urN4_;a)9bila2}5rBS#hZ5wV+t1xbyF1TW zj+~cdjbcMgY$zTOq6;ODaxzNA@PZIXX(-=cT8DBd;9ihfqqtbDr9#gXGtK24BPxjZ z9+Xp>W1(s)->-}VX~BoQv$I|-CBdO`gULrvNL>;@*HvTdh@wyNf}~IB5mFnTitX2i z;>W>tlQyc2)T4Mq+f!(i3#KuK-I8Kj3Wm(UYx?KWWt8DEPR_Jdb9CE~Fjc7Rkh#gh zowNv()KRO@##-C+ig0l!^*ol!Bj%d32_N*~d!|&>{t!k3lc?6VrdlCCb1?qyoR42m zv;4KdwCgvMT*{?tJKa(T?cl|b;k4P>c&O@~g71K5@}ys$)?}WSxD;<5%4wEz7h=+q ztLumn6>leWdDk#*@{=v9p)MsvuJMyf_VEs;pJh?i3z7_W@Q|3p$a}P@MQ-NpMtDUBgH!h4Ia#L&POr4Qw0Tqdw^}gCmQAB z8Dgkzn?V!_@04(cx0~-pqJOpeP1_}@Ml3pCb45EJoghLows9ET13J8kt0;m$6-jO( z4F|p+JFD1NT%4bpn4?&)d+~<360$z5on`eS6{H`S>t`VS$>(D`#mC*XK6zULj1Da# zpV$gw$2Ui{07NiYJQQNK;rOepRxA>soNK~B2;>z;{Ovx`k}(dlOHHuNHfeR}7tmIp zcM}q4*Fq8vSNJYi@4-;}`@bC?nrUy`3jR%HXhs79qWI5;hyTpH5%n-NcKu&j(aGwT z1~{geeq?Jd>>HL+?2`0K8dB2pvTS=LO~tb~vx_<=iN8^rW!y@~lBTAaxHmvVQJSeJ z!cb9ffMdP1lgI=>QJN{XpM4{reRrdIt|v|0-8!p}M*Qw^uV1@Ho-YsNd0!a(os$F* zT0tGHA#0%u0j*%S>kL*73@~7|iP;;!JbWSTA@`#VHv_l_%Z7CgX@>dhg_ zgn0|U)SY~U-E5{QiT@(uPp#1jaz!(_3^Cbz2 z4ZgWWz=PdGCiGznk{^4TBfx_;ZjAHQ>dB4YI}zfEnTbf60lR%=@VWt0yc=fd38Ig* z)Q38#e9^+tA7K}IDG5Z~>JE?J+n%0_-|i2{E*$jb4h?|_^$HRHjVkiyX6@Y+)0C2a zA+eegpT1dUpqQFIwx;!ayQcWQBQTj1n5&h<%Lggt@&tE19Rm~Rijtqw6nmYip_xg0 zO_IYpU304embcWP+**H|Z5~%R*mqq+y{KbTVqugkb)JFSgjVljsR{-c>u+{?moCCl zTL)?85;LXk0HIDC3v*|bB-r_z%zvL6Dp__L*A~Z*o?$rm>cYux&)W=6#+Cb}TF&Kd zdCgz3(ZrNA>-V>$C{a^Y^2F!l_%3lFe$s(IOfLBLEJ4Mcd!y&Ah9r)7q?oc z5L(+S8{AhZ)@3bw0*8(}Xw{94Vmz6FrK&VFrJN;xB96QmqYEibFz|yHgUluA-=+yS}I-+#_Pk zN67-#8W(R^e7f!;i0tXbJgMmJZH%yEwn*-}5ew13D<_FYWnt?{Mv1+MI~u;FN~?~m z{hUnlD1|RkN}c1HQ6l@^WYbHAXPJ^m0te1woe;LDJ}XEJqh1tPf=sD0%b+OuR1aCoP>I>GBn4C24Zu$D)qg=gq;D??5 zUSj%;-Hvk_ffj-+SI{ZCp`gZcNu=L@_N}kCcs?TyMr-37fhy$?a<7lt1`fZw<%$8@B6(Wgo!#!z9z{ab|x`+&;kP!(gfdY}A-GP&4Cbh-S< z1(kmgnMyB2z3ipEj5;4<{(=&<7a>A_Jl`ujUKYV@%k(oD=cD7W@8~5O=R*zdjM_y; zXwme~0wo0aDa~9rDnjF=B}Bbj|DHRQjN|?@(F^=bVFdr!#mwr|c0843k>%~5J|7|v zSY=T)iPU6rEAwrM(xTZwPio%D4y9Z4kL0bMLKvu4yd)0ZJA3<;>a2q~rEfcREn}~1 zCJ~3c?Afvx?3^@+!lnf(kB6YwfsJ*u^y7kZA?VmM%nBmaMspWu?WXq4)jQsq`9EbT zlF2zJ)wXuAF*2u|yd5hNrG>~|i}R&ZyeetTQ!?Hz6xGZZb3W6|vR>Hq=}*m=V=Lsp zUOMxh;ZfP4za~C{Ppn^%rhitvpnu^G{Z#o-r?TdEgSbtK_+~_iD49xM;$}X*mJF02|WBL{SDqK9}p4N!G$3m=x#@T+4QcapM{4j|Q zwO!(hldpuSW#by!zHEP@tzIC|KdD z%BJzQ7Ho1(HemWm`Z8m_D#*`PZ-(R%sZmPrS$aHS#WPjH3EDitxN|DY+ zYC|3S?PQ3NNYau$Qk8f>{w}~xCX;;CE=7;Kp4^xXR8#&^L+y-jep7oO^wnQ840tg1 zuN17QKsfdqZPlB8OzwF+)q#IsmenEmIbRAJHJ$JjxzawKpk8^sBm3iy=*kB%LppNb zhSdk`^n?01FKQ;=iU+McN7Mk0^`KE>mMe1CQ2a_R26_}^$bogFm=2vqJake7x)KN( zYz;gRPL+r4*KD>1U+DU+1jh{mT8#P#(z9^(aDljpeN{mRmx{AZX&hXKXNuxj3x*RrpjvOaZ#`1EqK!$+8=0yv8}=;>f=E?5tGbRUd4%?QL zy$kq6mZeF%k6E1&8nwAYMd!-lRkhQTob$7s`*XqcHs;l~mHV}fx&0I&i!CHaPVSM{ zHdRh7a>hP)t@YTrWm9y zl-ENWSVzlKVvTdWK>)enmGCEw(WYS=FtY{srdE{Z(3~4svwd)ct;`6Y{^qiW+9E@A ztzd?lj5F#k`=E1U-n*1JJc0{x{0q!_tkD<_S6bGsW)^RxGu%Rj^Mvw|R0WP1SqvAI zs(MiAd@Y5x!UKu376&|quQNxir;{Iz(+}3k-GNb29HaQh?K30u=6sXpIc?j0hF{VY zM$Do*>pN)eRljAOgpx7fMfSrnZ7>fi@@>Jh;qxj1#-Vj}JC3E^GCbC(r55_AG>6cq z4ru34FtVuBt)bkX4>ZFWjToyu)VA>IE6hXc+^(3ruUaKRqHnx3z)(GXetm;^0D95s zQ&drwfjhM4*|q=;i5Io0eDf?I{p}qo@7i7abHX5qLu~VDwYf4bmV~-^M_U?DL(+cG z{AyE^a|*73Ft)o5k-p)+GLXj#q01VlJ9#ZJkf|+c%6qfRgVp&6NsU3~F?!uh}HJm73xq>v$h zYoW3wJE6n9P|;{8U<^%UE2wjR4x^G_Nc$J(i)!>;g4`CCh2z^Dth#ah#<`#axDR?F z4>~hnN2%B2ZUuU6j>m1Qjj~5jQSdA&Q#7hOky#=Ue)}7LPJ!8nbZO_0Sw{G>>M7&E zb1dy|0Zi$(ubk`4^XkVI%4WIpe?Bh!D~IjvZs14yHw=aQ8-`N-=P*?Kzi&eRGZ_6Z zT>eis`!Dy3eT3=vt#Lbc+;}i5XJf7zM3QneL{t?w=U<1rk7+z2Cu^|~=~54tAeSYF zsXHsU;nM0dpK>+71yo(NFLV-^Lf7%U?Q$*q{^j04Gl71ya2)^j`nmJ$cmI9eFMjp+ z#)jKmi4lZc<;l>!={@jTm%?!5jS;6;c*Ml55~r6Y?22B^K3bPhKQ(ICc&z%w<4W1= zjTTtz_}IA$%kCqU)h#$!Yq>>2mVG}qYL}!avmCWYV}x4!YEeq)pgTp| zR;+skHuc7YXRLrcbYXt>?@pa{l^2pL>RrZ!22zMmi1ZR?nkaWF*`@XFK4jGh&Em3vn(l z3~^Q9&tM^eV=f^lccCUc9v02z%^n5VV6s$~k0uq5B#Ipd6`M1Kptg^v<2jiNdlAWQ z_MmtNEaeYIHaiuaFQdG&df7miiB5lZkSbg&kxY*Eh|KTW`Tk~VwKC~+-GoYE+pvwc{+nIEizq6!xP>7ZQ(S2%48l$Y98L zvs7s<&0ArXqOb*GdLH0>Yq-f!{I~e~Z@FUIPm?jzqFZvz9VeZLYNGO}>Vh<=!Er7W zS!X6RF^et7)IM1pq57z*^hP5w7HKSDd8jHX!*gkKrGc-GssrNu5H%7-cNE{h$!aEQK3g*qy;= z)}pxO8;}nLVYm_24@iEs8)R7i;Th0n4->&$8m6(LKCRd(yn7KY%QHu_f=*#e`H^U( z{u!`9JaRD?Z?23fEXrjx>A@+a!y-_oaDB)o@2s{2%A97-ctFfrN0cXQ@6aGH`X~Nr z144?qk;MzDU-cgQOLfT3-ZR#hKmYtKG*iGf4ZJ`|`9!^SkBDUUSJCba)>mM!)k~(z zdjUqB`)~!UObMHB1b$UItM$<0kwlqHH;c z=)+~bkOcIT7vI0Iy(wD)vsg9|oi##%Rgrq`Ek;pN)}lbpz`iv{F4K*{ZZ?Zjixxxr zY|SPl2NsXH+5pimj+MvbZ_+HrfvdC13|9Zs)Y=nW$z<0mhl}%irBSm5T3ZrN#2AhY z_ZrTmS(L`U#y}VZ@~QL9wUS6AnU*7LWS02Xyz`b>%rTml#Wb0yr>@c(Ym*40g;P{V zjV1XSHdU>oY!&Jh7MzhzUV8(9E+yl5UJYga>=0Ldjwtc`5!1>LxaB-kVW;IlSPs+0 zUBx=m8OKVp<`frNvMK>WMO(iKY%PuvqD+PK*vP6f?_o!O)MCW5Ic zv(%f5PLHyOJ2h@Yn_to@54Yq;fdoy40&sbe3A$4uUXHsHP_~K}h#)p&TyOx(~JE?y(IBAQKl}~VQjVC-c6oZwmESL;`Xth?2)-b6ImNcJi z;w|`Q*k?`L(+Dp}t(FocvzWB(%~9$EAB6_J6CrA}hMj-Vy*6iA$FdV}!lvk%6}M)4 zTf<)EbXr9^hveAav1yA?>O0aNEpv0&rju{(Gt|dP=AP%)uQm~OE7@+wEhILrRLt&E zoEsF^nz>4yK1|EOU*kM+9317S;+bb7?TJM2UUpc!%sDp}7!<`i=W!ot8*C&fpj>mk#qt~GCeqcy)?W6sl>eUnR%yCBR&Ow-rc|q;lhnI+f-%`6Xf)% zIYZru;27%vA{Qi2=J`PQC<28;tFx(V^sgXf>)8WNxxQwT14M9I6- z+V0@tiCiDkv`7r-06sJS8@s|Lf>mV+8h}SPT4ZGPSMaFK7_SMXH$3KN7b2V?iV-jA zh1!Z>2tv^HVbHnNUAf-wQW#zMV(h8=3x2Swd|-%AczEIWLcm~EAu7rc3s%56b;7ME zj}$pe#fc^314Mb9i)xH^_#({)tTD4hsoz!7XcHUh9*G|}?k=D?9LBkTm2?fgaIG(%%$DL#}a-_990rQBU+M;jrf zCcvgM`+oyZmsUqc?lly9axZfO)02l$TMS#I+jHYY`Uk!gtDv|@GBQ||uaG^n*QR3Q z@tV?D;R;KmkxSDQh<2DkDC1?m?jTvf2i^T;+}aYhzL?ymNZmdns2e)}2V>tDCRw{= zTV3q3ZQDkdZQHi3?y{@8Y@1!SZQHi(y7|qSx$~Vl=iX<2`@y3eSYpsBV zI`Q-6;)B=p(ZbX55C*pu1C&yqS|@Pytis3$VDux0kxKK}2tO&GC;cH~759o?W2V)2 z)`;U(nCHBE!-maQz%z#zoRNpJR+GmJ!3N^@cA>0EGg?OtgM_h|j1X=!4N%!`g~%hdI3%yz&wq4rYChPIGnSg{H%i>96! z-(@qsCOfnz7ozXoUXzfzDmr>gg$5Z1DK$z#;wn9nnfJhy6T5-oi9fT^_CY%VrL?l} zGvnrMZP_P|XC$*}{V}b^|Hc38YaZQESOWqA1|tiXKtIxxiQ%Zthz?_wfx@<8I{XUW z+LH%eO9RxR_)8gia6-1>ZjZB2(=`?uuX|MkX082Dz*=ep%hMwK$TVTyr2*|gDy&QOWu zorR#*(SDS{S|DzOU$<-I#JTKxj#@0(__e&GRz4NuZZLUS8}$w+$QBgWMMaKge*2-) zrm62RUyB?YSUCWTiP_j-thgG>#(ZEN+~bMuqT~i3;Ri`l${s0OCvCM>sqtIX?Cy`8 zm)MRz-s^YOw>9`aR#J^tJz6$S-et%elmR2iuSqMd(gr6a#gA_+=N(I6%Cc+-mg$?_1>PlK zbgD2`hLZ?z4S~uhJf=rraLBL?H#c$cXyqt{u^?#2vX2sFb z^EU-9jmp{IZ~^ii@+7ogf!n_QawvItcLiC}w^$~vgEi(mX79UwDdBg`IlF42E5lWE zbSibqoIx*0>WWMT{Z_NadHkSg8{YW4*mZ@6!>VP>ey}2PuGwo%>W7FwVv7R!OD32n zW6ArEJX8g_aIxkbBl^YeTy5mhl1kFGI#n>%3hI>b(^`1uh}2+>kKJh0NUC|1&(l)D zh3Barl&yHRG+Le2#~u>KoY-#GSF>v)>xsEp%zgpq4;V6upzm3>V&yk^AD}uIF{vIn zRN-^d4(Sk6ioqcK@EObsAi#Z-u&Hh#kZdv1rjm4u=$2QF<6$mgJ4BE0yefFI zT7HWn?f668n!;x>!CrbdA~lDfjX?)315k1fMR~lG)|X_o()w|NX&iYUTKxI2TLl|r z{&TWcBxP>*;|XSZ1GkL&lSg?XL9rR4Ub&4&03kf};+6$F)%2rsI%9W_i_P|P%Z^b@ zDHH2LV*jB@Izq0~E4F^j04+C|SFiV8{!bth%bz(KfCg42^ zGz5P7xor$)I4VX}Cf6|DqZ$-hG7(}91tg#AknfMLFozF1-R~KS3&5I0GNb`P1+hIB z?OPmW8md3RB6v#N{4S5jm@$WTT{Sg{rVEs*)vA^CQLx?XrMKM@*gcB3mk@j#l0(~2 z9I=(Xh8)bcR(@8=&9sl1C?1}w(z+FA2`Z^NXw1t(!rpYH3(gf7&m=mm3+-sls8vRq z#E(Os4ZNSDdxRo&`NiRpo)Ai|7^GziBL6s@;1DZqlN@P_rfv4Ce1={V2BI~@(;N`A zMqjHDayBZ);7{j>)-eo~ZwBHz0eMGRu`43F`@I0g!%s~ANs>Vum~RicKT1sUXnL=gOG zDR`d=#>s?m+Af1fiaxYxSx{c5@u%@gvoHf#s6g>u57#@#a2~fNvb%uTYPfBoT_$~a^w96(}#d;-wELAoaiZCbM zxY4fKlS6-l1!b1!yra|`LOQoJB))=CxUAYqFcTDThhA?d}6FD$gYlk**!# zD=!KW>>tg1EtmSejwz{usaTPgyQm~o+NDg`MvNo)*2eWX*qAQ)4_I?Pl__?+UL>zU zvoT(dQ)pe9z1y}qa^fi-NawtuXXM>*o6Al~8~$6e>l*vX)3pB_2NFKR#2f&zqbDp7 z5aGX%gMYRH3R1Q3LS91k6-#2tzadzwbwGd{Z~z+fBD5iJ6bz4o1Rj#7cBL|x8k%jO z{cW0%iYUcCODdCIB(++gAsK(^OkY5tbWY;)>IeTp{{d~Y#hpaDa-5r#&Ha?+G{tn~ zb(#A1=WG1~q1*ReXb4CcR7gFcFK*I6Lr8bXLt9>9IybMR&%ZK15Pg4p_(v5Sya_70 ziuUYG@EBKKbKYLWbDZ)|jXpJJZ&bB|>%8bcJ7>l2>hXuf-h5Bm+ zHZ55e9(Sg>G@8a`P@3e2(YWbpKayoLQ}ar?bOh2hs89=v+ifONL~;q(d^X$7qfw=; zENCt`J*+G;dV_85dL3Tm5qz2K4m$dvUXh>H*6A@*)DSZ2og!!0GMoCPTbcd!h z@fRl3f;{F%##~e|?vw6>4VLOJXrgF2O{)k7={TiDIE=(Dq*Qy@oTM*zDr{&ElSiYM zp<=R4r36J69aTWU+R9Hfd$H5gWmJ?V){KU3!FGyE(^@i!wFjeZHzi@5dLM387u=ld zDuI1Y9aR$wW>s#I{2!yLDaVkbP0&*0Rw%6bi(LtieJQ4(1V!z!ec zxPd)Ro0iU%RP#L|_l?KE=8&DRHK>jyVOYvhGeH+Dg_E%lgA(HtS6e$v%D7I;JSA2x zJyAuin-tvpN9g7>R_VAk2y;z??3BAp?u`h-AVDA;hP#m+Ie`7qbROGh%_UTW#R8yfGp<`u zT0}L)#f%(XEE)^iXVkO8^cvjflS zqgCxM310)JQde*o>fUl#>ZVeKsgO|j#uKGi)nF_ur&_f+8#C0&TfHnfsLOL|l(2qn zzdv^wdTi|o>$q(G;+tkTKrC4rE)BY?U`NHrct*gVx&Fq2&`!3htkZEOfODxftr4Te zoseFuag=IL1Nmq45nu|G#!^@0vYG5IueVyabw#q#aMxI9byjs99WGL*y)AKSaV(zx z_`(}GNM*1y<}4H9wYYSFJyg9J)H?v((!TfFaWx(sU*fU823wPgN}sS|an>&UvI;9B(IW(V)zPBm!iHD} z#^w74Lpmu7Q-GzlVS%*T-z*?q9;ZE1rs0ART4jnba~>D}G#opcQ=0H)af6HcoRn+b z<2rB{evcd1C9+1D2J<8wZ*NxIgjZtv5GLmCgt?t)h#_#ke{c+R6mv6))J@*}Y25ef z&~LoA&qL-#o=tcfhjH{wqDJ;~-TG^?2bCf~s0k4Rr!xwz%Aef_LeAklxE=Yzv|3jf zgD0G~)e9wr@)BCjlY84wz?$NS8KC9I$wf(T&+79JjF#n?BTI)Oub%4wiOcqw+R`R_q<`dcuoF z%~hKeL&tDFFYqCY)LkC&5y(k7TTrD>35rIAx}tH4k!g9bwYVJ>Vdir4F$T*wC@$08 z9Vo*Q0>*RcvK##h>MGUhA9xix+?c1wc6xJhn)^9;@BE6i*Rl8VQdstnLOP1mq$2;!bfASHmiW7|=fA{k$rs^-8n{D6_ z!O0=_K}HvcZJLSOC6z-L^pl3Gg>8-rU#Sp1VHMqgXPE@9x&IHe;K3;!^SQLDP1Gk&szPtk| z!gP;D7|#y~yVQ?sOFiT*V(Z-}5w1H6Q_U5JM#iW16yZiFRP1Re z6d4#47#NzEm};1qRP9}1;S?AECZC5?6r)p;GIW%UGW3$tBN7WTlOy|7R1?%A<1!8Z zWcm5P6(|@=;*K&3_$9aiP>2C|H*~SEHl}qnF*32RcmCVYu#s!C?PGvhf1vgQ({MEQ z0-#j>--RMe{&5&$0wkE87$5Ic5_O3gm&0wuE-r3wCp?G1zA70H{;-u#8CM~=RwB~( zn~C`<6feUh$bdO1%&N3!qbu6nGRd5`MM1E_qrbKh-8UYp5Bn)+3H>W^BhAn;{BMii zQ6h=TvFrK)^wKK>Ii6gKj}shWFYof%+9iCj?ME4sR7F+EI)n8FL{{PKEFvB65==*@ ztYjjVTJCuAFf8I~yB-pN_PJtqH&j$`#<<`CruB zL=_u3WB~-;t3q)iNn0eU(mFTih<4nOAb>1#WtBpLi(I)^zeYIHtkMGXCMx+I zxn4BT0V=+JPzPeY=!gAL9H~Iu%!rH0-S@IcG%~=tB#6 z3?WE7GAfJ{>GE{?Cn3T!QE}GK9b*EdSJ02&x@t|}JrL{^wrM@w^&})o;&q816M5`} zv)GB;AU7`haa1_vGQ}a$!m-zkV(+M>q!vI0Swo18{;<>GYZw7-V-`G#FZ z;+`vsBihuCk1RFz1IPbPX8$W|nDk6yiU8Si40!zy{^nmv_P1=2H*j<^as01|W>BQS zU)H`NU*-*((5?rqp;kgu@+hDpJ;?p8CA1d65)bxtJikJal(bvzdGGk}O*hXz+<}J? zLcR+L2OeA7Hg4Ngrc@8htV!xzT1}8!;I6q4U&S$O9SdTrot<`XEF=(`1{T&NmQ>K7 zMhGtK9(g1p@`t)<)=eZjN8=Kn#0pC2gzXjXcadjHMc_pfV(@^3541)LC1fY~k2zn&2PdaW`RPEHoKW^(p_b=LxpW&kF?v&nzb z1`@60=JZj9zNXk(E6D5D}(@k4Oi@$e2^M%grhlEuRwVGjDDay$Qpj z`_X-Y_!4e-Y*GVgF==F0ow5MlTTAsnKR;h#b0TF>AyJe`6r|%==oiwd6xDy5ky6qQ z)}Rd0f)8xoNo)1jj59p;ChIv4Eo7z*{m2yXq6)lJrnziw9jn%Ez|A-2Xg4@1)ET2u zIX8`u5M4m=+-6?`S;?VDFJkEMf+=q?0D7?rRv)mH=gptBFJGuQo21rlIyP>%ymGWk z=PsJ>>q~i>EN~{zO0TklBIe(8i>xkd=+U@;C{SdQ`E03*KXmWm4v#DEJi_-F+3lrR z;0al0yXA&axWr)U%1VZ@(83WozZbaogIoGYpl!5vz@Tz5?u36m;N=*f0UY$ssXR!q zWj~U)qW9Q9Fg9UW?|XPnelikeqa9R^Gk77PgEyEqW$1j=P@L z*ndO!fwPeq_7J_H1Sx>#L$EO_;MfYj{lKuD8ZrUtgQLUUEhvaXA$)-<61v`C=qUhI zioV&KR#l50fn!-2VT`aMv|LycLOFPT{rRSRGTBMc)A`Cl%K&4KIgMf}G%Qpb2@cB* zw8obt-BI3q8Lab!O<#zeaz{P-lI2l`2@qrjD+Qy)^VKks5&SeT(I)i?&Kf59{F`Rw zuh7Q>SQNwqLO%cu2lzcJ7eR*3!g}U)9=EQ}js-q{d%h!wl6X3%H0Z2^8f&^H;yqti4z6TNWc& zDUU8YV(ZHA*34HHaj#C43PFZq7a>=PMmj4+?C4&l=Y-W1D#1VYvJ1~K%$&g-o*-heAgLXXIGRhU zufonwl1R<@Kc8dPKkb`i5P9VFT_NOiRA=#tM0WX2Zut)_ zLjAlJS1&nnrL8x8!o$G+*z|kmgv4DMjvfnvH)7s$X=-nQC3(eU!ioQwIkaXrl+58 z@v)uj$7>i`^#+Xu%21!F#AuX|6lD-uelN9ggShOX&ZIN+G#y5T0q+RL*(T(EP)(nP744-ML= z+Rs3|2`L4I;b=WHwvKX_AD56GU+z92_Q9D*P|HjPYa$yW0o|NO{>4B1Uvq!T;g_N- zAbNf%J0QBo1cL@iahigvWJ9~A4-glDJEK?>9*+GI6)I~UIWi>7ybj#%Po}yT6d6Li z^AGh(W{NJwz#a~Qs!IvGKjqYir%cY1+8(5lFgGvl(nhFHc7H2^A(P}yeOa_;%+bh` zcql{#E$kdu?yhRNS$iE@F8!9E5NISAlyeuOhRD)&xMf0gz^J927u5aK|P- z>B%*9vSHy?L_q)OD>4+P;^tz4T>d(rqGI7Qp@@@EQ-v9w-;n;7N05{)V4c7}&Y^!`kH3}Q z4RtMV6gAARY~y$hG7uSbU|4hRMn97Dv0$Le@1jDIq&DKy{D$FOjqw{NruxivljBGw zP4iM(4Nrz^^~;{QBD7TVrb6PB=B$<-e9!0QeE8lcZLdDeb?Gv$ePllO2jgy&FSbW* zSDjDUV^=`S(Oo0;k(Idvzh}aXkfO)F6AqB?wWqYJw-1wOn5!{-ghaHb^v|B^92LmQ9QZj zHA&X)fd%B$^+TQaM@FPXM$$DdW|Vl)4bM-#?Slb^qUX1`$Yh6Lhc4>9J$I4ba->f3 z9CeGO>T!W3w(){M{OJ+?9!MK68KovK#k9TSX#R?++W4A+N>W8nnk**6AB)e;rev=$ zN_+(?(YEX;vsZ{EkEGw%J#iJYgR8A}p+iW;c@V>Z1&K->wI>!x-+!0*pn|{f=XA7J zfjw88LeeJgs4YI?&dHkBL|PRX`ULOIZlnniTUgo-k`2O2RXx4FC76;K^|ZC6WOAEw zz~V0bZ29xe=!#Xk?*b{sjw+^8l0Koy+e7HjWXgmPa4sITz+$VP!YlJ$eyfi3^6gGx6jZLpbUzX;!Z6K}aoc!1CRi zB6Lhwt%-GMcUW;Yiy6Y7hX(2oksbsi;Z6k*=;y;1!taBcCNBXkhuVPTi+1N*z*}bf z`R=&hH*Ck5oWz>FR~>MO$3dbDSJ!y|wrff-H$y(5KadrA_PR|rR>jS=*9&J*ykWLr z-1Z^QOxE=!6I z%Bozo)mW7#2Hd$-`hzg=F@6*cNz^$#BbGlIf${ZV1ADc}sNl=B72g`41|F7JtZ^BT z+y}nqn3Ug`2scS_{MjykPW2~*k$i6PhvvxJCW;n!SK5B8Rpm41fCEdy=ea-4F`rN5 zF>ClKp#4?}pI7eR#6U|}t`DA!GQJB7nT$HVV*{qPjIRU1Ou3W;I^pCt54o|ZHvWaH zooFx9L%#yv)!P;^er5LCU$5@qXMhJ-*T5Ah8|}byGNU5oMp3V)yR;hWJKojJEregX z<1UPt%&~=5OuP(|B{ty);vLdoe7o^?`tkQa7zoXKAW6D@lc+FTzucotaOfJ!(Bm zHE8f8j@6||lH`y2<&hP}Q1wr(=6ze0D6NRL{7QaE1=nTAzqjIeD}Be&@#_d*dyurz z&L7xo-D9!dS`i>^GaIPArR@r=N#-ppIh!UBcb!N*?nLUO+*%C>_dCF1IH)q>5oT(t zjQo{AoDB;mWL;3&;vTt?;bvJSj>^Gq4Jrh}S}D>G)+b!>oRDWI?c_d77$kF5ms{Gx zak*>~*5AvaB-Xl)IgdZ^Cupv6HxQ0 zM(KPaDpPsPOd)e)aFw}|=tfzg@J1P8oJx2ZBY=g4>_G(Hkgld(u&~jN((eJ}5@b1} zI(P7j443AZj*I@%q!$JQ2?DZV47U!|Tt6_;tlb`mSP3 z74DE4#|1FMDqwYbT4P6#wSI%s?*wDc>)MR$4z9ZtJg04+CTUds>1JSDwI}=vpRoRR zLqx(Tvf34CvkTMOPkoH~$CG~fSZb;(2S4Q6Vpe9G83V={hwQ>acu+MCX)@0i>Vd`% z4I8Ye+7&Kcbh(*bN1etKmrpN)v|=eI+$oD=zzii6nP&w|kn2Y-f!(v<aE zKmOz#{6PZB(8zD={il`RO6D}v(@mN_66KXUAEefgg|;VmBfP?UrfB$&zaRw7oanna zkNmVGz4Vhd!vZSnp1(&_5^t;eSv6O771BloJAHi=Pnn+aa6y(e2iiE97uZ{evzQ^8 z*lN@ZYx<-hLXP^IuYLGf<01O*>nDp0fo;;Iyt`JADrxt7-jEF(vv_btyp6CT8=@5t zm`I0lW+2+_xj2CRL|40kcYysuyYeiGihGe&a)yilqP}5h+^)m8$=mzrUe`$(?BIY> zfF7-V10Gu0CkWF)wz04&hhI>es0NS7d`cnT`4y8K!wUAKv$H09fa>KeNQvwUNDT1zn}_*RHykC$CD%*h7vRCQ&Z z4&N-!L>(@8i?K$l5)13n0%VPPV`iG7Q$2{1T3JypLSvN%1kX73goBIOEmg=Uf$9e? zm}g>JFu}EQKH>|K!)m9teoCmTc`y2Ll}msZYyy0Pkqjeid66>DP_?C{KCw94lHvLW z-+X!2YSm70s833lH0o+|A%Xwsw`@8lE3ia0n_Dve;LC7@I+i~@%$lD|3fNf&R6ob6 z@iGfx^OC4s`$|vO!0jTWwVpX;X^EqJF{i324I>N=f@u+rTN+xJGGR0LsCQc;iFD=F zbZJrgOpS;04o^wP7HF5QBaJ$KJgS2V4u02ViWD=6+7rcu`uc&MOoyf%ZBU|gQZkUg z<}ax>*Fo?d*77Ia)+{(`X45{a8>Bi$u-0BWSteyp#GJnTs?&k&<0NeHA$Qb3;SAJK zl}H*~eyD-0qHI3SEcn`_7d zq@YRsFdBig+k490BZSQwW)j}~GvM7x>2ymO4zakaHZ!q6C2{fz^NvvD8+e%7?BQBH z-}%B{oROo2+|6g%#+XmyyIJrK_(uEbg%MHlBn3^!&hWi+9c0iqM69enep#5FvV_^r z?Yr(k*5FbG{==#CGI1zU0Wk{V?UGhBBfv9HP9A-AmcJmL^f4S zY3E2$WQa&n#WRQ5DOqty_Pu z-NWQGCR^Hnu^Vo2rm`-M>zzf|uMCUd1X0{wISJL2Pp=AO5 zF@(50!g|SYw3n<_VP0T~`WUjtY**6Npphr5bD%i3#*p7h8$#;XTLJAt5J-x~O1~`z z`2C~P4%XSI(JbrEmVMEwqdsa^aqXWg;A6KBn^jDxTl!}Q!^WhprL$kb(Iqq zUS`i$tIPs#hdE-zAaMGoxcG?Z;RO2L0Y|gcjV_)FFo|e)MtTl`msLTwq>po$`H6_U zhdWK97~M>idl9GE_WgobQkK_P85H_0jN?s3O)+m&68B`_;FnbZ3W*Qm++ghSs7|T4b7m~VVV%j0gl`Iw!?+-9#Lsb!j3O%fSTVuK z37V>qM81D+Atl};23`TqEAfEkQDpz$-1$e__>X2jN>xh@Sq)I6sj@< ziJ^66GSmW9c%F7eu6&_t$UaLXF4KweZecS1ZiHPWy-$e_7`jVk74OS*!z=l#(CQ^K zW-ke|g^&0o=hn+4uh-8lUh0>!VIXXnQXwKr>`94+2~<;+`k z$|}QZ>#pm2g}8k*;)`@EnM~ZQtci%_$ink9t6`HP{gn}P1==;WDAld3JX?k%^GcTU za>m|CH|UsyFhyJBwG5=`6562hkVRMQ=_ron-Vlm$4bG^GFz|Jh5mM{J1`!!hAr~8F^w> z^YhQ=c|bFn_6~9X$v(30v$5IX;#Nl-XXRPgs{g_~RS*znH^6Vhe}8>T?aMA|qfnWO zQpf(wr^PfygfM+m2u!9}F|frrZPBQ!dh(varsYo!tCV)WA(Wn^_t=WR_G7cQU`AGx zrK^B6<}9+$w;$vra)QWMKf_Tnqg93AMVZ6Qd=q6rdB{;ZhsoT zWy9QhnpEnc@Dauz4!8gq zqDanAX#$^vf-4~ZqUJtSe?SO+Hmb?)l2#}v(8}2+P{ZZuhlib0$3G0|a5?JR>QgUUP$HTE5hb`h>imq#7P+Y*-UVLm@9km|V# zoigziFt$bxgQMwqKKhd!c--&ciywIED>faY3zHLrA{V#IA)!mq!FXxf?1coGK~N(b zjwu*@2B1^(bzFVBJO`4EJ$=it!a0kbgUvPL;Er(0io{W4G7Bkqh)=g)uS|l0YfD}f zaCJwY7vR-D=P9M68`cmtmQ^!F-$lt@0S|9G7cHgT13A0xMv)HmH#Z<4{~iYo_VOD{ z5!kU+>mUOvHouw+-y?*cNlUlDwD#;6ZvAIc$YcwG&qKZFh>EtM(Eda+w)E$HcfZyB zG*$<*ae_ApE%gxWx%O^~XMnRSNLv!y`g99F(J_m)spJAc95P|_joOIoru%atbw z9PYgkcE*8x#)-W{>96KDl&74iW<#wrK)1s zxzU{`rW5af+dT6Z@_1dG<}CtDMT`EGVEXSL_5D9)Z;6UJe-TW7)M?bY%E;8G?Yc!$ zic;F5=#dba^P~7f#qvC}Nd#XEo2r_UlgfR_`B2^W0QjXU?RAi$>f&{G_Lu8Fp0qDp z?vAdm%z#3kcZmaJ@afooB=A@>8_N~O9Yzu=ZCEikM>UgU+{%>pPvmSNzGk@*jnc5~ z(Z#H4OL^gw>)gqZ!9X|3i4LAdp9vo)?F9QCR3##{BHoZ73Uk^Ha={2rc*TBijfKH- z=$cZQdc<5%*$kVo|{+bL3 zEoU&tq*YPR)^y-SISeQNQ)YZ9v>Hm4O=J)lf(y=Yu1ao&zj#5GVGxyj%V%vl9}dw< zO;@NRd4qe@Et}E@Q;SChBR2QPKll1{*5*jT*<$$5TywvC77vt=1=0xZ46>_17YzbiBoDffH(1_qFP7v2SVhZmA_7JDB50t#C39 z8V<9(E?bVWI<7d6MzcS^w!XmZ**{AO!~DZNU)pgr=yY1 zT@!AapE;yg&hmj*g{I3vd## zx+d%^O?d%%?Dba|l~X6ZOW|>FPsrjPjn-h4swysH!RNJUWofC?K(^0uHrBPrH5#W> zMn8^@USzjUucqo%+5&))Dnnw`5l1mp>roaA99Nkk4keZl2wAF7oa(!x?@8uGWzc5Q zM}g`}zf-D@B6lVFYWmmJ8a+_%z8g$C7Ww~PD9&jki08NY!b!fK288R;E?e3Z+Pk{is%HxQU`xu9+y5 zq?DWJD7kKp(B2J$t5Ij8-)?g!T9_n<&0L8F5-D0dp>9!Qnl#E{eDtkNo#lw6rMJG$ z9Gz_Z&a_6ie?;F1Y^6I$Mg9_sml@-z6t!YLr=ml<6{^U~UIbZUUa_zy>fBtR3Rpig zc1kLSJj!rEJILzL^uE1mQ}hjMCkA|ZlWVC9T-#=~ip%McP%6QscEGlYLuUxDUC=aX zCK@}@!_@~@z;70I+Hp5#Tq4h#d4r!$Np1KhXkAGlY$ap7IZ9DY})&(xoTyle8^dBXbQUhPE6ehWHrfMh&0=d<)E2+pxvWo=@`^ zIk@;-$}a4zJmK;rnaC)^a1_a_ie7OE*|hYEq1<6EG>r}!XI9+(j>oe!fVBG%7d}?U z#ja?T@`XO(;q~fe2CfFm-g8FbVD;O7y9c;J)k0>#q7z-%oMy4l+ zW>V~Y?s`NoXkBeHlXg&u*8B7)B%alfYcCriYwFQWeZ6Qre!4timF`d$=YN~_fPM5Kc8P;B-WIDrg^-j=|{Szq6(TC)oa!V7y zLmMFN1&0lM`+TC$7}on;!51{d^&M`UW ztI$U4S&}_R?G;2sI)g4)uS-t}sbnRoXVwM!&vi3GfYsU?fSI5Hn2GCOJ5IpPZ%Y#+ z=l@;;{XiY_r#^RJSr?s1) z4b@ve?p5(@YTD-<%79-%w)Iv@!Nf+6F4F1`&t~S{b4!B3fl-!~58a~Uj~d4-xRt`k zsmGHs$D~Wr&+DWK$cy07NH@_z(Ku8gdSN989efXqpreBSw$I%17RdxoE<5C^N&9sk!s2b9*#}#v@O@Hgm z2|U7Gs*@hu1JO$H(Mk)%buh~*>paY&Z|_AKf-?cz6jlT-v6 zF>l9?C6EBRpV2&c1~{1$VeSA|G7T(VqyzZr&G>vm87oBq2S%H0D+RbZm}Z`t5Hf$C zFn7X*;R_D^ z#Ug0tYczRP$s!6w<27;5Mw0QT3uNO5xY($|*-DoR1cq8H9l}_^O(=g5jLnbU5*SLx zGpjfy(NPyjL`^Oln_$uI6(aEh(iS4G=$%0;n39C(iw79RlXG>W&8;R1h;oVaODw2nw^v{~`j(1K8$ z5pHKrj2wJhMfw0Sos}kyOS48Dw_~=ka$0ZPb!9=_FhfOx9NpMxd80!a-$dKOmOGDW zi$G74Sd(-u8c!%35lL|GkyxZdlYUCML{V-Ovq{g}SXea9t`pYM^ioot&1_(85oVZ6 zUhCw#HkfCg7mRT3|>99{swr3FlA@_$RnE?714^o;vps4j4}u=PfUAd zMmV3j;Rogci^f!ms$Z;gqiy7>soQwo7clLNJ4=JAyrz;=*Yhe8q7*$Du970BXW89Xyq92M4GSkNS-6uVN~Y4r7iG>{OyW=R?@DmRoi9GS^QtbP zFy2DB`|uZTv8|ow|Jcz6?C=10U$*_l2oWiacRwyoLafS!EO%Lv8N-*U8V+2<_~eEA zgPG-klSM19k%(%;3YM|>F||hE4>7GMA(GaOvZBrE{$t|Hvg(C2^PEsi4+)w#P4jE2XDi2SBm1?6NiSkOp-IT<|r}L9)4tLI_KJ*GKhv16IV}An+Jyx z=Mk`vCXkt-qg|ah5=GD;g5gZQugsv!#)$@ zkE=6=6W9u9VWiGjr|MgyF<&XcKX&S3oN{c{jt-*1HHaQgY({yjZiWW97rha^TxZy< z2%-5X;0EBP>(Y9|x*603*Pz-eMF5*#4M;F`QjTBH>rrO$r3iz5 z?_nHysyjnizhZQMXo1gz7b{p`yZ8Q78^ zFJ3&CzM9fzAqb6ac}@00d*zjW`)TBzL=s$M`X*0{z8$pkd2@#4CGyKEhzqQR!7*Lo@mhw`yNEE6~+nF3p;Qp;x#-C)N5qQD)z#rmZ#)g*~Nk z)#HPdF_V$0wlJ4f3HFy&fTB#7Iq|HwGdd#P3k=p3dcpfCfn$O)C7;y;;J4Za_;+DEH%|8nKwnWcD zBgHX)JrDRqtn(hC+?fV5QVpv1^3=t2!q~AVwMBXohuW@6p`!h>>C58%sth4+Baw|u zh&>N1`t(FHKv(P+@nT$Mvcl){&d%Y5dx|&jkUxjpUO3ii1*^l$zCE*>59`AvAja%`Bfry-`?(Oo?5wY|b4YM0lC?*o7_G$QC~QwKslQTWac z#;%`sWIt8-mVa1|2KH=u!^ukn-3xyQcm4@|+Ra&~nNBi0F81BZT$XgH@$2h2wk2W% znpo1OZuQ1N>bX52II+lsnQ`WVUxmZ?4fR_f0243_m`mbc3`?iy*HBJI)p2 z`GQ{`uS;@;e1COn-vgE2D!>EheLBCF-+ok-x5X8Cu>4H}98dH^O(VlqQwE>jlLcs> zNG`aSgDNHnH8zWw?h!tye^aN|%>@k;h`Z_H6*py3hHO^6PE1-GSbkhG%wg;+vVo&dc)3~9&` zPtZtJyCqCdrFUIEt%Gs_?J``ycD16pKm^bZn>4xq3i>9{b`Ri6yH|K>kfC; zI5l&P)4NHPR)*R0DUcyB4!|2cir(Y1&Bsn3X8v4D(#QW8Dtv@D)CCO zadQC85Zy=Rkrhm9&csynbm>B_nwMTFah9ETdNcLU@J{haekA|9*DA2pY&A|FS*L!*O+>@Q$00FeL+2lg2NWLITxH5 z0l;yj=vQWI@q~jVn~+5MG!mV@Y`gE958tV#UcO#56hn>b69 zM;lq+P@MW=cIvIXkQmKS$*7l|}AW%6zETA2b`qD*cL z(=k4-4=t6FzQo#uMXVwF{4HvE%%tGbiOlO)Q3Y6D<5W$ z9pm>%TBUI99MC`N9S$crpOCr4sWJHP)$Zg#NXa~j?WeVo03P3}_w%##A@F|Bjo-nNxJZX%lbcyQtG8sO zWKHes>38e-!hu1$6VvY+W-z?<942r=i&i<88UGWdQHuMQjWC-rs$7xE<_-PNgC z_aIqBfG^4puRkogKc%I-rLIVF=M8jCh?C4!M|Q=_kO&3gwwjv$ay{FUDs?k7xr%jD zHreor1+#e1_;6|2wGPtz$``x}nzWQFj8V&Wm8Tu#oaqM<$BLh+Xis=Tt+bzEpC}w) z_c&qJ6u&eWHDb<>p;%F_>|`0p6kXYpw0B_3sIT@!=fWHH`M{FYdkF}*CxT|`v%pvx z#F#^4tdS0|O9M1#db%MF(5Opy;i( zL(Pc2aM4*f_Bme@o{xMrsO=)&>YKQw+)P-`FwEHR4vjU>#9~X7ElQ#sRMjR^Cd)wl zg^67Bgn9CK=WP%Ar>T4J!}DcLDe z=ehSmTp##KyQ78cmArL=IjOD6+n@jHCbOatm)#4l$t5YV?q-J86T&;>lEyK&9(XLh zr{kPuX+P8LN%rd%8&&Ia)iKX_%=j`Mr*)c)cO1`-B$XBvoT3yQCDKA>8F0KL$GpHL zPe?6dkE&T+VX=uJOjXyrq$BQ`a8H@wN1%0nw4qBI$2zBx)ID^6;Ux+? zu{?X$_1hoz9d^jkDJpT-N6+HDNo%^MQ2~yqsSBJj4@5;|1@w+BE04#@Jo4I63<~?O?ok%g%vQakTJKpMsk&oeVES1>cnaF7ZkFpqN6lx` zzD+YhR%wq2DP0fJCNC}CXK`g{AA6*}!O}%#0!Tdho4ooh&a5&{xtcFmjO4%Kj$f(1 zTk||{u|*?tAT{{<)?PmD_$JVA;dw;UF+x~|!q-EE*Oy?gFIlB*^``@ob2VL?rogtP z0M34@?2$;}n;^OAV2?o|zHg`+@Adk+&@Syd!rS zWvW$e5w{onua4sp+jHuJ&olMz#V53Z5y-FkcJDz>Wk%_J>COk5<0ya*aZLZl9LH}A zJhJ`Q-n9K+c8=0`FWE^x^xn4Fa7PDUc;v2+us(dSaoIUR4D#QQh91R!${|j{)=Zy1 zG;hqgdhSklM-VKL6HNC3&B(p1B)2Nshe7)F=-HBe=8o%OhK1MN*Gq6dBuPvqDRVJ{ z;zVNY?wSB%W0s^OMR_HL(Ws)va7eWGF*MWx<1wG7hZ}o=B62D?i|&0b14_7UG287YDr%?aYMMpeCkY1i`b+H!J9sqrvKc#Y6c8At@QiLSwj)@ifz~Z|c$lOMA@?cPqFRmZ%_>bz2X4(B=`^3;MDjsEeAO=? zSoD&+L>A|fGt7+6kF2@LqhL06sD%|~YsIe=EcWqy{e_61N_D(*CacnMvyXMjP87HI z4PT6!$fzxx{}=>jeqzkkoN+!r9e|@lZUN4pn(T28v`k=_vIhTn^i9O3qTqd)-%!QQ zYB6*6B@&b(!#X4C~59SLZuorNU_wWZA36{>O%iX)VS5NNZh49C_ppI>?)wwml}_0MLzOXT>lmo#&Ew6d?mu8~~I_^4VGBQtCAke;RQa5DL` z1PFDPsKb3CS$v;RhlQ1J@AHa1VRuuxp}NOIvrC>4$$A0Ix0VpAc0lfG%8{mR{TRQ( zbXM#1Tci3H*Wt>cVuMta^6^z`=^B@j+YhJqq9?>zZPxyg2U(wvod=uwJs{8gtpyab zXHQX<0FOGW6+dw&%c_qMUOI^+Rnb?&HB7Fee|33p4#8i>%_ev(aTm7N1f#6lV%28O zQ`tQh$VDjy8x(Lh#$rg1Kco$Bw%gULq+lc4$&HFGvLMO30QBSDvZ#*~hEHVZ`5=Kw z3y^9D512@P%d~s{x!lrHeL4!TzL`9(ITC97`Cwnn8PSdxPG@0_v{No|kfu3DbtF}K zuoP+88j4dP+Bn7hlGwU$BJy+LN6g&d3HJWMAd1P9xCXG-_P)raipYg5R{KQO$j;I9 z1y1cw#13K|&kfsRZ@qQC<>j=|OC?*v1|VrY$s=2!{}e33aQcZghqc@YsHKq^)kpkg z>B;CWNX+K=u|y#N)O>n5YuyvPl5cO6B^scmG?J zC8ix)E1PlhNaw8FpD+b|D$z`Id^4)rJe78MNiBga?Z- z0$L&MRTieSB1_E#KaN*H#Ns1}?zOA%Ybr{G+Sn3moXTVZj=L`nt?D&-MjOMz-Yq&@ z$P3h23d_F8Dcf*?txX7}p>nM*s+65t z1il8bHHsBynUK|aEXSjzY6sz1nZ%|%XeWTcGLRyRl@q4YAR)JovbdTTY&7u>@}28A zgV^Npp?}I!?3K7IXu9ml-Lw;w@9m zBYTeU+Seh8uJ-w?4e_6byq0f7>O3xm(hO}Y=fgU5^vW|>0yQ^0+?}LT55ei$i zzlU-iRbd8TRX9Ept%h%ariV=%u%F@@FA>U*XdAalcH%>#5_a&w)g`uW%3}m?vP- zc5}DkuF6ruKDwEYj+2YTSQ9=rkp19U5P@(zRm(nLod(sG9{~nw1BUoS2OFDXa{xfw zZ~UaZLFUZxfQ*9?_X?*~`d;nn-BbaefLJ`DT13KF6?T5Mnt;v5d>H}s)aAIzJcs#B z|CuXPJKww}hWBKsUfks#Kh$)ptp?5U1b@ttXFRbe_BZ&_R9XC6CA4WhWhMUE9Y2H4 z{w#CBCR<)Fd1M;mx*m?Z=L-^1kv1WKtqG(BjMiR4M^5yN4rlFM6oGUS2Wf~7Z@e*- ze84Vr`Bmi!(a1y}-m^HHMpbAiKPVEv|(7=|}D#Ihfk+-S5Hlkfch02z&$(zS3vrYz2g*ic{xBy~*gIp(eG}^gMc7 zPu2Eivnp@BH3SOgx!aJXttx*()!=2)%Bf$Gs^4cCs@)=(PJNxhH5lVY&qSZYaa?A^LhZW`B9(N?fx<^gCb(VE%3QpA*_Pohgp6vCB36iVaq zc1TI%L2Le?kuv?6Dq`H+W>AqnjyEzUBK948|DB|)U0_4DzWF#7L{agwo%y$hC>->r z4|_g_6ZC!n2=GF4RqVh6$$reQ(bG0K)i9(oC1t6kY)R@DNxicxGxejwL2sB<>l#w4 zE$QkyFI^(kZ#eE5srv*JDRIqRp2Totc8I%{jWhC$GrPWVc&gE1(8#?k!xDEQ)Tu~e zdU@aD8enALmN@%1FmWUz;4p}41)@c>Fg}1vv~q>xD}KC#sF|L&FU);^Ye|Q;1#^ps z)WmmdQI2;%?S%6i86-GD88>r|(nJackvJ#50vG6fm$1GWf*f6>oBiDKG0Kkwb17KPnS%7CKb zB7$V58cTd8x*NXg=uEX8Man_cDu;)4+P}BuCvYH6P|`x-#CMOp;%u$e z&BZNHgXz-KlbLp;j)si^~BI{!yNLWs5fK+!##G;yVWq|<>7TlosfaWN-;C@oag~V`3rZM_HN`kpF`u1p# ztNTl4`j*Lf>>3NIoiu{ZrM9&E5H~ozq-Qz@Lkbp-xdm>FbHQ2KCc8WD7kt?=R*kG# z!rQ178&ZoU(~U<;lsg@n216Ze3rB2FwqjbZ=u|J?nN%<4J9(Bl(90xevE|7ejUYm9 zg@E_xX}u2d%O1mpA2XzjRwWinvSeg)gHABeMH(2!A^g@~4l%8e0WWAkBvv60Cr>TR zQB1%EQ zUoZeUdqjh+1gFo6h~C~z#A57mf5ibmq$y_uVtA_kWv8X)CzfVEooDaY!#P?5$Y zGPKXbE<75nc%D-|w4OrP#;87oL@2^4+sxKah;a-5&z_&SUf~-z(1}bP=tM^GYtR3a z!x4zjSa^)KWG6jxfUI#{<26g$iAI;o_+B{LXY@WfWEdEl6%#8s3@b`?&Tm#aSK!~| z^%DdrXnijW`d!ajWuKApw&{L+WCPpFialo&^dZ9jC7A%BO`2ZF&YUDe;Yu|zFuv`2 z)BE*7Lkay)M7uohJ)446X``0x0%PzPTWY92`1Oq4a2D_7V0wypPnXFR)WM0IlFgg@ zqz#hv2xJEQL8eu}O;e(w4rSA?5|eZHbS6jENytJBq59?bOf>Wrl8ySZH36H(6fGR#vHM6q zn}!7!I@4$*+LFXs{x?|=q2*QtYT%Lw3+5(8uc0j8o3}TrG(zSV#>4wo6~)u|R+Yx# z?0$AspZDjv{dfv417~C17Oy%Fal{%+B6H(NX`$Bl>II-L3N3 zZc+sKZbqewU*&_Xt;9k=%4*aVYBvE1n&JZS7Uqjd%n8nOQmzh^x#vWK{;In~=QO)g zT-n3OU(1@3QfL|$g1d2xeBb@O15Rl01+hmpup2De7p%Yrd$E7(In!*R+;IJZh}v!svi z;7N~pq8KZDXXap0qd_D=Y^B)rz4S0^SF=&v6YYTAV$ad43#x!+n~-6< zK{8*vWoAdW(gGGt&URD}@g6tMoY(+Lw=vvxhfIIK9AjvNF_(W}1Rxn(mp;tJfDV<0 zbJN0t(@Xb8UeO{&T{$$uDrs7)j$}=?WsuDl+T2N5Y<4TMHGOMcocPr$%~(yvtKv(n z`U96d!D0cb9>Dx2zz$m&lAhazs%UeR^K*gb>d8CPs+?qlpfA;t{InXa)^2ryC(FU(Zc6Xbnnh`lg`K&g^JeS>}^c0MJKUCfV+~ zV(EN0Z5ztoN;hqcj!8V+VRbSltJ<~|y`U+9#wv|~H zNE!j9uXa=dec@JQSgJ6N6@Il&tzCBJv9#ldR`Lm*<)YwH4tdlAlG0Fl8Nfa(J~c%DQ2AA-}x8D=p(l#n1+hgx;N;1Aq?lq@{Lt9FKu89CjnnHD1G_@p;%Lp`+b@ttb33!E_Xt;QUD9~nRQl&xAro9-{+&6^ljK2f-d>&qy&d#0xwH z@slNv@ULKp!Cf*JHuS@#4c?F->WjPc)yiuSargAIEg>muRxzY?Hzdq@G5CS)U1*Et zE2SLh=@DI1J(guiy2Igq(?(xI9WL%g^f@{5Hmr|!Qz4`vn|LjrtO=b~I6~5EU5Fxy z;-#<)6w#w=DkpSthAu+E;OL?!?6C9Mwt*o(@68(Jhvs-eX4V z=d=>HI|`3J%H5X|gSrC8KH^IL?h5=3ID6svwHH@(wRbSG`Zsor^q4`3PCn#-(YX?< z_q8+T)51$E0xyKR{L!LN(G=+9K6$3#PDT^IAe|Igkx=!4#rqKWoXiZdh`&ocjp=Ok zemJe6*{it~>;sr(B0fSmp(S#*y5I0)OOz~Oe6Im+($S}e3tyx7Y6pA8vKCBmSEQDa zLfkm*;uMbTLpcR0)tF_v-lbK%`5>POyI2E(!)2=Rj0p;WKi=|UNt6HsQv0xR3QIK9 zsew(AFyzH!7Azxum{%VC^`cqhGdGbABGQ4cYdNBPTx+XpJ=NUEDeP^e^w^AOE1pQI zP{Us-sk!v$gj}@684E!uWjzvpoF|%v-6hwnitN1sCSg@(>RDCVgU8Ile_-xX`hL6u zzI4*Q)AVu(-ef8{#~P9STQ5t|qIMRoh&S?7Oq+cL6vxG?{NUr@k(~7^%w)P6nPbDa~4Jw}*p-|cT4p1?)!c0FoB(^DNJ+FDg+LoP6=RgB7Or673WD5MG&C!4< zerd6q$ODkBvFoy*%cpHGKSt z3uDC6Sc=xvv@kDzRD)aIO`x}BaWLycA%(w-D`Pd+uL*rL|etagQ;U&xt_9?7#}=}5HI)cU-0 z%pMA`>Xb7s)|Y)4HKSZOu;{lg=KjeIyXb0{@EM`FTDkLRH`!W%z*lQJ74P%Ka76)H zblrSIzf+dMWbO`g;=(b@{pS)zUcO&GrIFe%&?YeX4r8B2bBArB%-5ZrQ+vonr%AYy z1+u0*K{UVUmV>h5vD!F;6}a%KdMZQLs04oGkpiaC)zI( zT2U9qta5o|6Y+It1)sE8>u&0)W~l$NX@ZQ8UZfB=`($EW6?FT%{EoRhOrb9)z@3r8y?Z99FNLDE;7V=Q zotj&igu*Rh^VQn3MQKBq!T{yTwGhn1YL6k*?j?{_ek5xe8#i#GG4S-a_Re2lssG!} z`Y-d0BcOdB@!m?4y&hMN68}#0-IIlm_xO)d#}ugX{q^OZe{-@LeJyv`cY&ze4t2~! zKb{qX-j;kt{?gC(vW%}X4pm@1F?~LH{^Q8d@X$dy@5ff~p!J3zmA>H`A)y+6RB_h* zZfIO+bd=*LiymRw{asW%xxaVl33_xtdVrrqIPn zc@y8oMJvNtgcO~4i0`f)GCFkWY8EF?4duLVjHTdb6oYLnO9}Q-pe{CKQJL)hV8)JI z$mVA0Dq&7Z1TbYdSC(WbJ+IBjXngZTu&I+vHF|>Zo$757{8lL;8Zr-Exkf?3jzN5k z_d9I>{>^J?!l)< zNd$7E9FVrta}3qy3L7Ys$^fRWNuu^hs^{*eXvazd&+Q*?lTfc>2+EdP(o0P_Z05HX zVKsfFAQ{t^CRu~Dw(CuJ>tvx*p$5@flA>QRl455b&{*U?xU8`)nF2T$uu_(l8VNtq z?pBiRQIckGzk8W&SFSB=g6eG`ZC;6v9w`?eF*S}3E@N`2ropeHP)E}o?qJkyVEI;K$!)bWY zt9>4WmDVJh7U~m$|K`T#hF!v|znj^=M;69uXrFys#51XT;DbMr4H)>7UQ1e2(cuQf z4kr~Tt1tpBB2GaJ(|j~lHgW40EgMMVqR6eJoJig1SBg|2=$~4I3P0eP$q%_`sS&4~ z26=&a&tLjQbch1`cVXa-2fTl1y8}->|Nqu?uVrNTov!=VKh)g89wUPTgAzkSKZ57_ zr=B^mcldE3K04t4{;RaG53&9yovq;@aR#VHx+R1^^*kr-vEEd!uea68Z<{R%_DD6fn&T4 zu;fDj07L-(_fLSJGdkeh&c&7A(ZLj`7iwnkAcqUexU;WjUkqeg1m1-IUZTIZA(4dtr2Gr`e{BIejlCgS<33MB=1!8?a74!F%=Uo7N`F@k} ze+1C_eU4Y_$mvdjci zwEtCIphA2PBzBhng5=M#e4r%)RW5rVD|_`PvY$7BK`}w~d>%0O9sY#*LUAq=^OjMF^PY5m<7!=s5jyRfosCQAo#hL`h5vN-M}6Q z0Li}){5?wi8)GVHNkF|U9*8V5ej)nhb^TLw1KqiPK(@{P1^L&P=`ZNt?_+}&0(8Uh zfyyZFPgMV7ECt;Jdw|`|{}b$w4&x77VxR>8wUs|GQ5FBf1UlvasqX$qfk5rI4>Wfr zztH>y`=daAef**C12yJ7;LDf&3;h3X+5@dGPy@vS(RSs3CWimbTp=g '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s +' "$PWD" ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum -warn ( ) { +warn () { echo "$*" -} +} >&2 -die ( ) { +die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac -# For Cygwin, ensure paths are in UNIX format before anything is touched. -if $cygwin ; then - [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` -fi - -# Attempt to set APP_HOME -# Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi -done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >&- -APP_HOME="`pwd -P`" -cd "$SAVED" >&- - CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -82,83 +133,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules -function splitJvmOpts() { - JVM_OPTS=("$@") -} -eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS -JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" -exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index aec99730b..9b42019c7 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,4 +1,22 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem +@rem SPDX-License-Identifier: Apache-2.0 +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -8,26 +26,30 @@ @rem Set local scope for the variables with windows NT shell if "%OS%"=="Windows_NT" setlocal -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= - set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -35,54 +57,36 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail -:init -@rem Get command-line arguments, handling Windowz variants - -if not "%OS%" == "Windows_NT" goto win9xME_args -if "%@eval[2+2]" == "4" goto 4NT_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* -goto execute - -:4NT_args -@rem Get arguments from the 4NT Shell from JP Software -set CMD_LINE_ARGS=%$ - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From e7c9ad4c5339d126630d0dbb7a090761e28313e2 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Thu, 19 Dec 2024 10:54:51 +0000 Subject: [PATCH 04/35] =?UTF-8?q?=F0=9F=9A=80[Release=20v2.13.0]=20Merge?= =?UTF-8?q?=20into=20Main=20(#301)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 🚀[Release v2.11.1 071024] Merge into Develop (#245) * replaced the control in PeerManager.c Signed-off-by: kcw-grunt * reverted BRBitcoinAmount Signed-off-by: kcw-grunt * Cleaning up the files Signed-off-by: kcw-grunt * Removed Pusher remnants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Added Fiat feature, and FCM for push notifications (#247) * Added fiat feature based on language user chose * added fcm for push notifications * Update PushNotificationService.kt * Added new Icon, removed duplicate fcm library, added in app messaging * Added new Icon, removed duplicate fcm library, added in app messaging * Tech debt/add af sdk (#248) * Recent newline Signed-off-by: kcw-grunt * AF added Refactored cruft - AF working Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/refactor brevents syncmarkers (#254) * Tech debt/add af sdk (#248) - AF working - Changed requiredActivity - Added analytics error report - test this.Activity is null or not - Bugfix - Phrase Reminder crash - added an exception handler for UserNotAuthenticatedException. - note: this should allow for the system to display the native authorization UI when needed - fixes issue - https://console.firebase.google.com/u/0/project/litewallet-beta/crashlytics/app/android:com.loafwallet/issues/09dac17241309f0e823ef597a9a82cd4 - Added dev note - remove calls to BREventManager - removed BREventManager - renamed error message - fixed Firebase Analytics event error Signed-off-by: kcw-grunt * version bump update gitignore Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/ fixed send issue add syncing measurements (#255) * removed unused BRSharedPreferences Signed-off-by: kcw-grunt * started commenting out more Timber Signed-off-by: kcw-grunt * Added more shared preferences for syncing Signed-off-by: kcw-grunt * Commented out many verbose Timber logs Signed-off-by: kcw-grunt * WIP Still trying to figure out the eplased time and last and start timestamps…??? I dunno Signed-off-by: kcw-grunt * Moved sync measurment into the method calls Signed-off-by: kcw-grunt * Debugged the install issue with send - Moved all the measurements in new methods - recalculated measure points - Added analytics - event did_complete_sync was shown! - adding a dummy file to make sure file system is set ||||WIP still verifying the JSON is present - added a blockheight label - changed the sleep time (a hack to allow the device time to refresh the view) to 100 ms from 500ms!! -WIP Why is the bundle not showing on time Signed-off-by: kcw-grunt * replaces afID to first location Signed-off-by: kcw-grunt * Removed redundant sync measurements Signed-off-by: kcw-grunt * code bump Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/test refactor cruft removal (#250) * Removed non english seed words -Refactor tests - Reset AnalyticsTests Signed-off-by: kcw-grunt * Rename Bitcoinletter to Litecoinletters -added tests testLitecoinSymbolConstants testAppExternalURLConstants testFirebaseAnalyticsConstants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * fix: [#152] make sure using Fragment, FragmentManager and FragmentTransaction from androidx (#260) * fix: [#126] the issue came from FragmentBalanceSeedReminder.fetchSeedPhrase, so we just wrap with runCatching for now to avoid crash (#263) * fix: [#264] add null checking for BRKeyStore.removeAliasAndFiles (#270) * fix: [#265] add null checking, migrate viewpage… (#271) * fix: [WIP][#265] work in progress add null checking, migrate viewpager to viewpager2 and more * fix: [#265] finalize migration some deprecated class and implement null checking at FragmentTransactionItem * fix: dix dismiss outside FragmentTransactionDetails * 🚀[Release v2.12.2 20241118] Merge into Develop (#272) * 🚀[Release v2.11.1 071024] Merge into Main (#246) * replaced the control in PeerManager.c Signed-off-by: kcw-grunt * reverted BRBitcoinAmount Signed-off-by: kcw-grunt * Cleaning up the files Signed-off-by: kcw-grunt * Removed Pusher remnants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * version bump Signed-off-by: kcw-grunt * Removed donation button removed xml removed fragments removed references of the donation removed analytical events Signed-off-by: kcw-grunt * fix: prevent activity close * code bump Signed-off-by: kcw-grunt * enabled for Debug Signed-off-by: kcw-grunt * fix: [#264] add null checking for BRKeyStore.removeAliasAndFiles (#270) * fix: [#265] add null checking, migrate viewpage… (#271) * fix: [WIP][#265] work in progress add null checking, migrate viewpager to viewpager2 and more * fix: [#265] finalize migration some deprecated class and implement null checking at FragmentTransactionItem * fix: dix dismiss outside FragmentTransactionDetails * code bump Signed-off-by: kcw-grunt * version bump Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt Co-authored-by: andhikayuana * revert minSDkVersion to 29 (#276) Signed-off-by: kcw-grunt * fix: [#258] avoid OOM with cache the result of opsAll, previously called multiple times at onBindViewHolder (#277) * fix: [#258] avoid OOM with cache the result of opsAll, previously called multiple times at onBindViewHolder * chore: [#258] cleanup comment * fix: [#266] add null checking and default value based on iOS since getCurrency can produce null (#278) * fix: [#266] add null checking and default value based on iOS since getCurrency can produce null * chore: [#266] cleanup comment * fix: [#274] fixing locale on android 14 and setup test using junit & mockk (#282) * chore: [#274] initial setup test using JUnit & MockK * chore: [#274] add more test case at LocaleHelperTest * fix: [#274] fix default language from shared preferences * fix: [#274] disable language split on AAB * chore: [#274] rename method * chore: [#126] simplify exception handling at BRKeyStore._getData and record exception to Firebase Crashlytics (#283) * Techdebt/remove auth prompt (#256) * Check reenter pin freeze after the auth prompt deleted. Signed-off-by: kcw-grunt * cleaned up deletion Signed-off-by: kcw-grunt * Many removals - Removed the fingerprint xml - Removed the Fingerprint Fragment and java files - Removed the enum referencing the fingerprint prompt - Removed check of fingerprint enrollment and checks
- Removed biometrics and fingerprint permissions Signed-off-by: kcw-grunt * removed fingerprint image code and version bump Signed-off-by: kcw-grunt * chore: remove FragmentFingerprint.java since, not used and failed to build --------- Signed-off-by: kcw-grunt Co-authored-by: josikie Co-authored-by: andhikayuana * 🚀[Release v2.12.4 20241130] Merge into Develop (#286) * 🚀[Release v2.11.1 071024] Merge into Main (#246) * replaced the control in PeerManager.c Signed-off-by: kcw-grunt * reverted BRBitcoinAmount Signed-off-by: kcw-grunt * Cleaning up the files Signed-off-by: kcw-grunt * Removed Pusher remnants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * 🚀[Release v2.12.2 20241118] Merge into Main (#273) * 🚀[Release v2.11.1 071024] Merge into Develop (#245) * replaced the control in PeerManager.c Signed-off-by: kcw-grunt * reverted BRBitcoinAmount Signed-off-by: kcw-grunt * Cleaning up the files Signed-off-by: kcw-grunt * Removed Pusher remnants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Added Fiat feature, and FCM for push notifications (#247) * Added fiat feature based on language user chose * added fcm for push notifications * Update PushNotificationService.kt * Added new Icon, removed duplicate fcm library, added in app messaging * Added new Icon, removed duplicate fcm library, added in app messaging * Tech debt/add af sdk (#248) * Recent newline Signed-off-by: kcw-grunt * AF added Refactored cruft - AF working Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/refactor brevents syncmarkers (#254) * Tech debt/add af sdk (#248) - AF working - Changed requiredActivity - Added analytics error report - test this.Activity is null or not - Bugfix - Phrase Reminder crash - added an exception handler for UserNotAuthenticatedException. - note: this should allow for the system to display the native authorization UI when needed - fixes issue - https://console.firebase.google.com/u/0/project/litewallet-beta/crashlytics/app/android:com.loafwallet/issues/09dac17241309f0e823ef597a9a82cd4 - Added dev note - remove calls to BREventManager - removed BREventManager - renamed error message - fixed Firebase Analytics event error Signed-off-by: kcw-grunt * version bump update gitignore Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/ fixed send issue add syncing measurements (#255) * removed unused BRSharedPreferences Signed-off-by: kcw-grunt * started commenting out more Timber Signed-off-by: kcw-grunt * Added more shared preferences for syncing Signed-off-by: kcw-grunt * Commented out many verbose Timber logs Signed-off-by: kcw-grunt * WIP Still trying to figure out the eplased time and last and start timestamps…??? I dunno Signed-off-by: kcw-grunt * Moved sync measurment into the method calls Signed-off-by: kcw-grunt * Debugged the install issue with send - Moved all the measurements in new methods - recalculated measure points - Added analytics - event did_complete_sync was shown! - adding a dummy file to make sure file system is set ||||WIP still verifying the JSON is present - added a blockheight label - changed the sleep time (a hack to allow the device time to refresh the view) to 100 ms from 500ms!! -WIP Why is the bundle not showing on time Signed-off-by: kcw-grunt * replaces afID to first location Signed-off-by: kcw-grunt * Removed redundant sync measurements Signed-off-by: kcw-grunt * code bump Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * Techdebt/test refactor cruft removal (#250) * Removed non english seed words -Refactor tests - Reset AnalyticsTests Signed-off-by: kcw-grunt * Rename Bitcoinletter to Litecoinletters -added tests testLitecoinSymbolConstants testAppExternalURLConstants testFirebaseAnalyticsConstants Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt * fix: [#152] make sure using Fragment, FragmentManager and FragmentTransaction from androidx (#260) * fix: [#126] the issue came from FragmentBalanceSeedReminder.fetchSeedPhrase, so we just wrap with runCatching for now to avoid crash (#263) * version bump Signed-off-by: kcw-grunt * Removed donation button removed xml removed fragments removed references of the donation removed analytical events Signed-off-by: kcw-grunt * fix: prevent activity close * code bump Signed-off-by: kcw-grunt * enabled for Debug Signed-off-by: kcw-grunt * fix: [#264] add null checking for BRKeyStore.removeAliasAndFiles (#270) * fix: [#265] add null checking, migrate viewpage… (#271) * fix: [WIP][#265] work in progress add null checking, migrate viewpager to viewpager2 and more * fix: [#265] finalize migration some deprecated class and implement null checking at FragmentTransactionItem * fix: dix dismiss outside FragmentTransactionDetails * code bump Signed-off-by: kcw-grunt * version bump Signed-off-by: kcw-grunt --------- Signed-off-by: kcw-grunt Co-authored-by: Josi Kie <54074780+josikie@users.noreply.github.com> Co-authored-by: Andhika Yuana * bumped version and code * compiled the env (#284) please check @andhikayuana @josikie that you are able to compile * Users complained that recyclerView was setting to the wrong language - Polished to index to a language - bump code * chore: [#126] simplify exception handling at BRKeyStore._getData and record exception to Firebase Crashlytics (#283) --------- Signed-off-by: kcw-grunt Co-authored-by: Josi Kie <54074780+josikie@users.noreply.github.com> Co-authored-by: Andhika Yuana * Added abilities to copy transaction id by click the transaction id (#290) * Added abilities to copy transaction id by click the transaction id * Update build.gradle * version and code bump * Unit Test(s) Migration (#287) * chore: [test] add BRConstantsTest for unitTest and deprecated ConstantsTests inside androidTest * chore: [test] remove unused test files * chore: [test] add todo to fill the tests and change package name * chore: [test] add basic example * chore: [test] integrate with ci/cd * chore: [test][ci] fix Cannot find a definition for command named android/restore-gradle-cache * chore: [test][ci] fix Cannot find a definition for command named android/run-tests * chore: [test][ci] fix Unexpected argument(s): test-command * chore: [test][ci] fix Cannot find a definition for executor named android/android-docker * chore: [test][ci] add default for gradle.properties * chore: [test][ci] avoid breaking in build.gradle * chore: [test][ci] enable androidx and jetifier * chore: [test][ci] init submodule * chore: [test][ci] just using run command for execute unit test step * chore: [test][ci] add env for google-services.json * chore: [test][ci] add env for google-services.json * chore: [test][ci] add env for google-services.json * Major refactor of code to move classes to the ideal location - Removed cruft - Moved all tests to the unitTest directory - Remove poorly placed tests into (AndroidTest) - Commented old code into the new Kotlin class(es) - working in the first test of currency - Removed the paymentRequest method - Renamed Classes * chore: resolve CurrencyTests * Added notes --------- Co-authored-by: Kerry Washington * feat: remote config feature toggle (#293) * feat: [remote-config] wip for remote config feature toggle * feat: [remote-config] implement base url toggle for prod and dev * feat: [test] add BRApiManagerTest * fix: fix getBaseUrlProd for FragmentBuy and add test * chore: change BRApiManager.fetchRates using getBaseUrlProd * version and code bump * Update .gitignore --------- Signed-off-by: kcw-grunt Co-authored-by: Josi Kie <54074780+josikie@users.noreply.github.com> Co-authored-by: Andhika Yuana Co-authored-by: josikie --- .circleci/config.yml | 46 +- .gitignore | 2 + .idea/inspectionProfiles/Project_Default.xml | 3 + app/build.gradle | 51 +- .../java/com/litewallet/PaperKeyTests.java | 81 -- .../litewallet/database/DatabaseTests.java | 160 --- .../com/litewallet/platform/KVStoreTests.java | 590 --------- .../litewallet/platform/PlatformTests.java | 92 -- .../activities/tests/MainActivityTest.java | 132 -- .../tests/CurrencyFormatterFixedTests.java | 129 -- .../tests/CurrencyFormatterTests.java | 129 -- .../litewallet/security/KeyStoreTests.java | 322 ----- .../litewallet/security/NewKeyStoreTests.java | 176 --- .../com/litewallet/wallet/WalletTests.java | 115 -- app/src/main/AndroidManifest.xml | 7 - .../main/java/com/breadwallet/BreadApp.java | 9 +- .../presenter/activities/LoginActivity.java | 24 - .../activities/intro/WriteDownActivity.java | 12 +- .../settings/FingerprintActivity.java | 158 --- .../settings/SecurityCenterActivity.java | 17 - .../activities/settings/SettingsActivity.java | 17 - .../presenter/activities/util/BRActivity.java | 2 +- .../presenter/fragments/FragmentBuy.java | 6 +- .../fragments/FragmentFingerprint.java | 235 ---- .../presenter/fragments/FragmentMenu.java | 33 +- .../fragments/FragmentTransactionItem.java | 22 + .../tools/manager/BRApiManager.java | 45 +- .../tools/manager/PromptManager.java | 17 - .../tools/security/AuthManager.java | 94 -- .../breadwallet/tools/security/BRSender.java | 12 +- .../tools/sqlite/CurrencyDataSource.java | 1 - .../tools/threads/PaymentProtocolTask.java | 14 +- .../breadwallet/tools/util/BRConstants.java | 2 + .../com/breadwallet/tools/util/Utils.java | 19 - .../data/source/RemoteConfigSource.kt | 76 ++ app/src/main/java/com/litewallet/di/Module.kt | 19 + app/src/main/res/layout/activity_pin.xml | 11 - .../layout/fingerprint_dialog_container.xml | 137 --- .../main/res/xml/remote_config_defaults.xml | 27 + .../com/litewallet/currency/CurrencyTests.kt | 100 ++ .../RemoteConfigSourceFirebaseImplTest.kt | 71 ++ .../com/litewallet/example/ExampleTest.kt | 125 ++ .../tools/database/DatabaseTests.kt | 85 ++ .../database/TransactionDataSourceTest.kt | 3 + .../tools/manager/BRApiManagerTest.kt | 122 ++ .../tools/security/BRKeyStoreTest.kt | 1059 +++++++++++++++++ .../tools/security/NewKeyStoreTests.kt | 190 +++ .../tools/security/ReplicatedKVStoreTest.kt | 4 + .../litewallet/tools/util/BRConstantsTest.kt | 108 ++ .../litewallet/tools/util/Bip39ReaderTest.kt | 4 + .../litewallet/tools/util/LocaleHelperTest.kt | 64 + .../litewallet/tools/util/SeedWordTests.kt | 89 ++ 52 files changed, 2313 insertions(+), 2755 deletions(-) delete mode 100644 app/src/androidTest/java/com/litewallet/PaperKeyTests.java delete mode 100644 app/src/androidTest/java/com/litewallet/database/DatabaseTests.java delete mode 100644 app/src/androidTest/java/com/litewallet/platform/KVStoreTests.java delete mode 100644 app/src/androidTest/java/com/litewallet/platform/PlatformTests.java delete mode 100644 app/src/androidTest/java/com/litewallet/presenter/activities/tests/MainActivityTest.java delete mode 100644 app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterFixedTests.java delete mode 100644 app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterTests.java delete mode 100644 app/src/androidTest/java/com/litewallet/security/KeyStoreTests.java delete mode 100644 app/src/androidTest/java/com/litewallet/security/NewKeyStoreTests.java delete mode 100644 app/src/androidTest/java/com/litewallet/wallet/WalletTests.java delete mode 100644 app/src/main/java/com/breadwallet/presenter/activities/settings/FingerprintActivity.java delete mode 100644 app/src/main/java/com/breadwallet/presenter/fragments/FragmentFingerprint.java create mode 100644 app/src/main/java/com/litewallet/data/source/RemoteConfigSource.kt create mode 100644 app/src/main/java/com/litewallet/di/Module.kt delete mode 100644 app/src/main/res/layout/fingerprint_dialog_container.xml create mode 100644 app/src/main/res/xml/remote_config_defaults.xml create mode 100644 app/src/test/java/com/litewallet/currency/CurrencyTests.kt create mode 100644 app/src/test/java/com/litewallet/data/source/RemoteConfigSourceFirebaseImplTest.kt create mode 100644 app/src/test/java/com/litewallet/example/ExampleTest.kt create mode 100644 app/src/test/java/com/litewallet/tools/database/DatabaseTests.kt create mode 100644 app/src/test/java/com/litewallet/tools/database/TransactionDataSourceTest.kt create mode 100644 app/src/test/java/com/litewallet/tools/manager/BRApiManagerTest.kt create mode 100644 app/src/test/java/com/litewallet/tools/security/BRKeyStoreTest.kt create mode 100644 app/src/test/java/com/litewallet/tools/security/NewKeyStoreTests.kt create mode 100644 app/src/test/java/com/litewallet/tools/security/ReplicatedKVStoreTest.kt create mode 100644 app/src/test/java/com/litewallet/tools/util/BRConstantsTest.kt create mode 100644 app/src/test/java/com/litewallet/tools/util/Bip39ReaderTest.kt create mode 100644 app/src/test/java/com/litewallet/tools/util/LocaleHelperTest.kt create mode 100644 app/src/test/java/com/litewallet/tools/util/SeedWordTests.kt diff --git a/.circleci/config.yml b/.circleci/config.yml index 6554e1f4e..4685ef3ef 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,25 +2,51 @@ # See: https://circleci.com/docs/2.0/configuration-reference version: 2.1 +orbs: + android: circleci/android@3.0.2 + # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: - say-hello: - # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. - # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor - docker: - - image: cimg/base:stable - # Add steps to the job - # See: https://circleci.com/docs/2.0/configuration-reference/#steps + unit-test: + executor: + name: android/android_machine + resource_class: large + tag: default steps: - checkout - run: name: "Say hello" - command: "echo Hello, World!" + command: "echo Hello, World! && ls" + - run: + name: "Default for gradle.properties" + command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" + - run: + name: "Initialize submodule" + command: "git submodule init && git submodule update --init --recursive" + - run: + name: "Copy google-services.json" + command: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json + - android/restore_gradle_cache + - run: + name: "Execute Unit Test" + command: ./gradlew testLitewalletDebugUnitTest + - android/save_gradle_cache + - run: + name: Save test results + command: | + mkdir -p ~/test-results/junit/ + find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; + when: always + - store_test_results: + path: ~/test-results + - store_artifacts: + path: ~/test-results/junit + # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: - say-hello-workflow: + test-and-build: jobs: - - say-hello + - unit-test diff --git a/.gitignore b/.gitignore index 87c9c4c0e..c600a1287 100644 --- a/.gitignore +++ b/.gitignore @@ -103,3 +103,5 @@ app/src/litewalletDebug/google-services.json /.idea/dictionaries/grunt.xml androidTestResultsUserPreferences.xml .idea/deploymentTargetSelector.xml +.idea/inspectionProfiles/Project_Default.xml +app/.idea/gradle.xml diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml index ecd817f69..f7d4c3d75 100644 --- a/.idea/inspectionProfiles/Project_Default.xml +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -1,6 +1,9 @@

-// * Created by Mihail on 7/7/15. -// * Copyright (c) 2015 Mihail Gutan -// *

-// * Permission is hereby granted, free of charge, to any person obtaining a copy -// * of this software and associated documentation files (the "Software"), to deal -// * in the Software without restriction, including without limitation the rights -// * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// * copies of the Software, and to permit persons to whom the Software is -// * furnished to do so, subject to the following conditions: -// *

-// * The above copyright notice and this permission notice shall be included in -// * all copies or substantial portions of the Software. -// *

-// * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// * THE SOFTWARE. -// */ -// -//@RunWith(AndroidJUnit4.class) -//public class MainActivityTest { -// -// @Rule -// public ActivityTestRule mActivityRule = -// new ActivityTestRule<>(MainActivity.class); -// -// @Test -// public void testCurrencyList() { -// onView(withId(R.id.main_button_burger)).perform(click()); -// onView(withId(R.id.settings)).perform(click()); -// onView(withId(R.id.local_currency)).perform(click()); -// if (!CurrencyListAdapter.currencyListAdapter.isEmpty()) { -// Random r = new Random(); -// -// //click on random list item; -// int rand; -// rand = r.nextInt(CurrencyListAdapter.currencyListAdapter.getCount()); -// onData(hasToString(startsWith(""))) -// .inAdapterView(withId(R.id.currency_list_view)).atPosition(rand) -// .perform(click()); -// rand = r.nextInt(CurrencyListAdapter.currencyListAdapter.getCount()); -// onData(hasToString(startsWith(""))) -// .inAdapterView(withId(R.id.currency_list_view)).atPosition(rand) -// .perform(click()); -// rand = r.nextInt(CurrencyListAdapter.currencyListAdapter.getCount()); -// onData(hasToString(startsWith(""))) -// .inAdapterView(withId(R.id.currency_list_view)).atPosition(rand) -// .perform(click()); -// rand = r.nextInt(CurrencyListAdapter.currencyListAdapter.getCount()); -// onData(hasToString(startsWith(""))) -// .inAdapterView(withId(R.id.currency_list_view)).atPosition(rand) -// .perform(click()); -// rand = r.nextInt(CurrencyListAdapter.currencyListAdapter.getCount()); -// onData(hasToString(startsWith(""))) -// .inAdapterView(withId(R.id.currency_list_view)).atPosition(rand) -// .perform(click()); -// } -// pressBack(); -// pressBack(); -// pressBack(); -// -// } -// -//// @Test -//// public void testFragments() { -//// onView(withId(R.id.main_button_burger)).perform(click()); -//// try { -//// Thread.sleep(1000); -//// } catch (InterruptedException e) { -//// e.printStackTrace(); -//// } -//// onView(withId(R.id.settings)).perform(click()); -//// onView(withId(R.id.start_recovery_wallet)).perform(click()); -//// pressBack(); -//// pressBack(); -//// onView(withId(R.id.about)).perform(click()); -//// onView(withId(R.id.main_button_burger)).perform(click()); -//// onView(withId(R.id.main_button_burger)).perform(click()); -//// onView(withId(R.id.main_button_burger)).perform(click()); -//// onView(withId(R.id.main_layout)).perform(swipeLeft()); -//// onView(withId(R.id.theAddressLayout)).perform(click()); -//// onView(withId(R.id.copy_address)).perform(click()); -//// onView(withId(R.id.main_layout)).perform(swipeRight()); -//// try { -//// Thread.sleep(500); -//// } catch (InterruptedException e) { -//// e.printStackTrace(); -//// } -//// } -//// -//// -//// @Test -//// public void testChangeText_sameActivity() { -//// onView(withId(R.id.address_edit_text)) -//// .perform(clearText(), typeText("some testing text"), closeSoftKeyboard(), clearText()); -//// -//// } -//} diff --git a/app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterFixedTests.java b/app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterFixedTests.java deleted file mode 100644 index 40b915d71..000000000 --- a/app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterFixedTests.java +++ /dev/null @@ -1,129 +0,0 @@ -//package com.breadwallet.presenter.currencyformatting.tests; -// -//import android.test.AndroidTestCase; -// -//import java.util.Locale; -// -// -///** -// * ISO 4217 currency codes can be found here: -// * http://en.wikipedia.org/wiki/ISO_4217 -// */ -// -// -//public class CurrencyFormatterFixedTests extends AndroidTestCase { -// /** -// * These tests differ between Android <= 4.0, and >= 4.1. -// */ -// -// public void testJPY() { -// String expected = "¥15"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("ja", "JP"), "JPY", 15.00); -// assertEquals(expected, actual); -// // 4.0 and below - Fails. (¥15.00) -// // 4.1 and above - Fails (¥15) -// // Yes, that is a different yen symbol to what we get with ja_JP locale (note -// // we're forcing en_US in the CurrencyFormatter). -// } -// -// public void testNZDollarsInJapanLocale() { -// // New Zealand currency, but the device locale is ja_JP -// String expected = "NZ$16"; // Yen has no decimal units -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("ja", "JP"), "NZD", 15.50); -// assertEquals(expected, actual); -// // 4.0 and below - Fails (NZ$15.50). -// } -// -// public void testCLP() { -// String expected = "$15"; // Chilean Peso has no decimal units. -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("es", "CL"), "CLP", 15.00); -// assertEquals(expected, actual); -// // 4.0 and below - Fails. (CL$ 15,00) -// // 4.1 and above - Fails (CLP15). -// } -// -// public void testNZDollarsInChileLocale() { -// // New Zealand currency, but the device locale is es_CL -// String expected = "NZ$16"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("es", "CL"), "NZD", 15.50); -// assertEquals(expected, actual); -// // 4.0 and below - Fails. (NZ$15,50) -// } -// -// public void testNZDollarsInAustralianLocale() { -// String expected = "NZ$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "AU"), "NZD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testAustralianDollarsInNewZealandLocale() { -// String expected = "AU$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "NZ"), "AUD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testGBP() { -// String expected = "£15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "GB"), "GBP", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testNZD() { -// String expected = "$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "NZ"), "NZD", 15.00); -// assertEquals(expected, actual); -// // Fails - NZ$15.00 -// } -// -// public void testUSD() { -// String expected = "$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "US"), "USD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testNZDInUSLocale() { -// String expected = "NZ$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "US"), "NZD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testEURInFranceLocale() { -// String expected = "15,00 €"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("fr", "FR"), "EUR", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testEURInRepublicOfIrelandLocale() { -// String expected = "€15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "IE"), "EUR", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testUSDollarsInCanadianLocale() { -// String expected = "US$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "CA"), "USD", 15.00); -// assertEquals(expected, actual); -// // Fails - $15.00 -// } -// -// public void testCanadianDollarsInUSLocale() { -// String expected = "CA$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "US"), "CAD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testEurosInUnitedKingdomLocale() { -// String expected = "€15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("en", "GB"), "EUR", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testPoundsInFranceLocale() { -// String expected = "15,00 £UK"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringFixed(new Locale("fr", "FR"), "GBP", 15.00); -// assertEquals(expected, actual); -// // Fails - 15,00 £ -// } -// -// -//} \ No newline at end of file diff --git a/app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterTests.java b/app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterTests.java deleted file mode 100644 index 30e09f0e5..000000000 --- a/app/src/androidTest/java/com/litewallet/presenter/currencyformatting/tests/CurrencyFormatterTests.java +++ /dev/null @@ -1,129 +0,0 @@ -//package com.breadwallet.presenter.currencyformatting.tests; -// -//import android.test.AndroidTestCase; -// -//import java.util.Locale; -//public class CurrencyFormatterTests extends AndroidTestCase { -// /** -// * These tests differ between Android <= 4.0, and >= 4.1. -// */ -// -// public void testJPY() { -// String expected = "¥15"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("ja", "JP"), "JPY", 15.00); -// assertEquals(expected, actual); -// // 4.0 and below - Fails. (¥15.00) -// // 4.1 and above - Passes. -// } -// -// public void testNZDollarsInJapanLocale() { -// // New Zealand currency, but the device locale is ja_JP -// String expected = "NZ$16"; // Yen has no decimal units -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("ja", "JP"), "NZD", 15.50); -// assertEquals(expected, actual); -// // 4.0 and below - Fails. NZ$15.50. -// // 4.1 and above - Passes. -// } -// -// public void testCLP() { -// String expected = "$15"; // Chilean Peso has no decimal units. -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("es", "CL"), "CLP", 15.00); -// assertEquals(expected, actual); -// // 4.0 and below - Fails. (CL$ 15,00) -// // 4.1 and above - Passes. -// } -// -// public void testNZDollarsInChileLocale() { -// // New Zealand currency, but the device locale is es_CL -// String expected = "NZ$16"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("es", "CL"), "NZD", 15.50); -// assertEquals(expected, actual); -// // 4.0 and below - Fails. NZD 15,50 -// // 4.1 and above - Fails. NZD16 -// } -// -// /** -// * These tests fail, presumably because the formatting tables for the provided locales don't -// * have a custom currency unit for the supplied ISO currency code. -// */ -// -// public void testNZDollarsInAustralianLocale() { -// String expected = "NZ$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "AU"), "NZD", 15.00); -// assertEquals(expected, actual); -// // Fails. NZD15.00 -// } -// -// public void testAustralianDollarsInNewZealandLocale() { -// String expected = "AU$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "NZ"), "AUD", 15.00); -// assertEquals(expected, actual); -// // Fails. AUD15.00 -// } -// -// /** -// * These tests pass in all cases I've tested. (device version and locale variations) -// */ -// -// public void testGBP() { -// String expected = "£15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "GB"), "GBP", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testNZD() { -// String expected = "$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "NZ"), "NZD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testUSD() { -// String expected = "$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "US"), "USD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testNZDInUSLocale() { -// String expected = "NZ$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "US"), "NZD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testEURInFranceLocale() { -// String expected = "15,00 €"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("fr", "FR"), "EUR", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testEURInRepublicOfIrelandLocale() { -// String expected = "€15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "IE"), "EUR", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testUSDollarsInCanadianLocale() { -// String expected = "US$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "CA"), "USD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testCanadianDollarsInUSLocale() { -// String expected = "CA$15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "US"), "CAD", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testEurosInUnitedKingdomLocale() { -// String expected = "€15.00"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("en", "GB"), "EUR", 15.00); -// assertEquals(expected, actual); -// } -// -// public void testPoundsInFranceLocale() { -// String expected = "15,00 £UK"; -// String actual = CurrencyFormatter.getFormattedCurrencyStringForLocale(new Locale("fr", "FR"), "GBP", 15.00); -// assertEquals(expected, actual); -// } -// -// -//} \ No newline at end of file diff --git a/app/src/androidTest/java/com/litewallet/security/KeyStoreTests.java b/app/src/androidTest/java/com/litewallet/security/KeyStoreTests.java deleted file mode 100644 index 5de7a564f..000000000 --- a/app/src/androidTest/java/com/litewallet/security/KeyStoreTests.java +++ /dev/null @@ -1,322 +0,0 @@ -package com.litewallet.security; - -import android.security.keystore.UserNotAuthenticatedException; -import androidx.test.rule.ActivityTestRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.breadwallet.presenter.activities.settings.TestActivity; -import com.breadwallet.tools.security.BRKeyStore; -import com.breadwallet.tools.threads.BRExecutor; - -import org.junit.Assert; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import java.io.File; - -import static com.breadwallet.tools.security.BRKeyStore.aliasObjectMap; - -@RunWith(AndroidJUnit4.class) -public class KeyStoreTests { - public static final String TAG = KeyStoreTests.class.getName(); - - @Rule - public ActivityTestRule mActivityRule = new ActivityTestRule<>(TestActivity.class); - - @Test - public void setGetPhrase() { - //set get phrase - byte[] phrase = "axis husband project any sea patch drip tip spirit tide bring belt".getBytes(); - try { - BRKeyStore.putPhrase(phrase, mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - assertFilesExist(BRKeyStore.PHRASE_ALIAS); - - byte[] freshGet = new byte[0]; - try { - freshGet = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - } - Assert.assertArrayEquals(freshGet, phrase); - - //set get Japaneese phrase - byte[] japPhrase = "こせき ぎじにってい けっこん せつぞく うんどう ふこう にっすう こせい きさま なまみ たきび はかい".getBytes(); - try { - BRKeyStore.putPhrase(japPhrase, mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - assertFilesExist(BRKeyStore.PHRASE_ALIAS); - byte[] freshJapGet = new byte[0]; - try { - freshJapGet = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - } - Assert.assertArrayEquals(freshJapGet, japPhrase); - - } - - @Test - public void setGetCanary() { - String canary = "canary"; - try { - BRKeyStore.putCanary(canary, mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - assertFilesExist(BRKeyStore.CANARY_ALIAS); - String freshGet = ""; - try { - freshGet = BRKeyStore.getCanary(mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - } - Assert.assertEquals(freshGet, canary); - } - - @Test - public void setGetMultiple() { - final String canary = "canary"; - for (int i = 0; i < 100; i++) { - BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { - @Override - public void run() { - try { - boolean b = BRKeyStore.putCanary(canary, mActivityRule.getActivity(), 0); - Assert.assertTrue(b); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - try { - String b = BRKeyStore.getCanary(mActivityRule.getActivity(), 0); - Assert.assertEquals(b, canary); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - } - }); - - } - - assertFilesExist(BRKeyStore.CANARY_ALIAS); - - - for (int i = 0; i < 100; i++) { - BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { - @Override - public void run() { - String freshGet = ""; - try { - freshGet = BRKeyStore.getCanary(mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - } - Assert.assertEquals(freshGet, canary); - } - }); - - } - try { - Thread.sleep(10000); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - } - - @Test - public void setGetMasterPubKey() { - byte[] pubKey = "26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(); - BRKeyStore.putMasterPublicKey(pubKey, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.PUB_KEY_ALIAS); - byte[] freshGet; - freshGet = BRKeyStore.getMasterPublicKey(mActivityRule.getActivity()); - Assert.assertArrayEquals(freshGet, freshGet); - } - - - @Test - public void setGetAuthKey() { - byte[] authKey = "26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(); - BRKeyStore.putAuthKey(authKey, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.AUTH_KEY_ALIAS); - byte[] freshGet; - freshGet = BRKeyStore.getAuthKey(mActivityRule.getActivity()); - Assert.assertArrayEquals(freshGet, freshGet); - } - - @Test - public void setGetWalletCreationTime() { - int time = 1479686841; - BRKeyStore.putWalletCreationTime(time, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.WALLET_CREATION_TIME_ALIAS); - int freshGet; - freshGet = BRKeyStore.getWalletCreationTime(mActivityRule.getActivity()); - Assert.assertEquals(freshGet, freshGet); - } - - @Test - public void setGetPassCode() { - String passCode = "0124"; - BRKeyStore.putPinCode(passCode, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.PASS_CODE_ALIAS); - String freshGet; - freshGet = BRKeyStore.getPinCode(mActivityRule.getActivity()); - Assert.assertEquals(freshGet, freshGet); - - passCode = "0000"; - BRKeyStore.putPinCode(passCode, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.PASS_CODE_ALIAS); - freshGet = BRKeyStore.getPinCode(mActivityRule.getActivity()); - Assert.assertEquals(freshGet, freshGet); - - passCode = "9999"; - BRKeyStore.putPinCode(passCode, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.PASS_CODE_ALIAS); - freshGet = BRKeyStore.getPinCode(mActivityRule.getActivity()); - Assert.assertEquals(freshGet, freshGet); - - passCode = "9876"; - BRKeyStore.putPinCode(passCode, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.PASS_CODE_ALIAS); - freshGet = BRKeyStore.getPinCode(mActivityRule.getActivity()); - Assert.assertEquals(freshGet, freshGet); - } - - @Test - public void setGetFailCount() { - int failCount = 2; - BRKeyStore.putFailCount(failCount, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.FAIL_COUNT_ALIAS); - int freshGet; - freshGet = BRKeyStore.getFailCount(mActivityRule.getActivity()); - Assert.assertEquals(freshGet, freshGet); - } - - @Test - public void setGetSpendLimit() { - long spendLimit = 100000; - BRKeyStore.putSpendLimit(spendLimit, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.SPEND_LIMIT_ALIAS); - long freshGet; - freshGet = BRKeyStore.getSpendLimit(mActivityRule.getActivity()); - Assert.assertEquals(freshGet, freshGet); - } - - @Test - public void setGetSFailTimeStamp() { - long failTime = 1479686841; - BRKeyStore.putFailTimeStamp(failTime, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.FAIL_TIMESTAMP_ALIAS); - long freshGet; - freshGet = BRKeyStore.getFailTimeStamp(mActivityRule.getActivity()); - Assert.assertEquals(freshGet, freshGet); - } - - @Test - public void setGetLastPasscodeUsedTime() { - long time = 1479686841; - BRKeyStore.putLastPinUsedTime(time, mActivityRule.getActivity()); - assertFilesExist(BRKeyStore.PASS_TIME_ALIAS); - long freshGet; - freshGet = BRKeyStore.getLastPinUsedTime(mActivityRule.getActivity()); - Assert.assertEquals(freshGet, freshGet); - } - - @Test - public void testClearKeyStore() { - try { - BRKeyStore.putPhrase("axis husband project any sea patch drip tip spirit tide bring belt".getBytes(), mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - try { - BRKeyStore.putCanary("canary", mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - BRKeyStore.putMasterPublicKey("26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(), mActivityRule.getActivity()); - BRKeyStore.putAuthKey("26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(), mActivityRule.getActivity()); - BRKeyStore.putToken("26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(), mActivityRule.getActivity()); - BRKeyStore.putWalletCreationTime(1479686841, mActivityRule.getActivity()); - BRKeyStore.putPinCode("0123", mActivityRule.getActivity()); - BRKeyStore.putFailCount(3, mActivityRule.getActivity()); - BRKeyStore.putFailTimeStamp(1479686841, mActivityRule.getActivity()); - BRKeyStore.putSpendLimit(10000000, mActivityRule.getActivity()); - BRKeyStore.putLastPinUsedTime(1479686841, mActivityRule.getActivity()); - BRKeyStore.putTotalLimit(1479686841, mActivityRule.getActivity()); - - for (String a : aliasObjectMap.keySet()) { - assertFilesExist(a); - } - - BRKeyStore.resetWalletKeyStore(mActivityRule.getActivity()); - - for (String a : aliasObjectMap.keySet()) { - assertFilesDontExist(a); - } - - - byte[] phrase = "some".getBytes(); - try { - phrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - } - - String canary = "some"; - - try { - canary = BRKeyStore.getCanary(mActivityRule.getActivity(), 0); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - } - - Assert.assertNull(phrase); - Assert.assertEquals(null, canary); - Assert.assertEquals(null, BRKeyStore.getMasterPublicKey(mActivityRule.getActivity())); - Assert.assertEquals(null, BRKeyStore.getAuthKey(mActivityRule.getActivity())); - Assert.assertEquals(null, BRKeyStore.getToken(mActivityRule.getActivity())); - Assert.assertEquals(0, BRKeyStore.getWalletCreationTime(mActivityRule.getActivity())); - Assert.assertEquals("", BRKeyStore.getPinCode(mActivityRule.getActivity())); - Assert.assertEquals(0, BRKeyStore.getFailCount(mActivityRule.getActivity())); - Assert.assertEquals(0, BRKeyStore.getFailTimeStamp(mActivityRule.getActivity())); - Assert.assertEquals(0, BRKeyStore.getSpendLimit(mActivityRule.getActivity())); - Assert.assertEquals(0, BRKeyStore.getLastPinUsedTime(mActivityRule.getActivity())); - - } - - @Test - public void testKeyStoreAuthTime() { - Assert.assertEquals(BRKeyStore.AUTH_DURATION_SEC, 300); - } - - @Test - public void testKeyStoreAliasMap() { - Assert.assertNotNull(aliasObjectMap); - Assert.assertEquals(aliasObjectMap.size(), 12); - } - - public void assertFilesExist(String alias) { - Assert.assertTrue(new File(BRKeyStore.getFilePath(aliasObjectMap.get(alias).datafileName, mActivityRule.getActivity())).exists()); - Assert.assertTrue(new File(BRKeyStore.getFilePath(aliasObjectMap.get(alias).ivFileName, mActivityRule.getActivity())).exists()); - } - - public void assertFilesDontExist(String alias) { - Assert.assertFalse(new File(BRKeyStore.getFilePath(aliasObjectMap.get(alias).datafileName, mActivityRule.getActivity())).exists()); - Assert.assertFalse(new File(BRKeyStore.getFilePath(aliasObjectMap.get(alias).ivFileName, mActivityRule.getActivity())).exists()); - } - -} diff --git a/app/src/androidTest/java/com/litewallet/security/NewKeyStoreTests.java b/app/src/androidTest/java/com/litewallet/security/NewKeyStoreTests.java deleted file mode 100644 index 5e1e9a64b..000000000 --- a/app/src/androidTest/java/com/litewallet/security/NewKeyStoreTests.java +++ /dev/null @@ -1,176 +0,0 @@ -package com.litewallet.security; - -import android.app.Activity; -import android.security.keystore.UserNotAuthenticatedException; -import androidx.test.rule.ActivityTestRule; -import androidx.test.ext.junit.runners.AndroidJUnit4; - -import com.breadwallet.presenter.activities.settings.TestActivity; -import com.breadwallet.tools.security.BRKeyStore; - -import org.junit.Assert; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; - -import static com.breadwallet.tools.security.BRKeyStore.PHRASE_ALIAS; -import static com.breadwallet.tools.security.BRKeyStore.aliasObjectMap; - -@RunWith(AndroidJUnit4.class) -public class NewKeyStoreTests { - public static final String TAG = NewKeyStoreTests.class.getName(); - - @Rule - public ActivityTestRule mActivityRule = new ActivityTestRule<>(TestActivity.class); - - @Before - public void setup() { - BRKeyStore.resetWalletKeyStore(mActivityRule.getActivity()); - } - - @Test - public void testBase64() { - Activity app = mActivityRule.getActivity(); - String temp = "here is some data to encrypt! @#$%^&*"; - byte[] phrase = temp.getBytes(); - BRKeyStore.storeEncryptedData(app, phrase, "phrase"); - byte[] retrievedPhrase = BRKeyStore.retrieveEncryptedData(app, "phrase"); - Assert.assertNotNull(retrievedPhrase); - Assert.assertArrayEquals("Oh no", phrase, retrievedPhrase); - String newTemp = new String(retrievedPhrase); - Assert.assertEquals(temp, newTemp); - - } - - @Test - public void setNewGetNew() { - //set get phrase - byte[] phrase = "axis husband project any sea patch drip tip spirit tide bring belt".getBytes(); - try { - boolean b = BRKeyStore.putPhrase(phrase, mActivityRule.getActivity(), 0); - Assert.assertEquals(true, b); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - try { - byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); - Assert.assertNotNull(getPhrase); - Assert.assertEquals(new String(getPhrase), new String(phrase)); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - - } - - @Test - public void setOldSetNew() { - byte[] phrase = "axis husband project any sea patch drip tip spirit tide bring belt".getBytes(); - BRKeyStore.AliasObject obj = aliasObjectMap.get(PHRASE_ALIAS); - try { - boolean b = BRKeyStore._setOldData(mActivityRule.getActivity(), phrase, obj.alias, obj.datafileName, obj.ivFileName, 0, true); - Assert.assertEquals(true, b); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - try { - byte[] getPhrase = BRKeyStore._getOldData(mActivityRule.getActivity(), obj.alias, obj.datafileName, obj.ivFileName, 0); - Assert.assertNotNull(getPhrase); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - - try { - boolean b = BRKeyStore.putPhrase(phrase, mActivityRule.getActivity(), 0); - Assert.assertEquals(true, b); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - - try { - byte[] getPhrase = BRKeyStore._getOldData(mActivityRule.getActivity(), obj.alias, obj.datafileName, obj.ivFileName, 0); - Assert.assertNull(getPhrase); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - - try { - byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); - Assert.assertNotNull(getPhrase); - Assert.assertEquals(new String(getPhrase), new String(phrase)); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - - } - - @Test - public void setOldGetNew() { - - //set get phrase - byte[] phrase = "axis husband project any sea patch drip tip spirit tide bring belt".getBytes(); - BRKeyStore.AliasObject obj = aliasObjectMap.get(PHRASE_ALIAS); - try { - boolean s = BRKeyStore._setOldData(mActivityRule.getActivity(), phrase, obj.alias, obj.datafileName, obj.ivFileName, 0, true); - Assert.assertEquals(true, s); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - try { - byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); - Assert.assertNotNull(getPhrase); - Assert.assertEquals(new String(getPhrase), new String(phrase)); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - try { - byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); - Assert.assertNotNull(getPhrase); - Assert.assertEquals(new String(getPhrase), new String(phrase)); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - try { - byte[] getPhrase = BRKeyStore._getOldData(mActivityRule.getActivity(), obj.alias, obj.datafileName, obj.ivFileName, 0); - Assert.assertNull(getPhrase); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - try { - byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); - Assert.assertNotNull(getPhrase); - Assert.assertEquals(new String(getPhrase), new String(phrase)); - } catch (UserNotAuthenticatedException e) { - e.printStackTrace(); - Assert.fail(); - } - - } - - @Test - public void allMultiThreading() { - for (int i = 0; i < 10; i++) { - setup(); - setNewGetNew(); - setup(); - setOldGetNew(); - setup(); - setOldSetNew(); - setup(); - - } - - } - -} diff --git a/app/src/androidTest/java/com/litewallet/wallet/WalletTests.java b/app/src/androidTest/java/com/litewallet/wallet/WalletTests.java deleted file mode 100644 index 3fa3b0a87..000000000 --- a/app/src/androidTest/java/com/litewallet/wallet/WalletTests.java +++ /dev/null @@ -1,115 +0,0 @@ -package com.litewallet.wallet; - -import com.breadwallet.presenter.entities.RequestObject; -import com.breadwallet.tools.security.BitcoinUrlHandler; -import com.breadwallet.tools.util.BRConstants; - - -import org.junit.Test; - -import java.math.BigDecimal; -import java.security.InvalidAlgorithmParameterException; - -import static org.junit.Assert.assertEquals; - -public class WalletTests { - public static final String TAG = WalletTests.class.getName(); - - static { - System.loadLibrary(BRConstants.NATIVE_LIB_NAME); - } - - @Test - public void paymentRequestTest() throws InvalidAlgorithmParameterException { - - RequestObject obj = BitcoinUrlHandler.getRequestFromString("n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - assertEquals("n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi", obj.address); - -// r = [BRPaymentRequest requestWithString:@"1BTCorgHwCg6u2YSAWKgS17qUad6kHmtQ"]; -// XCTAssertFalse(r.isValid); -// XCTAssertEqualObjects(@"bitcoin:1BTCorgHwCg6u2YSAWKgS17qUad6kHmtQ", r.string, -// @"[BRPaymentRequest requestWithString:]"); - - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?amount=1"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - BigDecimal bigDecimal = new BigDecimal(obj.amount); - long amountAsLong = bigDecimal.longValue(); - assertEquals(String.valueOf(amountAsLong), "100000000"); - - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?amount=0.00000001"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - bigDecimal = new BigDecimal(obj.amount); - amountAsLong = bigDecimal.longValue(); - assertEquals(String.valueOf(amountAsLong), "1"); - - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?amount=21000000"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - bigDecimal = new BigDecimal(obj.amount); - amountAsLong = bigDecimal.longValue(); - assertEquals(String.valueOf(amountAsLong), "2100000000000000"); - - // test for floating point rounding issues, these values cannot be exactly represented with an IEEE 754 double - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?amount=20999999.99999999"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - bigDecimal = new BigDecimal(obj.amount); - amountAsLong = bigDecimal.longValue(); - assertEquals(String.valueOf(amountAsLong), "2099999999999999"); - - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?amount=20999999.99999995"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - bigDecimal = new BigDecimal(obj.amount); - amountAsLong = bigDecimal.longValue(); - assertEquals(String.valueOf(amountAsLong), "2099999999999995"); - - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?amount=0.07433"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - bigDecimal = new BigDecimal(obj.amount); - amountAsLong = bigDecimal.longValue(); - assertEquals(String.valueOf(amountAsLong), "7433000"); - - // invalid amount string - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?amount=foobar"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - assertEquals(obj.amount, null); - - // test correct encoding of '&' in argument value - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?label=foo%26bar"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - assertEquals(obj.label, "foo"); - - // test handling of ' ' in label or message - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?label=foo bar&message=bar foo"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - assertEquals(obj.label, "foo bar"); - assertEquals(obj.message, "bar foo"); - - // test bip73 - obj = BitcoinUrlHandler.getRequestFromString("litecoin:n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi?r=https://foobar.com"); - assertEquals(obj.address, "n2eMqTT929pb1RDNuqEnxdaLau1rxy3efi"); - assertEquals(obj.r, "https://foobar.com"); - - obj = BitcoinUrlHandler.getRequestFromString("litecoin:?r=https://foobar.com"); - assertEquals(obj.address, null); - assertEquals(obj.r, "https://foobar.com"); - } - - @Test - public void currencyManagerTests() { - -// String amount; -// String result; - -// MainActivity app = MainActivity.app; -// if (app != null) { -// -// amount = "232432.42234"; -// result = CurrencyManager.getInstance(app).getFormattedCurrencyString("USD", amount); -// assertEquals("$232432.42234", result); -// } - } - - -} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 633d9aea5..a15c6e32b 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -14,8 +14,6 @@ - - @@ -193,11 +191,6 @@ android:exported="true" android:launchMode="singleTask" android:screenOrientation="portrait" /> - AuthManager.getInstance().authPrompt(LoginActivity.this, "", "", false, true, new BRAuthCompletion() { - @Override - public void onComplete() { - unlockWallet(); - AnalyticsManager.logCustomEvent(BRConstants._20200217_DUWB); - } - - @Override - public void onCancel() { - } - })); - } - - new Handler().postDelayed(() -> { - if (fingerPrint != null && useFingerprint) - fingerPrint.performClick(); - }, 500); - setCurrentLtcPrice(); } diff --git a/app/src/main/java/com/breadwallet/presenter/activities/intro/WriteDownActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/intro/WriteDownActivity.java index 47aa19e49..cd2421050 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/intro/WriteDownActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/intro/WriteDownActivity.java @@ -61,17 +61,7 @@ public void onClick(View v) { @Override public void onClick(View v) { if (!BRAnimator.isClickAllowed()) return; - AuthManager.getInstance().authPrompt(WriteDownActivity.this, null, getString(R.string.VerifyPin_continueBody), true, false, new BRAuthCompletion() { - @Override - public void onComplete() { - PostAuth.getInstance().onPhraseCheckAuth(WriteDownActivity.this, false); - } - - @Override - public void onCancel() { - - } - }); + /// DEV NOTES: Remove this call to auth Prompt } }); diff --git a/app/src/main/java/com/breadwallet/presenter/activities/settings/FingerprintActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/settings/FingerprintActivity.java deleted file mode 100644 index 33da87142..000000000 --- a/app/src/main/java/com/breadwallet/presenter/activities/settings/FingerprintActivity.java +++ /dev/null @@ -1,158 +0,0 @@ -package com.breadwallet.presenter.activities.settings; - -import android.app.Activity; -import android.content.Intent; -import android.graphics.Color; -import android.os.Bundle; -import android.text.SpannableString; -import android.text.Spanned; -import android.text.TextPaint; -import android.text.method.LinkMovementMethod; -import android.text.style.ClickableSpan; -import android.view.View; -import android.widget.CompoundButton; -import android.widget.ImageButton; -import android.widget.RelativeLayout; -import android.widget.TextView; -import android.widget.ToggleButton; - -import com.breadwallet.R; -import com.breadwallet.presenter.activities.util.BRActivity; -import com.breadwallet.presenter.customviews.BRDialogView; -import com.breadwallet.presenter.interfaces.BRAuthCompletion; -import com.breadwallet.tools.animation.BRDialog; -import com.breadwallet.tools.manager.BRSharedPrefs; -import com.breadwallet.tools.security.AuthManager; -import com.breadwallet.tools.security.BRKeyStore; -import com.breadwallet.tools.util.BRCurrency; -import com.breadwallet.tools.util.BRExchange; -import com.breadwallet.tools.util.Utils; - -import java.math.BigDecimal; - -import timber.log.Timber; - - -public class FingerprintActivity extends BRActivity { - - public RelativeLayout layout; - public static boolean appVisible = false; - private static FingerprintActivity app; - private TextView limitExchange; - private TextView limitInfo; - - private ToggleButton toggleButton; - - public static FingerprintActivity getApp() { - return app; - } - - @Override - protected void onSaveInstanceState(Bundle outState) { - super.onSaveInstanceState(outState); - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_fingerprint); - toggleButton = (ToggleButton) findViewById(R.id.toggleButton); - limitExchange = (TextView) findViewById(R.id.limit_exchange); - limitInfo = (TextView) findViewById(R.id.limit_info); - - ImageButton faq = (ImageButton) findViewById(R.id.faq_button); - //TODO: all views are using the layout of this button. Views should be refactored without it - // Hiding until layouts are built. - - toggleButton.setChecked(BRSharedPrefs.getUseFingerprint(this)); - - limitExchange.setText(getLimitText()); - - toggleButton.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() { - @Override - public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) { - Activity app = FingerprintActivity.this; - if (isChecked && !Utils.isFingerprintEnrolled(app)) { - Timber.d("timber: onCheckedChanged: fingerprint not setup"); - BRDialog.showCustomDialog(app, getString(R.string.TouchIdSettings_disabledWarning_title_android), getString(R.string.TouchIdSettings_disabledWarning_body_android), getString(R.string.Button_ok), null, new BRDialogView.BROnClickListener() { - @Override - public void onClick(BRDialogView brDialogView) { - brDialogView.dismissWithAnimation(); - } - }, null, null, 0); - buttonView.setChecked(false); - } else { - BRSharedPrefs.putUseFingerprint(app, isChecked); - } - } - }); - SpannableString ss = new SpannableString(getString(R.string.TouchIdSettings_customizeText_android)); - ClickableSpan clickableSpan = new ClickableSpan() { - @Override - public void onClick(View textView) { - - AuthManager.getInstance().authPrompt(FingerprintActivity.this, null, getString(R.string.VerifyPin_continueBody), true, false, new BRAuthCompletion() { - @Override - public void onComplete() { - Intent intent = new Intent(FingerprintActivity.this, SpendLimitActivity.class); - overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left); - startActivity(intent); - finish(); - } - - @Override - public void onCancel() { - - } - }); - } - - @Override - public void updateDrawState(TextPaint ds) { - super.updateDrawState(ds); - ds.setUnderlineText(false); - } - }; - //start index of the last space (beginning of the last word) - int indexOfSpace = limitInfo.getText().toString().lastIndexOf(" "); - // make the whole text clickable if failed to select the last word - ss.setSpan(clickableSpan, indexOfSpace == -1 ? 0 : indexOfSpace, limitInfo.getText().length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - - limitInfo.setText(ss); - limitInfo.setMovementMethod(LinkMovementMethod.getInstance()); - limitInfo.setHighlightColor(Color.TRANSPARENT); - - } - - private String getLimitText() { - String iso = BRSharedPrefs.getIsoSymbol(this); - //amount in satoshis - BigDecimal satoshis = new BigDecimal(BRKeyStore.getSpendLimit(this)); - //amount in BTC, mBTC or bits - BigDecimal amount = BRExchange.getAmountFromLitoshis(this, "LTC", satoshis); - //amount in user preferred ISO (e.g. USD) - BigDecimal curAmount = BRExchange.getAmountFromLitoshis(this, iso, satoshis); - //formatted string for the label - return String.format(getString(R.string.TouchIdSettings_spendingLimit), BRCurrency.getFormattedCurrencyString(this, "LTC", amount), BRCurrency.getFormattedCurrencyString(this, iso, curAmount)); - } - - @Override - protected void onResume() { - super.onResume(); - appVisible = true; - app = this; - } - - @Override - public void onBackPressed() { - super.onBackPressed(); - overridePendingTransition(R.anim.enter_from_left, R.anim.exit_to_right); - } - - @Override - public void onPause() { - super.onPause(); - appVisible = false; - } - -} diff --git a/app/src/main/java/com/breadwallet/presenter/activities/settings/SecurityCenterActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/settings/SecurityCenterActivity.java index 8481ddc4a..9d508a9e2 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/settings/SecurityCenterActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/settings/SecurityCenterActivity.java @@ -157,23 +157,6 @@ public void onClick(View v) { } })); - int resId = Utils.isFingerprintEnrolled(SecurityCenterActivity.this) - && BRSharedPrefs.getUseFingerprint(SecurityCenterActivity.this) - ? R.drawable.ic_check_mark_blue - : R.drawable.ic_check_mark_grey; - - if (Utils.isFingerprintAvailable(this)) { - itemList.add(new BRSecurityCenterItem(getString(R.string.SecurityCenter_touchIdTitle_android), getString(R.string.SecurityCenter_touchIdDescription), - resId, new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(SecurityCenterActivity.this, FingerprintActivity.class); - startActivity(intent); - overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left); - } - })); - } - boolean isPaperKeySet = BRSharedPrefs.getPhraseWroteDown(this); itemList.add(new BRSecurityCenterItem(getString(R.string.SecurityCenter_paperKeyTitle), getString(R.string.SecurityCenter_paperKeyDescription), isPaperKeySet ? R.drawable.ic_check_mark_blue : R.drawable.ic_check_mark_grey, new View.OnClickListener() { diff --git a/app/src/main/java/com/breadwallet/presenter/activities/settings/SettingsActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/settings/SettingsActivity.java index 4c8d9d223..fef1b76cd 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/settings/SettingsActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/settings/SettingsActivity.java @@ -143,23 +143,6 @@ private void populateItems() { /*Manage Title*/ items.add(new BRSettingsItem(getString(R.string.Settings_manage), "", null, true)); - /*Fingerprint Limits*/ - if (AuthManager.isFingerPrintAvailableAndSetup(this)) { - items.add(new BRSettingsItem(getString(R.string.Settings_touchIdLimit_android), "", v -> AuthManager.getInstance().authPrompt(SettingsActivity.this, null, getString(R.string.VerifyPin_continueBody), true, false, new BRAuthCompletion() { - @Override - public void onComplete() { - Intent intent = new Intent(SettingsActivity.this, SpendLimitActivity.class); - overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left); - startActivity(intent); - } - - @Override - public void onCancel() { - - } - }), false)); - } - /*Languages*/ items.add(new BRSettingsItem(getString(R.string.Settings_languages), null, v -> { ChangeLanguageBottomSheet fragment = new ChangeLanguageBottomSheet(); diff --git a/app/src/main/java/com/breadwallet/presenter/activities/util/BRActivity.java b/app/src/main/java/com/breadwallet/presenter/activities/util/BRActivity.java index fe983077a..6d933fa49 100644 --- a/app/src/main/java/com/breadwallet/presenter/activities/util/BRActivity.java +++ b/app/src/main/java/com/breadwallet/presenter/activities/util/BRActivity.java @@ -139,7 +139,7 @@ public void run() { public static void init(Activity app) { InternetManager.getInstance(); if (!(app instanceof IntroActivity || app instanceof RecoverActivity || app instanceof WriteDownActivity)) - BRApiManager.getInstance().startTimer(app); + BreadApp.module.getApiManager().startTimer(app); //show wallet locked if it is if (!ActivityUTILS.isAppSafe(app)) if (AuthManager.getInstance().isWalletDisabled(app)) diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentBuy.java b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentBuy.java index cd0fe8b44..077de939c 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentBuy.java +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentBuy.java @@ -30,6 +30,7 @@ import androidx.appcompat.widget.Toolbar; import androidx.fragment.app.Fragment; +import com.breadwallet.BreadApp; import com.breadwallet.BuildConfig; import com.breadwallet.R; import com.breadwallet.tools.animation.BRAnimator; @@ -135,12 +136,13 @@ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle sa return rootView; } - public static String url(Context context, Partner partner, String currency) { + public static String url(Context context, Partner partner, String currency) { String walletAddress = BRSharedPrefs.getReceiveAddress(context); Long timestamp = new Date().getTime(); String uuid = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID); String prefix = partner == Partner.MOONPAY ? "/moonpay/buy" : ""; - return String.format(LW_API_HOST + prefix + "?address=%s&code=%s&idate=%s&uid=%s", walletAddress, currency, timestamp, uuid); + String baseUrl = BreadApp.module.getApiManager().getBaseUrlProd(); + return String.format(baseUrl + prefix + "?address=%s&code=%s&idate=%s&uid=%s", walletAddress, currency, timestamp, uuid); } private void closePayment() { diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentFingerprint.java b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentFingerprint.java deleted file mode 100644 index 1ae433f15..000000000 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentFingerprint.java +++ /dev/null @@ -1,235 +0,0 @@ -package com.breadwallet.presenter.fragments; - -import android.animation.Animator; -import android.animation.AnimatorListenerAdapter; -import android.animation.ArgbEvaluator; -import android.animation.ValueAnimator; -import android.app.Activity; -import android.content.Context; -import android.hardware.fingerprint.FingerprintManager; -import android.os.Bundle; -import android.os.Handler; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.view.ViewTreeObserver; -import android.view.animation.AnticipateInterpolator; -import android.widget.Button; -import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.RelativeLayout; -import android.widget.TextView; - -import androidx.annotation.Nullable; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentActivity; - -import com.breadwallet.R; -import com.breadwallet.presenter.activities.BreadActivity; -import com.breadwallet.presenter.interfaces.BRAuthCompletion; -import com.breadwallet.tools.animation.BRAnimator; -import com.breadwallet.tools.animation.DecelerateOvershootInterpolator; -import com.breadwallet.tools.manager.AnalyticsManager; -import com.breadwallet.tools.security.AuthManager; -import com.breadwallet.tools.security.FingerprintUiHelper; -import com.breadwallet.tools.util.BRConstants; -import com.breadwallet.tools.util.Utils; - -import timber.log.Timber; - -/** - * A dialog which uses fingerprint APIs to authenticate the user, and falls back to password - * authentication if fingerprint is not available. - */ -public class FragmentFingerprint extends Fragment - implements FingerprintUiHelper.Callback { - public static final String TAG = FragmentFingerprint.class.getName(); - - private FingerprintManager.CryptoObject mCryptoObject; - private FingerprintUiHelper mFingerprintUiHelper; - private BRAuthCompletion completion; - private TextView title; - private TextView message; - private LinearLayout fingerPrintLayout; - private RelativeLayout fingerprintBackground; - private boolean authSucceeded; - public static final int ANIMATION_DURATION = 300; - private String customTitle; - private String customMessage; - - FingerprintUiHelper.FingerprintUiHelperBuilder mFingerprintUiHelperBuilder; - - public FragmentFingerprint() { - } - - @Override - public void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setRetainInstance(true); - } - - @Override - public View onCreateView(LayoutInflater inflater, ViewGroup container, - Bundle savedInstanceState) { - - View authModalView = inflater.inflate(R.layout.fingerprint_dialog_container, container, false); - message = (TextView) authModalView.findViewById(R.id.fingerprint_description); - title = (TextView) authModalView.findViewById(R.id.fingerprint_title); - fingerPrintLayout = (LinearLayout) authModalView.findViewById(R.id.fingerprint_layout); - fingerprintBackground = (RelativeLayout) authModalView.findViewById(R.id.fingerprint_background); - Bundle bundle = getArguments(); - String titleString = bundle.getString("title"); - String messageString = bundle.getString("message"); - if (!Utils.isNullOrEmpty(titleString)) { - customTitle = titleString; - title.setText(customTitle); - } - if (!Utils.isNullOrEmpty(messageString)) { - customMessage = messageString; - message.setText(customMessage); - } - FingerprintManager mFingerprintManager = (FingerprintManager) getActivity().getSystemService(Activity.FINGERPRINT_SERVICE); - mFingerprintUiHelperBuilder = new FingerprintUiHelper.FingerprintUiHelperBuilder(mFingerprintManager); - mFingerprintUiHelper = mFingerprintUiHelperBuilder.build((ImageView) authModalView.findViewById(R.id.fingerprint_icon), - (TextView) authModalView.findViewById(R.id.fingerprint_status), this, getContext()); - View mFingerprintContent = authModalView.findViewById(R.id.fingerprint_container); - - Button mCancelButton = (Button) authModalView.findViewById(R.id.cancel_button); - Button mSecondDialogButton = (Button) authModalView.findViewById(R.id.second_dialog_button); - mCancelButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (!BRAnimator.isClickAllowed()) return; - closeMe(); - } - }); - mCancelButton.setText(R.string.Button_cancel); - mSecondDialogButton.setText(getString(R.string.Prompts_TouchId_usePin_android)); - mFingerprintContent.setVisibility(View.VISIBLE); - mSecondDialogButton.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View view) { - if (!BRAnimator.isClickAllowed()) return; - closeMe(); - } - }); - - return authModalView; - } - - @Override - public void onViewCreated(View view, @Nullable Bundle savedInstanceState) { - super.onViewCreated(view, savedInstanceState); - - final ViewTreeObserver observer = fingerPrintLayout.getViewTreeObserver(); - observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { - @Override - public void onGlobalLayout() { - if(observer.isAlive()) { - observer.removeOnGlobalLayoutListener(this); - } - animateBackgroundDim(false); - animateSignalSlide(false); - } - }); - } - - @Override - public void onStop() { - super.onStop(); - if (!authSucceeded) - completion.onCancel(); - } - - @Override - public void onResume() { - super.onResume(); - mFingerprintUiHelper.startListening(mCryptoObject); - authSucceeded = false; - } - - @Override - public void onPause() { - super.onPause(); - mFingerprintUiHelper.stopListening(); - } - - @Override - public void onAuthenticated() { - final FragmentActivity app = getActivity(); - authSucceeded = true; - - if (completion != null) completion.onComplete(); - BRAnimator.killAllFragments(app); - BRAnimator.startBreadIfNotStarted(app); - - closeMe(); - - } - - public void setCompletion(BRAuthCompletion completion) { - this.completion = completion; - } - - @Override - public void onError() { - String authError = "auth_prompt_failed"; - Bundle params = new Bundle(); - params.putString("lwa_error_message",authError); - AnalyticsManager.logCustomEventWithParams(BRConstants._20200112_ERR, params); - } - - private void animateBackgroundDim(boolean reverse) { - int transColor = reverse ? R.color.black_trans : android.R.color.transparent; - int blackTransColor = reverse ? android.R.color.transparent : R.color.black_trans; - - ValueAnimator anim = new ValueAnimator(); - anim.setIntValues(transColor, blackTransColor); - anim.setEvaluator(new ArgbEvaluator()); - anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() { - @Override - public void onAnimationUpdate(ValueAnimator valueAnimator) { - fingerprintBackground.setBackgroundColor((Integer) valueAnimator.getAnimatedValue()); - } - }); - - anim.setDuration(ANIMATION_DURATION); - anim.start(); - } - - private void animateSignalSlide(final boolean reverse) { - float layoutTY = fingerPrintLayout.getTranslationY(); - if (!reverse) { - fingerPrintLayout.setTranslationY(layoutTY + BreadActivity.screenParametersPoint.y); - fingerPrintLayout.animate() - .translationY(layoutTY) - .setDuration(ANIMATION_DURATION + 200) - .setInterpolator(new DecelerateOvershootInterpolator(2.0f, 1f)) - .withLayer(); - } else { - fingerPrintLayout.animate() - .translationY(1500) - .setDuration(ANIMATION_DURATION) - .withLayer() - .setInterpolator(new AnticipateInterpolator(2.0f)) - .setListener(new AnimatorListenerAdapter() { - @Override - public void onAnimationEnd(Animator animation) { - super.onAnimationEnd(animation); - if (getActivity() != null) { - fingerPrintLayout.clearAnimation(); - AnalyticsManager.logCustomEvent(BRConstants._20230131_NENR); - } - } - }); - - } - - } - - private void closeMe() { - animateBackgroundDim(true); - animateSignalSlide(true); - } - -} \ No newline at end of file diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentMenu.java b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentMenu.java index 64b5bd588..eaedb8d44 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentMenu.java +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentMenu.java @@ -1,5 +1,7 @@ package com.breadwallet.presenter.fragments; +import static com.litewallet.data.source.RemoteConfigSource.KEY_FEATURE_MENU_HIDDEN_EXAMPLE; + import android.app.Activity; import android.app.Fragment; import android.content.Context; @@ -22,6 +24,7 @@ import androidx.browser.customtabs.CustomTabsIntent; import androidx.constraintlayout.widget.ConstraintLayout; +import com.breadwallet.BreadApp; import com.breadwallet.R; import com.breadwallet.presenter.activities.settings.SecurityCenterActivity; import com.breadwallet.presenter.activities.settings.SettingsActivity; @@ -30,11 +33,16 @@ import com.breadwallet.tools.animation.SlideDetector; import com.breadwallet.tools.manager.AnalyticsManager; import com.breadwallet.tools.util.BRConstants; -import com.platform.APIClient; +import com.litewallet.data.source.RemoteConfigSource; + +import org.json.JSONException; +import org.json.JSONObject; import java.util.ArrayList; import java.util.List; +import timber.log.Timber; + public class FragmentMenu extends Fragment { public TextView mTitle; @@ -84,6 +92,26 @@ public View onCreateView(LayoutInflater inflater, BRAnimator.startBreadActivity(from, true); })); + /** + * remote config example here + */ + try { + RemoteConfigSource remoteConfigSource = BreadApp.module.getRemoteConfigSource(); + String string = remoteConfigSource.getString(KEY_FEATURE_MENU_HIDDEN_EXAMPLE); + Timber.d("timber: [RemoteConfig] -> " + string); + JSONObject configValue = new JSONObject(string); + if (configValue.optBoolean("enabled", false)) { + itemList.add(new BRMenuItem(configValue.optString("title"), R.drawable.litewalletlogo, V -> { + Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse(configValue.optString("url"))); + startActivity(intent); + })); + } + } catch (Exception e) { + Timber.d("timber: [RemoteConfig] -> "+e.getLocalizedMessage()); + Timber.d(e); + } + /* Close button*/ rootView.findViewById(R.id.close_button).setOnClickListener(v -> { closeMenu(); @@ -149,7 +177,8 @@ public View getView(int position, @Nullable View convertView, @NonNull ViewGroup private void closeMenu() { BRAnimator.animateBackgroundDim(background, true); - BRAnimator.animateSignalSlide(signalLayout, true, () -> {}); + BRAnimator.animateSignalSlide(signalLayout, true, () -> { + }); if (getActivity() != null && !getActivity().isDestroyed() && !getActivity().isFinishing()) { getActivity().getFragmentManager().popBackStack(); } diff --git a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionItem.java b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionItem.java index a23d77d78..c27596698 100644 --- a/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionItem.java +++ b/app/src/main/java/com/breadwallet/presenter/fragments/FragmentTransactionItem.java @@ -1,6 +1,11 @@ package com.breadwallet.presenter.fragments; +import static androidx.core.content.ContextCompat.getSystemService; + +import android.content.ClipData; +import android.content.ClipboardManager; +import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; @@ -14,6 +19,7 @@ import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -162,6 +168,22 @@ private void fillTexts() { String toFrom = sent ? String.format(getString(R.string.TransactionDetails_to), sendAddress) : String.format(getString(R.string.TransactionDetails_from), sendAddress); mTxHash.setText(item.getTxHashHexReversed()); + mTxHash.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + String mTxtHashCopy = mTxHash.getText().toString(); + + // Get the ClipboardManager + ClipboardManager clipboard = (ClipboardManager) requireActivity().getSystemService(Context.CLIPBOARD_SERVICE); + + // Create a ClipData object with the text + ClipData clip = ClipData.newPlainText("Copied Text", mTxtHashCopy); + + // Set the ClipData to the clipboard + clipboard.setPrimaryClip(clip); + } + }); + mTxHashLink.setOnClickListener(view -> { close(); String txUrl = BRConstants.BLOCK_EXPLORER_BASE_URL + item.getTxHashHexReversed(); diff --git a/app/src/main/java/com/breadwallet/tools/manager/BRApiManager.java b/app/src/main/java/com/breadwallet/tools/manager/BRApiManager.java index 62dff5bb9..d2254a9da 100644 --- a/app/src/main/java/com/breadwallet/tools/manager/BRApiManager.java +++ b/app/src/main/java/com/breadwallet/tools/manager/BRApiManager.java @@ -1,16 +1,20 @@ package com.breadwallet.tools.manager; +import static com.breadwallet.tools.util.BRConstants.LW_API_HOST; +import static com.breadwallet.tools.util.BRConstants.LW_API_HOST_NEW; +import static com.breadwallet.tools.util.BRConstants.LW_BACKUP_API_HOST; +import static com.breadwallet.tools.util.BRConstants.LW_BACKUP_API_HOST_NEW; +import static com.breadwallet.tools.util.BRConstants._20230113_BAC; + import android.app.Activity; import android.content.Context; import android.os.Handler; -import com.breadwallet.BreadApp; import com.breadwallet.presenter.entities.CurrencyEntity; import com.breadwallet.tools.sqlite.CurrencyDataSource; import com.breadwallet.tools.threads.BRExecutor; - -import com.breadwallet.tools.util.BRConstants; import com.breadwallet.tools.util.Utils; +import com.litewallet.data.source.RemoteConfigSource; import com.platform.APIClient; import org.json.JSONArray; @@ -34,27 +38,18 @@ import okhttp3.Response; import timber.log.Timber; -import static com.breadwallet.tools.util.BRConstants.*; -import static com.breadwallet.tools.util.BRConstants.LW_API_HOST; -import static com.breadwallet.tools.util.BRConstants.LW_BACKUP_API_HOST; - public class BRApiManager { - private static BRApiManager instance; private Timer timer; private TimerTask timerTask; private Handler handler; - private BRApiManager() { - handler = new Handler(); - } + private RemoteConfigSource remoteConfigSource; - public static BRApiManager getInstance() { - if (instance == null) { - instance = new BRApiManager(); - } - return instance; + public BRApiManager(RemoteConfigSource remoteConfigSource) { + this.remoteConfigSource = remoteConfigSource; + this.handler = new Handler(); } private Set getCurrencies(Activity context) { @@ -132,14 +127,15 @@ public void stopTimerTask() { } } - public static JSONArray fetchRates(Activity activity) { - String jsonString = createGETRequestURL(activity, LW_API_HOST + "/api/v1/rates"); + public JSONArray fetchRates(Activity activity) { + String jsonString = createGETRequestURL(activity, getBaseUrlProd() + "/api/v1/rates"); JSONArray jsonArray = null; if (jsonString == null) return null; try { jsonArray = new JSONArray(jsonString); // DEV Uncomment to view values - // Timber.d("timber: JSON %s",jsonArray.toString()); +// Timber.d("timber: baseUrlProd: %s", getBaseUrlProd()); +// Timber.d("timber: JSON %s",jsonArray.toString()); } catch (JSONException ex) { Timber.e(ex); @@ -147,8 +143,9 @@ public static JSONArray fetchRates(Activity activity) { return jsonArray == null ? backupFetchRates(activity) : jsonArray; } - public static JSONArray backupFetchRates(Activity activity) { - String jsonString = createGETRequestURL(activity, LW_BACKUP_API_HOST + "/api/v1/rates"); + public JSONArray backupFetchRates(Activity activity) { + String baseUrlDev = remoteConfigSource.getBoolean(RemoteConfigSource.KEY_API_BASEURL_DEV_NEW_ENABLED) ? LW_BACKUP_API_HOST_NEW : LW_BACKUP_API_HOST; + String jsonString = createGETRequestURL(activity, baseUrlDev + "/api/v1/rates"); AnalyticsManager.logCustomEvent(_20230113_BAC); @@ -165,7 +162,7 @@ public static JSONArray backupFetchRates(Activity activity) { // createGETRequestURL // Creates the params and headers to make a GET Request - private static String createGETRequestURL(Context app, String myURL) { + private String createGETRequestURL(Context app, String myURL) { Request request = new Request.Builder() .url(myURL) .header("Content-Type", "application/json") @@ -197,4 +194,8 @@ private static String createGETRequestURL(Context app, String myURL) { } return response; } + + public String getBaseUrlProd() { + return remoteConfigSource.getBoolean(RemoteConfigSource.KEY_API_BASEURL_PROD_NEW_ENABLED) ? LW_API_HOST_NEW : LW_API_HOST; + } } diff --git a/app/src/main/java/com/breadwallet/tools/manager/PromptManager.java b/app/src/main/java/com/breadwallet/tools/manager/PromptManager.java index 763876d26..666efdd41 100644 --- a/app/src/main/java/com/breadwallet/tools/manager/PromptManager.java +++ b/app/src/main/java/com/breadwallet/tools/manager/PromptManager.java @@ -8,14 +8,11 @@ import com.breadwallet.R; import com.breadwallet.presenter.activities.UpdatePinActivity; import com.breadwallet.presenter.activities.intro.WriteDownActivity; -import com.breadwallet.presenter.activities.settings.FingerprintActivity; import com.breadwallet.presenter.activities.settings.ShareDataActivity; import com.breadwallet.tools.security.BRKeyStore; import com.breadwallet.tools.threads.BRExecutor; -import com.breadwallet.tools.util.Utils; import com.breadwallet.wallet.BRPeerManager; -import static com.breadwallet.tools.manager.PromptManager.PromptItem.FINGER_PRINT; import static com.breadwallet.tools.manager.PromptManager.PromptItem.PAPER_KEY; import static com.breadwallet.tools.manager.PromptManager.PromptItem.RECOMMEND_RESCAN; import static com.breadwallet.tools.manager.PromptManager.PromptItem.SHARE_DATA; @@ -61,8 +58,6 @@ public PromptInfo(String title, String description, View.OnClickListener listene public boolean shouldPrompt(Context app, PromptItem item) { assert (app != null); switch (item) { - case FINGER_PRINT: - return !BRSharedPrefs.getUseFingerprint(app) && Utils.isFingerprintAvailable(app); case PAPER_KEY: return !BRSharedPrefs.getPhraseWroteDown(app); case UPGRADE_PIN: @@ -80,22 +75,12 @@ public PromptItem nextPrompt(Context app) { if (shouldPrompt(app, RECOMMEND_RESCAN)) return RECOMMEND_RESCAN; if (shouldPrompt(app, UPGRADE_PIN)) return UPGRADE_PIN; if (shouldPrompt(app, PAPER_KEY)) return PAPER_KEY; - if (shouldPrompt(app, FINGER_PRINT)) return FINGER_PRINT; if (shouldPrompt(app, SHARE_DATA)) return SHARE_DATA; return null; } public PromptInfo promptInfo(final Activity app, PromptItem item) { switch (item) { - case FINGER_PRINT: - return new PromptInfo(app.getString(R.string.Prompts_TouchId_title_android), app.getString(R.string.Prompts_TouchId_body_android), new View.OnClickListener() { - @Override - public void onClick(View v) { - Intent intent = new Intent(app, FingerprintActivity.class); - app.startActivity(intent); - app.overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left); - } - }); case PAPER_KEY: return new PromptInfo(app.getString(R.string.Prompts_PaperKey_title), app.getString(R.string.Prompts_PaperKey_body), new View.OnClickListener() { @Override @@ -157,8 +142,6 @@ public void run() { */ public String getPromptName(PromptItem prompt) { switch (prompt) { - case FINGER_PRINT: - return "touchIdPrompt"; case PAPER_KEY: return "paperKeyPrompt"; case UPGRADE_PIN: diff --git a/app/src/main/java/com/breadwallet/tools/security/AuthManager.java b/app/src/main/java/com/breadwallet/tools/security/AuthManager.java index a81a58e5c..d65acc67c 100644 --- a/app/src/main/java/com/breadwallet/tools/security/AuthManager.java +++ b/app/src/main/java/com/breadwallet/tools/security/AuthManager.java @@ -1,10 +1,7 @@ package com.breadwallet.tools.security; import android.app.Activity; -import android.app.KeyguardManager; import android.content.Context; -import android.content.DialogInterface; -import android.os.Bundle; import android.os.Handler; import android.view.View; @@ -14,18 +11,10 @@ import com.breadwallet.R; import com.breadwallet.presenter.activities.DisabledActivity; import com.breadwallet.presenter.activities.util.ActivityUTILS; -import com.breadwallet.presenter.customviews.BRDialogView; -import com.breadwallet.presenter.fragments.FragmentFingerprint; -import com.breadwallet.presenter.fragments.FragmentPin; -import com.breadwallet.presenter.interfaces.BRAuthCompletion; -import com.breadwallet.tools.animation.BRDialog; import com.breadwallet.tools.manager.BRSharedPrefs; import com.breadwallet.tools.threads.BRExecutor; -import com.breadwallet.tools.util.Utils; import com.breadwallet.wallet.BRWalletManager; -import java.util.concurrent.TimeUnit; - import timber.log.Timber; public class AuthManager { @@ -175,89 +164,6 @@ public void run() { } } - public void authPrompt(final Context context, String title, String message, boolean forcePin, boolean forceFingerprint, BRAuthCompletion completion) { - if (context == null || !(context instanceof Activity)) { - Timber.i("timber: authPrompt: context is null or not Activity: %s", context); - return; - } - KeyguardManager keyguardManager = (KeyguardManager) context.getSystemService(Activity.KEYGUARD_SERVICE); - - boolean useFingerPrint = isFingerPrintAvailableAndSetup(context); - - if (BRKeyStore.getFailCount(context) != 0) { - useFingerPrint = false; - } - long passTime = BRKeyStore.getLastPinUsedTime(context); - - if (passTime + TimeUnit.MILLISECONDS.convert(2, TimeUnit.DAYS) <= System.currentTimeMillis()) { - useFingerPrint = false; - } - - if (forceFingerprint) - useFingerPrint = true; - - if (forcePin) - useFingerPrint = false; - - final FragmentActivity app = (FragmentActivity) context; - - FragmentFingerprint fingerprintFragment; - FragmentPin breadPin; - - if (keyguardManager.isKeyguardSecure()) { - if (useFingerPrint) { - fingerprintFragment = new FragmentFingerprint(); - Bundle args = new Bundle(); - args.putString("title", title); - args.putString("message", message); - fingerprintFragment.setArguments(args); - fingerprintFragment.setCompletion(completion); - androidx.fragment.app.FragmentTransaction transaction = app.getSupportFragmentManager().beginTransaction(); - transaction.setCustomAnimations(0, 0, 0, R.animator.plain_300); - transaction.add(android.R.id.content, fingerprintFragment, FragmentFingerprint.class.getName()); - transaction.addToBackStack(null); - if (!app.isDestroyed() && !app.isFinishing()) - transaction.commit(); - } else { - breadPin = new FragmentPin(); - Bundle args = new Bundle(); - args.putString("title", title); - args.putString("message", message); - breadPin.setArguments(args); - breadPin.setCompletion(completion); - FragmentTransaction transaction = app.getSupportFragmentManager().beginTransaction(); - transaction.setCustomAnimations(0, 0, 0, R.animator.plain_300); - transaction.add(android.R.id.content, breadPin, breadPin.getClass().getName()); - transaction.addToBackStack(null); - if (!app.isDestroyed() && !app.isFinishing()) { - transaction.commit(); - } - } - } else { - BRDialog.showCustomDialog(app, - "", - app.getString(R.string.Prompts_NoScreenLock_body_android), - app.getString(R.string.AccessibilityLabels_close), - null, - new BRDialogView.BROnClickListener() { - @Override - public void onClick(BRDialogView brDialogView) { - app.finish(); - } - }, null, new DialogInterface.OnDismissListener() { - @Override - public void onDismiss(DialogInterface dialog) { - app.finish(); - } - }, 0); - } - - } - - public static boolean isFingerPrintAvailableAndSetup(Context context) { - return Utils.isFingerprintAvailable(context) && Utils.isFingerprintEnrolled(context); - } - public interface OnPinSuccess { void onSuccess(); } diff --git a/app/src/main/java/com/breadwallet/tools/security/BRSender.java b/app/src/main/java/com/breadwallet/tools/security/BRSender.java index be4af0f36..d31cf2c0c 100644 --- a/app/src/main/java/com/breadwallet/tools/security/BRSender.java +++ b/app/src/main/java/com/breadwallet/tools/security/BRSender.java @@ -281,10 +281,11 @@ public void onClick(BRDialogView brDialogView) { } //successfully created the transaction, authenticate user - AuthManager.getInstance().authPrompt(ctx, "", message, forcePin, false, new BRAuthCompletion() { + BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { @Override - public void onComplete() { - BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { + public void run() { + PostAuth.getInstance().onPublishTxAuth(ctx, false); + BRExecutor.getInstance().forMainThreadTasks().execute(new Runnable() { @Override public void run() { PostAuth.getInstance().onPublishTxAuth(ctx, false); @@ -298,13 +299,10 @@ public void run() { } }); - } - @Override - public void onCancel() { - //nothing } }); + } private String createConfirmation(Context ctx, TransactionItem transactionItem) { diff --git a/app/src/main/java/com/breadwallet/tools/sqlite/CurrencyDataSource.java b/app/src/main/java/com/breadwallet/tools/sqlite/CurrencyDataSource.java index 00d63cdac..83fca8593 100644 --- a/app/src/main/java/com/breadwallet/tools/sqlite/CurrencyDataSource.java +++ b/app/src/main/java/com/breadwallet/tools/sqlite/CurrencyDataSource.java @@ -16,7 +16,6 @@ import timber.log.Timber; public class CurrencyDataSource implements BRDataSourceInterface { - private static final String TAG = CurrencyDataSource.class.getName(); private SQLiteDatabase database; private final BRSQLiteHelper dbHelper; diff --git a/app/src/main/java/com/breadwallet/tools/threads/PaymentProtocolTask.java b/app/src/main/java/com/breadwallet/tools/threads/PaymentProtocolTask.java index 8664a8ca3..fc4281dd1 100644 --- a/app/src/main/java/com/breadwallet/tools/threads/PaymentProtocolTask.java +++ b/app/src/main/java/com/breadwallet/tools/threads/PaymentProtocolTask.java @@ -320,18 +320,8 @@ public void onClick(BRDialogView brDialogView) { app.runOnUiThread(new Runnable() { @Override public void run() { - AuthManager.getInstance().authPrompt(app, "Confirmation", message, false, false, new BRAuthCompletion() { - @Override - public void onComplete() { - PostAuth.getInstance().setTmpPaymentRequest(paymentRequest); - PostAuth.getInstance().onPaymentProtocolRequest(app, false); - } - - @Override - public void onCancel() { - Timber.d("timber: onCancel: "); - } - }); + /// DEV NOTES: Remove this call to auth Prompt + } }); } diff --git a/app/src/main/java/com/breadwallet/tools/util/BRConstants.java b/app/src/main/java/com/breadwallet/tools/util/BRConstants.java index 8699519a9..239021653 100644 --- a/app/src/main/java/com/breadwallet/tools/util/BRConstants.java +++ b/app/src/main/java/com/breadwallet/tools/util/BRConstants.java @@ -103,6 +103,8 @@ private BRConstants() { */ public static final String LW_API_HOST = "https://api-prod.lite-wallet.org"; public static final String LW_BACKUP_API_HOST = "https://api-dev.lite-wallet.org"; + public static final String LW_API_HOST_NEW = "https://prod.apigsltd.net"; + public static final String LW_BACKUP_API_HOST_NEW = "https://dev.apigsltd.net"; public static final String BLOCK_EXPLORER_BASE_URL = BuildConfig.LITECOIN_TESTNET ? "https://chain.so/tx/LTCTEST/" : "https://blockchair.com/litecoin/transaction/"; diff --git a/app/src/main/java/com/breadwallet/tools/util/Utils.java b/app/src/main/java/com/breadwallet/tools/util/Utils.java index 8e77006d8..6c9960c7a 100644 --- a/app/src/main/java/com/breadwallet/tools/util/Utils.java +++ b/app/src/main/java/com/breadwallet/tools/util/Utils.java @@ -6,7 +6,6 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; -import android.hardware.fingerprint.FingerprintManager; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -168,24 +167,6 @@ public static String createBitcoinUrl(String address, long satoshiAmount, String } - public static boolean isFingerprintEnrolled(Context app) { - FingerprintManager fingerprintManager = (FingerprintManager) app.getSystemService(FINGERPRINT_SERVICE); - if (fingerprintManager == null) return false; - // Device doesn't support fingerprint authentication - return ActivityCompat.checkSelfPermission(app, Manifest.permission.USE_FINGERPRINT) == PackageManager.PERMISSION_GRANTED && fingerprintManager.isHardwareDetected() && fingerprintManager.hasEnrolledFingerprints(); - } - - public static boolean isFingerprintAvailable(Context app) { - FingerprintManager fingerprintManager = (FingerprintManager) app.getSystemService(FINGERPRINT_SERVICE); - if (fingerprintManager == null) return false; - // Device doesn't support fingerprint authentication - if (ActivityCompat.checkSelfPermission(app, Manifest.permission.USE_FINGERPRINT) != PackageManager.PERMISSION_GRANTED) { - Toast.makeText(app, "Fingerprint authentication permission not enabled", Toast.LENGTH_LONG).show(); - return false; - } - return fingerprintManager.isHardwareDetected(); - } - public static void hideKeyboard(Context app) { if (app != null) { View view = ((Activity) app).getCurrentFocus(); diff --git a/app/src/main/java/com/litewallet/data/source/RemoteConfigSource.kt b/app/src/main/java/com/litewallet/data/source/RemoteConfigSource.kt new file mode 100644 index 000000000..d1ab9a534 --- /dev/null +++ b/app/src/main/java/com/litewallet/data/source/RemoteConfigSource.kt @@ -0,0 +1,76 @@ +package com.litewallet.data.source + + +import com.breadwallet.BuildConfig +import com.breadwallet.R +import com.google.firebase.remoteconfig.ConfigUpdate +import com.google.firebase.remoteconfig.ConfigUpdateListener +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import com.google.firebase.remoteconfig.FirebaseRemoteConfigException +import com.google.firebase.remoteconfig.remoteConfigSettings +import timber.log.Timber + +interface RemoteConfigSource { + + companion object { + const val KEY_FEATURE_MENU_HIDDEN_EXAMPLE = "feature_menu_hidden_example" + const val KEY_API_BASEURL_PROD_NEW_ENABLED = "key_api_baseurl_prod_new_enabled" + const val KEY_API_BASEURL_DEV_NEW_ENABLED = "key_api_baseurl_dev_new_enabled" + const val KEY_KEYSTORE_MANAGER_ENABLED = "key_keystore_manager_enabled" + } + + fun initialize() + fun getString(key: String): String + fun getNumber(key: String): Double + fun getBoolean(key: String): Boolean + + class FirebaseImpl( + private val remoteConfig: FirebaseRemoteConfig + ) : RemoteConfigSource { + + init { + val configSettings = remoteConfigSettings { + minimumFetchIntervalInSeconds = if (BuildConfig.DEBUG) { + 0 // fetch every time in debug mode + } else { + 60 * 180 // fetch every 3 hours in production mode + } + } + remoteConfig.setConfigSettingsAsync(configSettings) + remoteConfig.setDefaultsAsync(R.xml.remote_config_defaults) + } + + override fun initialize() { + remoteConfig.fetchAndActivate() + .addOnSuccessListener { Timber.d("timber: RemoteConfig Success fetchAndActivate") } + .addOnFailureListener { + Timber.d( + it, + "timber: RemoteConfig Failure fetchAndActivate" + ) + } + remoteConfig.addOnConfigUpdateListener(object : ConfigUpdateListener { + override fun onUpdate(configUpdate: ConfigUpdate) { + Timber.d("timber: [RemoteConfig] onUpdate ${configUpdate.updatedKeys}") + } + + override fun onError(error: FirebaseRemoteConfigException) { + Timber.d("timber: [RemoteConfig] onError ${error.code} | ${error.message}") + } + + }) + } + + override fun getString(key: String): String { + return remoteConfig.getString(key) + } + + override fun getNumber(key: String): Double { + return remoteConfig.getDouble(key) + } + + override fun getBoolean(key: String): Boolean { + return remoteConfig.getBoolean(key) + } + } +} \ No newline at end of file diff --git a/app/src/main/java/com/litewallet/di/Module.kt b/app/src/main/java/com/litewallet/di/Module.kt new file mode 100644 index 000000000..c54fb74ee --- /dev/null +++ b/app/src/main/java/com/litewallet/di/Module.kt @@ -0,0 +1,19 @@ +package com.litewallet.di + +import com.breadwallet.tools.manager.BRApiManager +import com.google.firebase.ktx.Firebase +import com.google.firebase.remoteconfig.ktx.remoteConfig +import com.litewallet.data.source.RemoteConfigSource + +class Module( + val remoteConfigSource: RemoteConfigSource = provideRemoteConfigSource(), + val apiManager: BRApiManager = provideBRApiManager(remoteConfigSource) +) + +private fun provideBRApiManager(remoteConfigSource: RemoteConfigSource): BRApiManager { + return BRApiManager(remoteConfigSource) +} + +private fun provideRemoteConfigSource(): RemoteConfigSource { + return RemoteConfigSource.FirebaseImpl(Firebase.remoteConfig) +} diff --git a/app/src/main/res/layout/activity_pin.xml b/app/src/main/res/layout/activity_pin.xml index 86d9f71aa..74885c5f7 100644 --- a/app/src/main/res/layout/activity_pin.xml +++ b/app/src/main/res/layout/activity_pin.xml @@ -195,17 +195,6 @@ app:layout_constraintLeft_toLeftOf="@+id/unlocked_image" app:layout_constraintRight_toRightOf="@+id/unlocked_image" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/xml/remote_config_defaults.xml b/app/src/main/res/xml/remote_config_defaults.xml new file mode 100644 index 000000000..16e21bcaa --- /dev/null +++ b/app/src/main/res/xml/remote_config_defaults.xml @@ -0,0 +1,27 @@ + + + + key_api_baseurl_prod_new_enabled + false + + + key_api_baseurl_dev_new_enabled + false + + + key_keystore_manager_enabled + false + + + update_debounce_interval + 3.0 + + + progress_update_interval + 1.0 + + + feature_menu_hidden_example + {"enabled":true,"title":"litewallet-android repository","url":"https://github.com/litecoin-foundation/litewallet-android"} + + \ No newline at end of file diff --git a/app/src/test/java/com/litewallet/currency/CurrencyTests.kt b/app/src/test/java/com/litewallet/currency/CurrencyTests.kt new file mode 100644 index 000000000..673c9ea02 --- /dev/null +++ b/app/src/test/java/com/litewallet/currency/CurrencyTests.kt @@ -0,0 +1,100 @@ +package com.litewallet.currency + +import android.content.Context +import android.database.Cursor +import android.database.sqlite.SQLiteDatabase +import com.breadwallet.presenter.entities.CurrencyEntity +import com.breadwallet.tools.sqlite.CurrencyDataSource +import io.mockk.Runs +import io.mockk.every +import io.mockk.just +import io.mockk.mockk +import io.mockk.spyk +import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertNotNull +import org.junit.Before +import org.junit.Test + +class CurrencyTests { + + private lateinit var database: SQLiteDatabase + private var currencyDataSource: CurrencyDataSource? = null + + @Before + fun setup() { + val context: Context = mockk(relaxed = true) + currencyDataSource = spyk(CurrencyDataSource.getInstance(context)) + + database = mockk() + every { currencyDataSource?.openDatabase() } returns database + } + + @After + fun tearDown() { + currencyDataSource?.closeDatabase() + currencyDataSource = null + } + + @Test + fun `invoke CurrencyDataSource instance and getAllCurrencies, should return the correct number of currencies`() { + //The actual number of currencies is 174. The 0 is a placeholder and needs to be replaced with a db query. + mockCursorDataFromDatabase() + assertEquals(currencyDataSource?.allCurrencies?.count(), 0) + } + + @Test + fun `invoke CurrencyDataSource instance and open database, then should validate database was not null`() { + val database = currencyDataSource?.openDatabase() + assertNotNull(database) + } + + @Test + fun `invoke CurrencyDataSource instance database, should return currency by ISO`() { + + val expected = CurrencyEntity("USD", "USD", 110.345f) + mockCursorDataFromDatabase(isEmpty = false, expected = expected) + + val actual = currencyDataSource?.getCurrencyByIso("USD") + assertEquals(expected.code, actual?.code) + assertEquals(expected.name, actual?.name) + assertEquals(expected.rate, actual?.rate) + } + + @Test + fun `invoke putCurrencies, then should save the CurrencyEntity into database`() { + val fetchedCurrencies = listOf( + CurrencyEntity("USD", "USD", 110.345f), + CurrencyEntity("IDR", "IDR", 1752020.9450135066f) + ) + + every { database.beginTransaction() } just Runs + fetchedCurrencies.forEachIndexed { index, currencyEntity -> + every { database.insertWithOnConflict(any(), any(), any(), any()) } returns (index + 1L) + } + every { database.setTransactionSuccessful() } just Runs + every { database.endTransaction() } just Runs + + currencyDataSource?.putCurrencies(fetchedCurrencies) + } + + private fun mockCursorDataFromDatabase( + isEmpty: Boolean = true, + expected: CurrencyEntity? = null + ) { + val cursor = mockk() + every { database.query(any(), any(), any(), any(), any(), any(), any()) } returns cursor + if (isEmpty) { + every { cursor.moveToFirst() } returns false + every { cursor.isAfterLast } returns true + } else { + every { cursor.getString(any()) } returns expected?.code + every { cursor.getString(any()) } returns expected?.name + every { cursor.getFloat(any()) } returns expected?.rate!! + + every { cursor.moveToFirst() } returns true + every { cursor.isAfterLast } returns false + } + every { cursor.close() } just Runs + } +} diff --git a/app/src/test/java/com/litewallet/data/source/RemoteConfigSourceFirebaseImplTest.kt b/app/src/test/java/com/litewallet/data/source/RemoteConfigSourceFirebaseImplTest.kt new file mode 100644 index 000000000..826fc2729 --- /dev/null +++ b/app/src/test/java/com/litewallet/data/source/RemoteConfigSourceFirebaseImplTest.kt @@ -0,0 +1,71 @@ +package com.litewallet.data.source + +import com.google.android.gms.tasks.Tasks +import com.google.firebase.remoteconfig.FirebaseRemoteConfig +import io.mockk.every +import io.mockk.mockk +import io.mockk.verify +import org.json.JSONObject +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class RemoteConfigSourceFirebaseImplTest { + + private val firebaseRemoteConfig: FirebaseRemoteConfig = mockk(relaxed = true) + private lateinit var remoteConfigSource: RemoteConfigSource + + @Before + fun setUp() { + remoteConfigSource = RemoteConfigSource.FirebaseImpl(firebaseRemoteConfig) + } + + @Test + fun `call initialize, then should return success`() { + every { firebaseRemoteConfig.fetchAndActivate() } answers { Tasks.forResult(true) } + + every { firebaseRemoteConfig.addOnConfigUpdateListener(any()) } returns mockk(relaxed = true) + + remoteConfigSource.initialize() + + verify { firebaseRemoteConfig.fetchAndActivate() } + verify { firebaseRemoteConfig.addOnConfigUpdateListener(any()) } + } + + @Test + fun `call getString, then should return with expected config value`() { + + every { firebaseRemoteConfig.getString(any()) } returns """ + {"enabled":false,"title":"litewallet-android repository","url":"https://github.com/litecoin-foundation/litewallet-android"} + """.trimIndent() + val actual = + remoteConfigSource.getString(RemoteConfigSource.KEY_FEATURE_MENU_HIDDEN_EXAMPLE) + runCatching { JSONObject(actual) } + .onSuccess { configValue -> + assertEquals(false, configValue.optBoolean("enabled")) + assertEquals("litewallet-android repository", configValue.optString("title")) + assertEquals( + "https://github.com/litecoin-foundation/litewallet-android", + configValue.optString("url") + ) + } + } + + @Test + fun `call getBoolean, then should return with expected config value`() { + every { firebaseRemoteConfig.getBoolean(any()) } returns true + + val actual = remoteConfigSource.getBoolean("TEST_KEY") + + assertEquals(true, actual) + } + + @Test + fun `call getNumber, then should return with expected config value`() { + every { firebaseRemoteConfig.getDouble(any()) } returns 100.0 + + val actual = remoteConfigSource.getNumber("TEST_KEY") + + assertEquals(100.0, actual, .1) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/litewallet/example/ExampleTest.kt b/app/src/test/java/com/litewallet/example/ExampleTest.kt new file mode 100644 index 000000000..45a00b03f --- /dev/null +++ b/app/src/test/java/com/litewallet/example/ExampleTest.kt @@ -0,0 +1,125 @@ +package com.litewallet.example + +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import io.mockk.verifyOrder +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test + +class ExampleTest { + + @Test + fun `add invoked with valid mocked values , should return correct value as expected`() { + val dependencyOne = mockk() + val dependencyTwo = mockk() + + every { dependencyOne.value } returns 10 + every { dependencyTwo.value } returns 3 + + val system = SystemUnderTest(dependencyOne, dependencyTwo) + + val expected = 13 + val result = system.add() + assertEquals(expected, result) + verify { + dependencyOne.value + dependencyTwo.value + } + } + + @Test + fun `add invoked with valid mocked value for dependencyOne, should return correct value as expected`() { + val dependencyOne = mockk() + val dependencyTwo = mockk(relaxed = true) + + every { dependencyOne.value } returns 7 + + val system = SystemUnderTest(dependencyOne, dependencyTwo) + + val expected = 7 + val result = system.add() + assertEquals(expected, result) + verify { + dependencyOne.value + dependencyTwo.value + } + } + + @Test + fun `add invoked with valid mocked value for dependencyOne and actual value for dependencyTwo , should return correct value as expected`() { + val dependencyOne = mockk() + val dependencyTwo = spyk(DependencyTwo(3)) + + every { dependencyOne.value } returns 7 + + val system = SystemUnderTest(dependencyOne, dependencyTwo) + + val expected = 10 + val result = system.add() + assertEquals(expected, result) + verify { + dependencyOne.value + dependencyTwo.value + } + } + + + @Test + fun `calc invoked with valid mocked values, should return correct value as expected and verify by order`() { + val dependencyOne = mockk() + val dependencyTwo = mockk() + + every { dependencyOne.value } returns 7 + every { dependencyTwo.value } returns 4 + + val system = SystemUnderTest(dependencyOne, dependencyTwo) + + val expected = 17 + val result = system.calc() + assertEquals(expected, result) + verifyOrder { + system.multiply() + system.add() + } + } + + @Test + fun `basic example coroutines`() = runBlocking { + val dependencyOne = mockk() + val dependencyTwo = mockk() + + every { dependencyOne.value } returns 9 + every { dependencyTwo.value } returns 11 + + val system = SystemUnderTest(dependencyOne, dependencyTwo) + + val expected = listOf(9, 11) + val result = system.fetch() + assertEquals(expected, result) + coVerify { system.fetch() } + } + +} + +class DependencyOne(val value: Int) +class DependencyTwo(val value: Int) + +class SystemUnderTest( + private val dependencyOne: DependencyOne, + private val dependencyTwo: DependencyTwo +) { + fun add(): Int = dependencyOne.value + dependencyTwo.value + fun multiply(): Int = dependencyOne.value * dependencyTwo.value + + fun calc(): Int = multiply() - add() + + suspend fun fetch(): List { + delay(3000) + return listOf(dependencyOne.value, dependencyTwo.value) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/litewallet/tools/database/DatabaseTests.kt b/app/src/test/java/com/litewallet/tools/database/DatabaseTests.kt new file mode 100644 index 000000000..4c7a101aa --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/database/DatabaseTests.kt @@ -0,0 +1,85 @@ +package com.litewallet.tools.database + +class DatabaseTests { +} + +//package com.litewallet.analytics; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +//import android.util.Log; +//import androidx.test.ext.junit.rules.ActivityScenarioRule; +// +//import com.breadwallet.presenter.activities.intro.IntroActivity; +//import com.breadwallet.tools.util.BRConstants; +// +//import org.junit.After; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Rule; +//import org.junit.Test; +//import org.junit.runner.RunWith; +// +//import java.net.URI; +// +//@Deprecated +//@RunWith(AndroidJUnit4.class) +// public class ConstantsTests { +// public static final String TAG = ConstantsTests.class.getName(); +// @Rule +// public ActivityScenarioRule mActivityRule = new ActivityScenarioRule<>(IntroActivity.class); +// @Before +// public void setUp() { +// Log.e(TAG, "setUp: "); +// } +// +// @After +// public void tearDown() { +// } +// @Test +// public void testLitecoinSymbolConstants() { +// Assert.assertSame(BRConstants.litecoinLowercase,"ł"); +// Assert.assertSame(BRConstants.litecoinUppercase,"Ł"); +// } +// @Test +// public void testAppExternalURLConstants() { +// Assert.assertSame(BRConstants.TWITTER_LINK,"https://twitter.com/Litewallet_App"); +// Assert.assertSame(BRConstants.INSTAGRAM_LINK,"https://www.instagram.com/litewallet.app"); +// Assert.assertSame(BRConstants.WEB_LINK,"https://litewallet.io"); +// Assert.assertSame(BRConstants.TOS_LINK,"https://litewallet.io/privacy"); +// Assert.assertSame(BRConstants.CUSTOMER_SUPPORT_LINK,"https://support.litewallet.io/hc/en-us/requests/new"); +// Assert.assertSame(BRConstants.BITREFILL_AFFILIATE_LINK,"https://www.bitrefill.com/"); +// } +// @Test +// public void testFirebaseAnalyticsConstants() { +// Assert.assertSame(BRConstants._20191105_AL,"app_launched"); +// Assert.assertSame(BRConstants._20191105_VSC,"visit_send_controller"); +// Assert.assertSame(BRConstants._20202116_VRC,"visit_receive_controller"); +// Assert.assertSame(BRConstants._20191105_DSL,"did_send_ltc"); +// Assert.assertSame(BRConstants._20191105_DTBT,"did_tap_buy_tab"); +// Assert.assertSame(BRConstants._20200111_RNI,"rate_not_initialized"); +// Assert.assertSame(BRConstants._20200111_FNI,"feeperkb_not_initialized"); +// Assert.assertSame(BRConstants._20200111_TNI,"transaction_not_initialized"); +// Assert.assertSame(BRConstants._20200111_WNI,"wallet_not_initialized"); +// Assert.assertSame(BRConstants._20200111_PNI,"phrase_not_initialized"); +// Assert.assertSame(BRConstants._20200111_UTST,"unable_to_sign_transaction"); +// Assert.assertSame(BRConstants._20200112_ERR,"error"); +// Assert.assertSame(BRConstants._20200112_DSR,"did_start_resync"); +// Assert.assertSame(BRConstants._20200125_DSRR,"did_show_review_request"); +// Assert.assertSame(BRConstants._20201118_DTGS,"did_tap_get_support"); +// Assert.assertSame(BRConstants._20200217_DUWP,"did_unlock_with_pin"); +// Assert.assertSame(BRConstants._20200217_DUWB,"did_unlock_with_biometrics"); +// Assert.assertSame(BRConstants._20200301_DUDFPK,"did_use_default_fee_per_kb"); +// Assert.assertSame(BRConstants._20201121_SIL,"started_IFPS_lookup"); +// Assert.assertSame(BRConstants._20201121_DRIA,"did_resolve_IPFS_address"); +// Assert.assertSame(BRConstants._20201121_FRIA,"failed_resolve_IPFS_address"); +// Assert.assertSame(BRConstants._20200207_DTHB,"did_tap_header_balance"); +// Assert.assertSame(BRConstants._20210427_HCIEEH,"heartbeat_check_if_event_even_happens"); +// Assert.assertSame(BRConstants._20220822_UTOU,"user_tapped_on_ud"); +// Assert.assertSame(BRConstants._20230131_NENR,"no_error_nominal_response"); +// Assert.assertSame(BRConstants._20230407_DCS,"did_complete_sync"); +// Assert.assertSame(BRConstants._20240123_RAGI,"registered_android_general_interest"); +// Assert.assertSame(BRConstants._20231225_UAP,"user_accepted_push"); +// Assert.assertSame(BRConstants._20240101_US,"user_signup"); +// Assert.assertSame(BRConstants._20241006_DRR,"did_request_rating"); +// Assert.assertSame(BRConstants._20241006_UCR,"user_completed_rating"); +// } +//} diff --git a/app/src/test/java/com/litewallet/tools/database/TransactionDataSourceTest.kt b/app/src/test/java/com/litewallet/tools/database/TransactionDataSourceTest.kt new file mode 100644 index 000000000..4e3ced570 --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/database/TransactionDataSourceTest.kt @@ -0,0 +1,3 @@ +package com.litewallet.tools.database + +class TransactionDataSourceTest {} diff --git a/app/src/test/java/com/litewallet/tools/manager/BRApiManagerTest.kt b/app/src/test/java/com/litewallet/tools/manager/BRApiManagerTest.kt new file mode 100644 index 000000000..1cc2b63fb --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/manager/BRApiManagerTest.kt @@ -0,0 +1,122 @@ +package com.litewallet.tools.manager + +import android.app.Activity +import android.content.Context +import com.breadwallet.presenter.activities.util.ActivityUTILS +import com.breadwallet.tools.manager.BRApiManager +import com.breadwallet.tools.util.BRConstants +import com.breadwallet.tools.util.Utils +import com.litewallet.data.source.RemoteConfigSource +import com.platform.APIClient +import io.mockk.every +import io.mockk.mockk +import io.mockk.mockkObject +import io.mockk.mockkStatic +import io.mockk.spyk +import io.mockk.verifyAll +import okhttp3.Protocol +import okhttp3.Request +import okhttp3.Response +import okhttp3.ResponseBody.Companion.toResponseBody +import org.junit.Assert.assertEquals +import org.junit.Before +import org.junit.Test + +class BRApiManagerTest { + + private val remoteConfigSource: RemoteConfigSource = mockk() + private lateinit var apiManager: BRApiManager + + @Before + fun setUp() { + apiManager = spyk(BRApiManager(remoteConfigSource), recordPrivateCalls = true) + } + + @Test + fun `invoke fetchRates, should return success with parsed JSONArray`() { + val activity: Activity = mockk(relaxed = true) + val responseString = """ + [ + { + "code": "AED", + "n": 416.81128312406213, + "price": "AED416.811283124062145364", + "name": "United Arab Emirates Dirham" + }, + { + "code": "AFN", + "n": 7841.21263788453, + "price": "Af7841.212637884529266812", + "name": "Afghan Afghani" + }, + { + "code": "ALL", + "n": 10592.359754930994, + "price": "ALL10592.359754930995026136", + "name": "Albanian Lek" + } + ] + """.trimIndent() + mockkStatic(ActivityUTILS::class) + mockkObject(APIClient.getInstance(activity)) + every { + remoteConfigSource.getBoolean(RemoteConfigSource.KEY_API_BASEURL_PROD_NEW_ENABLED) + } returns false + every { + apiManager invoke "createGETRequestURL" withArguments (listOf( + activity as Context, + BRConstants.LW_API_HOST + )) + } returns responseString + every { ActivityUTILS.isMainThread() } returns false + every { APIClient.getInstance(activity).getCurrentLocale(activity) } returns "en" + + val request = Request.Builder() + .url(BRConstants.LW_API_HOST) + .header("Content-Type", "application/json") + .header("Accept", "application/json") + .header("User-agent", Utils.getAgentString(activity, "android/HttpURLConnection")) + .get().build() + every { + APIClient.getInstance(activity).sendRequest(request, false, 0) + } returns Response.Builder() + .request(request) + .protocol(Protocol.HTTP_1_1) + .code(200) + .message("OK") + .body(responseString.toResponseBody()) + .build() + + val result = apiManager.fetchRates(activity) + + verifyAll { + remoteConfigSource.getBoolean(RemoteConfigSource.KEY_API_BASEURL_PROD_NEW_ENABLED) + + ActivityUTILS.isMainThread() + APIClient.getInstance(activity).getCurrentLocale(activity) + APIClient.getInstance(activity).sendRequest(any(), any(), any()) + } + + val jsonAED = result.getJSONObject(0) + assertEquals("AED", jsonAED.optString("code")) + assertEquals("United Arab Emirates Dirham", jsonAED.optString("name")) + } + + @Test + fun `invoke getBaseUrlProd with KEY_API_BASEURL_PROD_NEW_ENABLED true, then should return new baseUrlProd`() { + every { remoteConfigSource.getBoolean(RemoteConfigSource.KEY_API_BASEURL_PROD_NEW_ENABLED) } returns true + + val actual = apiManager.baseUrlProd + + assertEquals(BRConstants.LW_API_HOST_NEW, actual) + } + + @Test + fun `invoke getBaseUrlProd with KEY_API_BASEURL_PROD_NEW_ENABLED false, then should return old baseUrlProd`() { + every { remoteConfigSource.getBoolean(RemoteConfigSource.KEY_API_BASEURL_PROD_NEW_ENABLED) } returns false + + val actual = apiManager.baseUrlProd + + assertEquals(BRConstants.LW_API_HOST, actual) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/litewallet/tools/security/BRKeyStoreTest.kt b/app/src/test/java/com/litewallet/tools/security/BRKeyStoreTest.kt new file mode 100644 index 000000000..8e84130ec --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/security/BRKeyStoreTest.kt @@ -0,0 +1,1059 @@ +package com.litewallet.tools.security +// +//import com.breadwallet.presenter.activities.intro.WriteDownActivity +//import com.breadwallet.tools.threads.BRExecutor +//import com.breadwallet.tools.util.BRConstants +//import com.litewallet.example.DependencyOne +//import com.litewallet.example.DependencyTwo +//import com.litewallet.example.SystemUnderTest +//import com.platform.entities.TxMetaData +//import com.platform.interfaces.KVStoreAdaptor +//import com.platform.kvstore.CompletionObject +//import com.platform.kvstore.ReplicatedKVStore +//import com.platform.sqlite.KVItem +//import com.platform.sqlite.PlatformSqliteHelper +//import com.platform.tools.KVStoreManager +//import org.junit.After +//import org.junit.Assert +//import org.junit.Before +//import org.junit.ClassRule +//import org.junit.Test +//import org.junit.runner.RunWith +//import java.util.Arrays +//import java.util.concurrent.atomic.AtomicInteger +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import io.mockk.verifyOrder +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test +// +class BRKeyStoreTest { + +} + +// from com.litewallet.security; +//package com.litewallet.security; +// +//import android.security.keystore.UserNotAuthenticatedException; +//import androidx.test.rule.ActivityTestRule; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +// +//import com.breadwallet.presenter.activities.settings.TestActivity; +//import com.breadwallet.tools.security.BRKeyStore; +//import com.breadwallet.tools.threads.BRExecutor; +// +//import org.junit.Assert; +//import org.junit.Rule; +//import org.junit.Test; +//import org.junit.runner.RunWith; +// +//import java.io.File; +// +//import static com.breadwallet.tools.security.BRKeyStore.aliasObjectMap; +// +//@RunWith(AndroidJUnit4.class) +// public class KeyStoreTests { +// public static final String TAG = KeyStoreTests.class.getName(); +// +// @Rule +// public ActivityTestRule mActivityRule = new ActivityTestRule<>(TestActivity.class); +// +// @Test +// public void setGetPhrase() { +// //set get phrase +// byte[] phrase = "axis husband project any sea patch drip tip spirit tide bring belt".getBytes(); +// try { +// BRKeyStore.putPhrase(phrase, mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// assertFilesExist(BRKeyStore.PHRASE_ALIAS); +// +// byte[] freshGet = new byte[0]; +// try { +// freshGet = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// } +// Assert.assertArrayEquals(freshGet, phrase); +// +// //set get Japaneese phrase +// byte[] japPhrase = "こせき ぎじにってい けっこん せつぞく うんどう ふこう にっすう こせい きさま なまみ たきび はかい".getBytes(); +// try { +// BRKeyStore.putPhrase(japPhrase, mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// assertFilesExist(BRKeyStore.PHRASE_ALIAS); +// byte[] freshJapGet = new byte[0]; +// try { +// freshJapGet = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// } +// Assert.assertArrayEquals(freshJapGet, japPhrase); +// +// } +// +// @Test +// public void setGetCanary() { +// String canary = "canary"; +// try { +// BRKeyStore.putCanary(canary, mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// assertFilesExist(BRKeyStore.CANARY_ALIAS); +// String freshGet = ""; +// try { +// freshGet = BRKeyStore.getCanary(mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// } +// Assert.assertEquals(freshGet, canary); +// } +// +// @Test +// public void setGetMultiple() { +// final String canary = "canary"; +// for (int i = 0; i < 100; i++) { +// BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { +// @Override +// public void run() { +// try { +// boolean b = BRKeyStore.putCanary(canary, mActivityRule.getActivity(), 0); +// Assert.assertTrue(b); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// try { +// String b = BRKeyStore.getCanary(mActivityRule.getActivity(), 0); +// Assert.assertEquals(b, canary); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// } +// }); +// +// } +// +// assertFilesExist(BRKeyStore.CANARY_ALIAS); +// +// +// for (int i = 0; i < 100; i++) { +// BRExecutor.getInstance().forLightWeightBackgroundTasks().execute(new Runnable() { +// @Override +// public void run() { +// String freshGet = ""; +// try { +// freshGet = BRKeyStore.getCanary(mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// } +// Assert.assertEquals(freshGet, canary); +// } +// }); +// +// } +// try { +// Thread.sleep(10000); +// } catch (InterruptedException e) { +// e.printStackTrace(); +// } +// +// } +// +// @Test +// public void setGetMasterPubKey() { +// byte[] pubKey = "26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(); +// BRKeyStore.putMasterPublicKey(pubKey, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.PUB_KEY_ALIAS); +// byte[] freshGet; +// freshGet = BRKeyStore.getMasterPublicKey(mActivityRule.getActivity()); +// Assert.assertArrayEquals(freshGet, freshGet); +// } +// +// +// @Test +// public void setGetAuthKey() { +// byte[] authKey = "26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(); +// BRKeyStore.putAuthKey(authKey, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.AUTH_KEY_ALIAS); +// byte[] freshGet; +// freshGet = BRKeyStore.getAuthKey(mActivityRule.getActivity()); +// Assert.assertArrayEquals(freshGet, freshGet); +// } +// +// @Test +// public void setGetWalletCreationTime() { +// int time = 1479686841; +// BRKeyStore.putWalletCreationTime(time, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.WALLET_CREATION_TIME_ALIAS); +// int freshGet; +// freshGet = BRKeyStore.getWalletCreationTime(mActivityRule.getActivity()); +// Assert.assertEquals(freshGet, freshGet); +// } +// +// @Test +// public void setGetPassCode() { +// String passCode = "0124"; +// BRKeyStore.putPinCode(passCode, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.PASS_CODE_ALIAS); +// String freshGet; +// freshGet = BRKeyStore.getPinCode(mActivityRule.getActivity()); +// Assert.assertEquals(freshGet, freshGet); +// +// passCode = "0000"; +// BRKeyStore.putPinCode(passCode, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.PASS_CODE_ALIAS); +// freshGet = BRKeyStore.getPinCode(mActivityRule.getActivity()); +// Assert.assertEquals(freshGet, freshGet); +// +// passCode = "9999"; +// BRKeyStore.putPinCode(passCode, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.PASS_CODE_ALIAS); +// freshGet = BRKeyStore.getPinCode(mActivityRule.getActivity()); +// Assert.assertEquals(freshGet, freshGet); +// +// passCode = "9876"; +// BRKeyStore.putPinCode(passCode, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.PASS_CODE_ALIAS); +// freshGet = BRKeyStore.getPinCode(mActivityRule.getActivity()); +// Assert.assertEquals(freshGet, freshGet); +// } +// +// @Test +// public void setGetFailCount() { +// int failCount = 2; +// BRKeyStore.putFailCount(failCount, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.FAIL_COUNT_ALIAS); +// int freshGet; +// freshGet = BRKeyStore.getFailCount(mActivityRule.getActivity()); +// Assert.assertEquals(freshGet, freshGet); +// } +// +// @Test +// public void setGetSpendLimit() { +// long spendLimit = 100000; +// BRKeyStore.putSpendLimit(spendLimit, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.SPEND_LIMIT_ALIAS); +// long freshGet; +// freshGet = BRKeyStore.getSpendLimit(mActivityRule.getActivity()); +// Assert.assertEquals(freshGet, freshGet); +// } +// +// @Test +// public void setGetSFailTimeStamp() { +// long failTime = 1479686841; +// BRKeyStore.putFailTimeStamp(failTime, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.FAIL_TIMESTAMP_ALIAS); +// long freshGet; +// freshGet = BRKeyStore.getFailTimeStamp(mActivityRule.getActivity()); +// Assert.assertEquals(freshGet, freshGet); +// } +// +// @Test +// public void setGetLastPasscodeUsedTime() { +// long time = 1479686841; +// BRKeyStore.putLastPinUsedTime(time, mActivityRule.getActivity()); +// assertFilesExist(BRKeyStore.PASS_TIME_ALIAS); +// long freshGet; +// freshGet = BRKeyStore.getLastPinUsedTime(mActivityRule.getActivity()); +// Assert.assertEquals(freshGet, freshGet); +// } +// +// @Test +// public void testClearKeyStore() { +// try { +// BRKeyStore.putPhrase("axis husband project any sea patch drip tip spirit tide bring belt".getBytes(), mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// try { +// BRKeyStore.putCanary("canary", mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// BRKeyStore.putMasterPublicKey("26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(), mActivityRule.getActivity()); +// BRKeyStore.putAuthKey("26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(), mActivityRule.getActivity()); +// BRKeyStore.putToken("26wZYDdvpmCrYZeUcxgqd1KquN4o6wXwLomBW5SjnwUqG".getBytes(), mActivityRule.getActivity()); +// BRKeyStore.putWalletCreationTime(1479686841, mActivityRule.getActivity()); +// BRKeyStore.putPinCode("0123", mActivityRule.getActivity()); +// BRKeyStore.putFailCount(3, mActivityRule.getActivity()); +// BRKeyStore.putFailTimeStamp(1479686841, mActivityRule.getActivity()); +// BRKeyStore.putSpendLimit(10000000, mActivityRule.getActivity()); +// BRKeyStore.putLastPinUsedTime(1479686841, mActivityRule.getActivity()); +// BRKeyStore.putTotalLimit(1479686841, mActivityRule.getActivity()); +// +// for (String a : aliasObjectMap.keySet()) { +// assertFilesExist(a); +// } +// +// BRKeyStore.resetWalletKeyStore(mActivityRule.getActivity()); +// +// for (String a : aliasObjectMap.keySet()) { +// assertFilesDontExist(a); +// } +// +// +// byte[] phrase = "some".getBytes(); +// try { +// phrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// } +// +// String canary = "some"; +// +// try { +// canary = BRKeyStore.getCanary(mActivityRule.getActivity(), 0); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// } +// +// Assert.assertNull(phrase); +// Assert.assertEquals(null, canary); +// Assert.assertEquals(null, BRKeyStore.getMasterPublicKey(mActivityRule.getActivity())); +// Assert.assertEquals(null, BRKeyStore.getAuthKey(mActivityRule.getActivity())); +// Assert.assertEquals(null, BRKeyStore.getToken(mActivityRule.getActivity())); +// Assert.assertEquals(0, BRKeyStore.getWalletCreationTime(mActivityRule.getActivity())); +// Assert.assertEquals("", BRKeyStore.getPinCode(mActivityRule.getActivity())); +// Assert.assertEquals(0, BRKeyStore.getFailCount(mActivityRule.getActivity())); +// Assert.assertEquals(0, BRKeyStore.getFailTimeStamp(mActivityRule.getActivity())); +// Assert.assertEquals(0, BRKeyStore.getSpendLimit(mActivityRule.getActivity())); +// Assert.assertEquals(0, BRKeyStore.getLastPinUsedTime(mActivityRule.getActivity())); +// +// } +// +// @Test +// public void testKeyStoreAuthTime() { +// Assert.assertEquals(BRKeyStore.AUTH_DURATION_SEC, 300); +// } +// +// @Test +// public void testKeyStoreAliasMap() { +// Assert.assertNotNull(aliasObjectMap); +// Assert.assertEquals(aliasObjectMap.size(), 12); +// } +// +// public void assertFilesExist(String alias) { +// Assert.assertTrue(new File(BRKeyStore.getFilePath(aliasObjectMap.get(alias).datafileName, mActivityRule.getActivity())).exists()); +// Assert.assertTrue(new File(BRKeyStore.getFilePath(aliasObjectMap.get(alias).ivFileName, mActivityRule.getActivity())).exists()); +// } +// +// public void assertFilesDontExist(String alias) { +// Assert.assertFalse(new File(BRKeyStore.getFilePath(aliasObjectMap.get(alias).datafileName, mActivityRule.getActivity())).exists()); +// Assert.assertFalse(new File(BRKeyStore.getFilePath(aliasObjectMap.get(alias).ivFileName, mActivityRule.getActivity())).exists()); +// } +// +//} + + +//TODO: Transcode and add Kotlin tests. Copied from legacy Java BRKeyStoreTest +////@RunWith(androidx.test.ext.junit.runners.AndroidJUnit4::class) +////@LargeTest +//fun `add invoked with valid mocked values, should return correct value as expected`() { +// val dependencyOne = mockk() +// val dependencyTwo = mockk() +// +// every { dependencyOne.value } returns 10 +// every { dependencyTwo.value } returns 3 +// +// val system = SystemUnderTest(dependencyOne, dependencyTwo) +// +// val expected = 13 +// val result = system.add() +// assertEquals(expected, result) +// verify { +// dependencyOne.value +// dependencyTwo.value +// } +//} +// +// +// class MockUpAdapter : KVStoreAdaptor { +// var remoteKVs: MutableMap = HashMap() +// +// override fun ver(key: String): CompletionObject { +// val result = remoteKVs[key] +// return if (result == null) CompletionObject(CompletionObject.RemoteKVStoreError.notFound) else CompletionObject( +// result.version, +// result.time, +// if (result.deleted == 0) null else CompletionObject.RemoteKVStoreError.tombstone +// ) +// } +// +// override fun put(key: String, value: ByteArray, version: Long): CompletionObject { +// val result = remoteKVs[key] +// if (result == null) { +// if (version != 1L) return CompletionObject(CompletionObject.RemoteKVStoreError.notFound) +// val newObj = KVItem(1, -1, key, value, System.currentTimeMillis(), 0) +// remoteKVs[key] = newObj +// return CompletionObject(1, newObj.time, null) +// } +// if (version != result.version) return CompletionObject(CompletionObject.RemoteKVStoreError.conflict) +// val newObj = KVItem(result.version + 1, -1, key, value, System.currentTimeMillis(), 0) +// remoteKVs[newObj.key] = newObj +// return CompletionObject(newObj.version, newObj.time, null) +// } +// +// override fun del(key: String, version: Long): CompletionObject { +// val result = remoteKVs[key] +// ?: return CompletionObject(CompletionObject.RemoteKVStoreError.notFound) +// if (result.version != version) return CompletionObject(CompletionObject.RemoteKVStoreError.conflict) +// val newObj = KVItem(result.version + 1, -1, result.key, result.value, result.time, 1) +// remoteKVs[newObj.key] = newObj +// return CompletionObject(newObj.version, newObj.time, null) +// } +// +// override fun get(key: String, version: Long): CompletionObject { +// val result = remoteKVs[key] +// ?: return CompletionObject(CompletionObject.RemoteKVStoreError.notFound) +// if (version != result.version) return CompletionObject( +// 0, +// System.currentTimeMillis(), +// CompletionObject.RemoteKVStoreError.conflict +// ) +// return CompletionObject( +// result.version, +// result.time, +// result.value, +// if (result.deleted == 0) null else CompletionObject.RemoteKVStoreError.tombstone +// ) +// } +// +// override fun keys(): CompletionObject { +// val result: MutableList = ArrayList() +// for (kv in remoteKVs.values) { +// if (kv.deleted != 0) kv.err = CompletionObject.RemoteKVStoreError.tombstone +// result.add(kv) +// } +// return CompletionObject(result) +// } +// +// fun putKv(kv: KVItem) { +// remoteKVs[kv.key] = kv +// } +// } +// +// @Before +// fun setUp() { +// BRConstants.PLATFORM_ON = false +// (remote as MockUpAdapter).remoteKVs.clear() +// remote.putKv( +// KVItem( +// 1, +// 1, +// "hello", +// ReplicatedKVStore.encrypt("hello".toByteArray(), mActivityRule.getActivity()), +// System.currentTimeMillis(), +// 0 +// ) +// ) +// remote.putKv( +// KVItem( +// 1, +// 1, +// "removed", +// ReplicatedKVStore.encrypt("removed".toByteArray(), mActivityRule.getActivity()), +// System.currentTimeMillis(), +// 1 +// ) +// ) +// for (i in 0..19) { +// remote.putKv( +// KVItem( +// 1, +// 1, +// "testkey$i", +// ReplicatedKVStore.encrypt( +// ("testkey$i").toByteArray(), +// mActivityRule.getActivity() +// ), +// System.currentTimeMillis(), +// 0 +// ) +// ) +// } +// +// store = ReplicatedKVStore.getInstance(mActivityRule.getActivity(), remote) +// store.deleteAllKVs() +// Assert.assertEquals(22, remote.remoteKVs.size.toLong()) +// } +// +// @After +// fun tearDown() { +// store!!.deleteAllKVs() +// mActivityRule.getActivity().deleteDatabase(PlatformSqliteHelper.DATABASE_NAME) +// (remote as MockUpAdapter).remoteKVs.clear() +// } +// +// fun assertDatabasesAreSynced() { +// val remoteKV: MutableMap = LinkedHashMap() +// val kvs: List = ArrayList((remote as MockUpAdapter).remoteKVs.values) +// for (kv in kvs) { +// if (kv.deleted == 0) remoteKV[kv.key] = kv.value +// } +// +// val allLocalKeys = store!!.rawKVs +// val localKV: MutableMap = LinkedHashMap() +// +// for (kv in allLocalKeys) { +// if (kv.deleted == 0) { +// val `object` = store!![kv.key, kv.version] +// +// // KVItem tmpKv = object.kv; +// localKV[kv.key] = `object`.kv.value +// } +// } +// +// Assert.assertEquals(remoteKV.size.toLong(), localKV.size.toLong()) +// +// var it: Iterator<*> = remoteKV.entries.iterator() +// while (it.hasNext()) { +// val pair = it.next() as Map.Entry<*, *> +// val `val` = +// ReplicatedKVStore.decrypt(pair.value as ByteArray?, mActivityRule.getActivity()) +// val valToAssert = localKV[pair.key as String] +// val valStr = String(`val`!!) +// val valToAssertStr = String(valToAssert!!) +// Assert.assertArrayEquals(`val`, valToAssert) +// } +// +// it = localKV.entries.iterator() +// while (it.hasNext()) { +// val pair = it.next() as Map.Entry<*, *> +// val `val` = pair.value as ByteArray +// val valToAssert = +// ReplicatedKVStore.decrypt(remoteKV[pair.key as String], mActivityRule.getActivity()) +// Assert.assertArrayEquals(`val`, valToAssert) +// } +// } +// +// +// @Test +// fun testSetLocal() { +// val obj = store!!.set(0, 1, "Key1", "Key1".toByteArray(), System.currentTimeMillis(), 0) +// Assert.assertNull(obj.err) +// store!![0, 1, "Key1", "Key1".toByteArray(), System.currentTimeMillis()] = +// 0 +// store!!.set(KVItem(0, 1, "Key2", "Key2".toByteArray(), System.currentTimeMillis(), 2)) +// store!!.set( +// arrayOf( +// KVItem(0, 4, "Key3", "Key3".toByteArray(), System.currentTimeMillis(), 2), +// KVItem(0, 2, "Key4", "Key4".toByteArray(), System.currentTimeMillis(), 0) +// ) +// ) +// store!!.set( +// Arrays.asList( +// *arrayOf( +// KVItem(0, 4, "Key5", "Key5".toByteArray(), System.currentTimeMillis(), 1), +// KVItem(0, 5, "Key6", "Key6".toByteArray(), System.currentTimeMillis(), 5) +// ) +// ) +// ) +// Assert.assertEquals(6, store!!.rawKVs.size.toLong()) +// } +// +// @Test +// fun testDeleteAll() { +// val obj = store!!.set(0, 1, "Key1", "Key1".toByteArray(), System.currentTimeMillis(), 0) +// store!![0, 1, "Key1", "Key1".toByteArray(), System.currentTimeMillis()] = +// 0 +// store!!.set(KVItem(0, 1, "Key2", "Key2".toByteArray(), System.currentTimeMillis(), 2)) +// store!!.set( +// arrayOf( +// KVItem(0, 4, "Key3", "Key3".toByteArray(), System.currentTimeMillis(), 2), +// KVItem(0, 2, "Key4", "Key4".toByteArray(), System.currentTimeMillis(), 0) +// ) +// ) +// store!!.set( +// Arrays.asList( +// *arrayOf( +// KVItem(0, 4, "Key5", "Key5".toByteArray(), System.currentTimeMillis(), 1), +// KVItem(0, 5, "Key6", "Key6".toByteArray(), System.currentTimeMillis(), 5) +// ) +// ) +// ) +// var kvs = store!!.rawKVs +// Assert.assertEquals(6, kvs.size.toLong()) +// store!!.deleteAllKVs() +// kvs = store!!.rawKVs +// Assert.assertEquals(0, kvs.size.toLong()) +// } +// +// @Test +// fun testSetLocalIncrementsVersion() { +// store!!.deleteAllKVs() +// val obj = store!!.set(0, 0, "Key1", "Key1".toByteArray(), System.currentTimeMillis(), 0) +// Assert.assertNull(obj.err) +// val test = store!!.rawKVs +// Assert.assertEquals(1, test.size.toLong()) +// Assert.assertEquals(1, store!!.localVersion("Key1").version) +// } +// +// @Test +// fun testMultithreadedInserts() { +// val count = AtomicInteger() +// for (i in 0..999) { +// val finalI = i +// BRExecutor.getInstance().forLightWeightBackgroundTasks().execute { +// val obj = store!!.set( +// 0, 0, +// "Key$finalI", "Key1".toByteArray(), System.currentTimeMillis(), 0 +// ) +// Assert.assertNull(obj.err) +// Assert.assertEquals(obj.version, 1) +// count.incrementAndGet() +// val remObj = store!!.delete( +// "Key$finalI", store!!.localVersion( +// "Key$finalI" +// ).version +// ) +// Assert.assertNull(remObj.err) +// count.decrementAndGet() +// } +// } +// try { +// Thread.sleep(10000) +// Assert.assertEquals(count.get().toLong(), 0) +// val items = store!!.rawKVs +// Assert.assertEquals(items.size.toLong(), 1000) +// } catch (e: InterruptedException) { +// e.printStackTrace() +// } +// } +// +// @Test +// fun testSetThenGet() { +// val value = "hello".toByteArray() +// val setObj = store!!.set(0, 0, "hello", value, System.currentTimeMillis(), 0) +// Assert.assertNull(setObj.err) +// val v1 = setObj.version +// val t1 = setObj.time +// var obj = store!!["hello", 0] +// val kvNoVersion = obj.kv +// obj = store!!["hello", 1] +// val kvWithVersion = obj.kv +// Assert.assertArrayEquals(value, kvNoVersion.value) +// Assert.assertEquals(v1, kvNoVersion.version) +// Assert.assertEquals(t1.toDouble(), kvNoVersion.time.toDouble(), 0.001) +// +// Assert.assertNotNull(kvWithVersion) +// Assert.assertArrayEquals(value, kvWithVersion.value) +// Assert.assertEquals(v1, kvWithVersion.version) +// Assert.assertEquals(t1.toDouble(), kvWithVersion.time.toDouble(), 0.001) +// } +// +// @Test +// fun testSetThenSetIncrementsVersion() { +// val value = "hello".toByteArray() +// val value2 = "hello2".toByteArray() +// val setObj = store!!.set(0, 0, "hello", value, System.currentTimeMillis(), 0) +// val setObj2 = store!!.set(setObj.version, 0, "hello", value2, System.currentTimeMillis(), 0) +// Assert.assertEquals(setObj2.version, setObj.version + 1) +// } +// +// @Test +// fun testSetThenDel() { +// val value = "hello".toByteArray() +// val setObj = store!!.set(0, 0, "hello", value, System.currentTimeMillis(), 0) +// val delObj = store!!.delete("hello", setObj.version) +// Assert.assertNull(setObj.err) +// Assert.assertNull(delObj.err) +// +// Assert.assertEquals(delObj.version, setObj.version + 1) +// } +// +// @Test +// fun testSetThenDelThenGet() { +// val value = "hello".toByteArray() +// val setObj = store!!.set(0, 0, "hello", value, System.currentTimeMillis(), 0) +// val delObj = store!!.delete("hello", setObj.version) +// Assert.assertNull(setObj.err) +// Assert.assertNull(delObj.err) +// +// val `object` = store!!["hello", 0] +// val getKv = `object`.kv +// +// Assert.assertEquals(delObj.version, setObj.version + 1) +// Assert.assertEquals(getKv.version, setObj.version + 1) +// } +// +// @Test +// fun testSetWithIncorrectFirstVersionFails() { +// val value = "hello".toByteArray() +// val setObj = store!!.set(1, 0, "hello", value, System.currentTimeMillis(), 0) +// Assert.assertNotNull(setObj.err) +// } +// +// @Test +// fun testSetWithStaleVersionFails() { +// val value = "hello".toByteArray() +// val setObj = store!!.set(0, 0, "hello", value, System.currentTimeMillis(), 0) +// val setStaleObj = store!!.set(0, 0, "hello", value, System.currentTimeMillis(), 0) +// Assert.assertNull(setObj.err) +// Assert.assertEquals(CompletionObject.RemoteKVStoreError.conflict, setStaleObj.err) +// } +// +// @Test +// fun testGetNonExistentKeyFails() { +// val `object` = store!!["hello", 0] +// val getKv = `object`.kv +// Assert.assertNull(getKv) +// } +// +// @Test +// fun testGetNonExistentKeyVersionFails() { +// val `object` = store!!["hello", 1] +// val getKv = `object`.kv +// +// Assert.assertNull(getKv) +// } +// +// @Test +// fun testGetAllKeys() { +// val value = "hello".toByteArray() +// val time = System.currentTimeMillis() +// val setObj = store!!.set(0, 0, "hello", value, time, 0) +// val list = store!!.rawKVs +// +// Assert.assertNotNull(list) +// Assert.assertEquals(1, list.size.toLong()) +// Assert.assertEquals("hello", list[0].key) +// Assert.assertEquals(setObj.version, list[0].version) +// Assert.assertEquals(setObj.time.toDouble(), list[0].time.toDouble(), 0.001) +// Assert.assertEquals(0, list[0].remoteVersion) +// Assert.assertEquals(0, list[0].deleted.toLong()) +// } +// +// @Test +// fun testSetRemoteVersion() { +// val value = "hello".toByteArray() +// val time = System.currentTimeMillis() +// val setObj = store!!.set(0, 0, "hello", value, time, 0) +// val setRemoteVersionObj = store!!.setRemoteVersion("hello", setObj.version, 1) +// Assert.assertEquals(setRemoteVersionObj.version, setObj.version + 1) +// val remoteVer = store!!.remoteVersion("hello") +// Assert.assertEquals(1, remoteVer) +// } +// +// @Test +// fun testBasicSyncGetAllObjects() { +// val success = store!!.syncAllKeys() +// Assert.assertEquals(true, success) +// +// val localKeys = store!!.rawKVs +// Assert.assertEquals( +// ((remote as MockUpAdapter).remoteKVs.size - 1).toLong(), +// localKeys.size.toLong() +// ) +// assertDatabasesAreSynced() +// } +// +// @Test +// fun testSyncTenTimes() { +// var n = 10 +// while (n > 0) { +// val success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// n-- +// } +// assertDatabasesAreSynced() +// } +// +// @Test +// fun testSyncAddsLocalKeysToRemote() { +// val setObj = +// store!!.set(KVItem(0, -1, "derp", "derp".toByteArray(), System.currentTimeMillis(), 0)) +// Assert.assertNull(setObj.err) +// val success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// val obj = remote["derp", 1] +// Assert.assertArrayEquals( +// ReplicatedKVStore.decrypt(obj.value, mActivityRule.getActivity()), +// "derp".toByteArray() +// ) +// } +// +// @Test +// fun testSyncSavesRemoteVersion() { +// val success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// val ver = store!!.remoteVersion("hello") +// Assert.assertEquals((remote as MockUpAdapter).remoteKVs["hello"]!!.version, 1) +// Assert.assertEquals(remote.remoteKVs["hello"]!!.version, ver) +// assertDatabasesAreSynced() +// } +// +// @Test +// fun testSyncPreventsAnotherConcurrentSync() { +//// boolean success = store.syncAllKeys(); +//// new Thread(new Runnable() { +//// @Override +//// public void run() { +//// boolean success = store.syncAllKeys(); +//// Assert.assertTrue(success); +//// Assert.assertFalse(success); +//// } +//// }).start(); +//// Assert.assertTrue(success); +// } +// +// @Test +// fun testLocalDeleteReplicates() { +// val setObj = store!!.set( +// KVItem( +// 0, +// 0, +// "goodbye_cruel_world", +// "goodbye_cruel_world".toByteArray(), +// System.currentTimeMillis(), +// 0 +// ) +// ) +// Assert.assertNull(setObj.err) +// var success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// val delObj = store!!.delete( +// "goodbye_cruel_world", +// store!!.localVersion("goodbye_cruel_world").version +// ) +// Assert.assertNull(delObj.err) +// success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// val kv = (remote as MockUpAdapter).remoteKVs["goodbye_cruel_world"] +// Assert.assertTrue(kv!!.deleted > 0) +// } +// +// @Test +// fun testLocalUpdateReplicates() { +// var setObj = store!!.set( +// KVItem( +// 0, +// -1, +// "goodbye_cruel_world", +// "goodbye_cruel_world".toByteArray(), +// System.currentTimeMillis(), +// 0 +// ) +// ) +// var success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// setObj = store!!.set( +// KVItem( +// store!!.localVersion("goodbye_cruel_world").version, +// -1, +// "goodbye_cruel_world", +// "goodbye_cruel_world with some new info".toByteArray(), +// System.currentTimeMillis(), +// 0 +// ) +// ) +// success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// Assert.assertArrayEquals( +// "goodbye_cruel_world with some new info".toByteArray(), ReplicatedKVStore.decrypt( +// remote["goodbye_cruel_world", store!!.remoteVersion("goodbye_cruel_world")].value, +// mActivityRule.getActivity() +// ) +// ) +// } +// +// @Test +// fun testRemoteDeleteReplicates() { +// var success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// val kv = (remote as MockUpAdapter).remoteKVs["hello"] +// remote.remoteKVs["hello"] = +// KVItem(kv!!.version + 1, -1, kv.key, kv.value, System.currentTimeMillis(), 1) +// success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// val getObj = store!!["hello", 0] +// Assert.assertNull(getObj.err) +// Assert.assertTrue(getObj.kv.deleted > 0) +// success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// } +// +// @Test +// fun testRemoteUpdateReplicates() { +// var success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// +// val kv = (remote as MockUpAdapter).remoteKVs["hello"] +// remote.remoteKVs["hello"] = +// KVItem( +// kv!!.version + 1, +// -1, +// kv.key, +// ReplicatedKVStore.encrypt("newVal".toByteArray(), mActivityRule.getActivity()), +// System.currentTimeMillis(), +// 0 +// ) +// success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// +// val getObj = store!!["hello", 0] +// Assert.assertNull(getObj.err) +// Assert.assertArrayEquals(getObj.kv.value, "newVal".toByteArray()) +// success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// assertDatabasesAreSynced() +// } +// +// @Test +// fun testEnableEncryptedReplication() { +// (remote as MockUpAdapter).remoteKVs.clear() +// val setObj = +// store!!.set(KVItem(0, 0, "derp", "derp".toByteArray(), System.currentTimeMillis(), 0)) +// Assert.assertNull(setObj.err) +// val success = store!!.syncAllKeys() +// Assert.assertTrue(success) +// val obj = remote["derp", 1] +// Assert.assertArrayEquals( +// ReplicatedKVStore.decrypt(obj.value, mActivityRule.getActivity()), +// "derp".toByteArray() +// ) +// } +// +// @Test +// fun testGetAllMds() { +// store!![0, 1, "Key1", "Key1".toByteArray(), System.currentTimeMillis()] = +// 0 +// store!!.set(KVItem(0, 1, "Key2", "Key2".toByteArray(), System.currentTimeMillis(), 2)) +// store!!.set( +// arrayOf( +// KVItem(0, 4, "fdf-gsd34534", "second".toByteArray(), System.currentTimeMillis(), 2), +// KVItem( +// 0, +// 2, +// "fsdtxn2-fdslkjf34", +// "ignore".toByteArray(), +// System.currentTimeMillis(), +// 0 +// ) +// ) +// ) +// store!!.set( +// Arrays.asList( +// *arrayOf( +// KVItem(0, 4, "Key5", "Key5".toByteArray(), System.currentTimeMillis(), 1), +// KVItem(0, 5, "Key6", "Key6".toByteArray(), System.currentTimeMillis(), 5) +// ) +// ) +// ) +// val kvs = store!!.rawKVs +// Assert.assertEquals(kvs.size.toLong(), 6) +// +// val tx = TxMetaData() +// val theHash = byteArrayOf(3, 5, 64, 2, 4, 5, 63, 7, 0, 56, 34) +// tx.blockHeight = 123 +// tx.classVersion = 3 +// tx.comment = "hehey !" +// tx.creationTime = 21324 +// tx.deviceId = "someDevice2324" +// tx.fee = 234 +// tx.txSize = 23423 +// tx.exchangeCurrency = "curr" +// tx.exchangeRate = 23.4343 +// KVStoreManager.getInstance().putTxMetaData(mActivityRule.getActivity(), tx, theHash) +// val items = store!!.rawKVs +// Assert.assertEquals(7, items.size.toLong()) +// +// val mds = KVStoreManager.getInstance().getAllTxMD(mActivityRule.getActivity()) +// Assert.assertEquals(mds.size.toLong(), 1) +// +// // Assert.assertEquals(mds.(0).blockHeight, 123); +//// Assert.assertEquals(mds.get(0).classVersion, 3); +//// Assert.assertEquals(mds.get(0).comment, "hehey !"); +//// Assert.assertEquals(mds.get(0).creationTime, 21324); +//// Assert.assertEquals(mds.get(0).deviceId, "someDevice2324"); +//// Assert.assertEquals(mds.get(0).fee, 234); +//// Assert.assertEquals(mds.get(0).txSize, 23423); +//// Assert.assertEquals(mds.get(0).exchangeCurrency, "curr"); +//// Assert.assertEquals(mds.get(0).exchangeRate, 23.4343, 0); +// } +// +// @Test +// fun testEncryptDecrypt() { +// val data = +// "Ladies and Gentlemen of the class of '99: If I could offer you only one tip for the future, " + +// "sunscreen would be it." +// val encryptedData = +// ReplicatedKVStore.encrypt(data.toByteArray(), mActivityRule.getActivity()) +// +// Assert.assertTrue(encryptedData != null && encryptedData.size > 0) +// +// val decryptedData = ReplicatedKVStore.decrypt(encryptedData, mActivityRule.getActivity()) +// +// Assert.assertNotEquals(encryptedData, decryptedData) +// +// Assert.assertArrayEquals(decryptedData, data.toByteArray()) +// Assert.assertEquals(String(decryptedData!!), data) +// } +// +// @Test +// fun testKVManager() { +// val tx = TxMetaData() +// val theHash = byteArrayOf(3, 5, 64, 2, 4, 5, 63, 7, 0, 56, 34) +// tx.blockHeight = 123 +// tx.classVersion = 3 +// tx.comment = "hehey !" +// tx.creationTime = 21324 +// tx.deviceId = "someDevice2324" +// tx.fee = 234 +// tx.txSize = 23423 +// tx.exchangeCurrency = "curr" +// tx.exchangeRate = 23.4343 +// KVStoreManager.getInstance().putTxMetaData(mActivityRule.getActivity(), tx, theHash) +// val items = store!!.rawKVs +// Assert.assertEquals(1, items.size.toLong()) +// +// val newTx = KVStoreManager.getInstance().getTxMetaData(mActivityRule.getActivity(), theHash) +// Assert.assertEquals(newTx.blockHeight.toLong(), 123) +// Assert.assertEquals(newTx.classVersion.toLong(), 3) +// Assert.assertEquals(newTx.comment, "hehey !") +// Assert.assertEquals(newTx.creationTime.toLong(), 21324) +// Assert.assertEquals(newTx.deviceId, "someDevice2324") +// Assert.assertEquals(newTx.fee, 234) +// Assert.assertEquals(newTx.txSize.toLong(), 23423) +// Assert.assertEquals(newTx.exchangeCurrency, "curr") +// Assert.assertEquals(newTx.exchangeRate, 23.4343, 0.0) +// } //((MockUpAdapter) remote).remoteKVs.size() +// +// companion object { +// val TAG: String = KVStoreTests::class.java.name +// +// @ClassRule +// var mActivityRule: ActivityTestRule = +// ActivityTestRule( +// WriteDownActivity::class.java +// ) +// private val remote: KVStoreAdaptor = MockUpAdapter() +// private var store: ReplicatedKVStore? = null +// } +//} +// +//} \ No newline at end of file diff --git a/app/src/test/java/com/litewallet/tools/security/NewKeyStoreTests.kt b/app/src/test/java/com/litewallet/tools/security/NewKeyStoreTests.kt new file mode 100644 index 000000000..05181f696 --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/security/NewKeyStoreTests.kt @@ -0,0 +1,190 @@ +package com.litewallet.tools.security +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import io.mockk.verifyOrder +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test +class NewKeyStoreTests { +} + +//package com.litewallet.security; +// +//import android.app.Activity; +//import android.security.keystore.UserNotAuthenticatedException; +//import androidx.test.rule.ActivityTestRule; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +// +//import com.breadwallet.presenter.activities.settings.TestActivity; +//import com.breadwallet.tools.security.BRKeyStore; +// +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Rule; +//import org.junit.Test; +//import org.junit.runner.RunWith; +// +//import static com.breadwallet.tools.security.BRKeyStore.PHRASE_ALIAS; +//import static com.breadwallet.tools.security.BRKeyStore.aliasObjectMap; +// +//@RunWith(AndroidJUnit4.class) +// public class NewKeyStoreTests { +// public static final String TAG = NewKeyStoreTests.class.getName(); +// +// @Rule +// public ActivityTestRule mActivityRule = new ActivityTestRule<>(TestActivity.class); +// +// @Before +// public void setup() { +// BRKeyStore.resetWalletKeyStore(mActivityRule.getActivity()); +// } +// +// @Test +// public void testBase64() { +// Activity app = mActivityRule.getActivity(); +// String temp = "here is some data to encrypt! @#$%^&*"; +// byte[] phrase = temp.getBytes(); +// BRKeyStore.storeEncryptedData(app, phrase, "phrase"); +// byte[] retrievedPhrase = BRKeyStore.retrieveEncryptedData(app, "phrase"); +// Assert.assertNotNull(retrievedPhrase); +// Assert.assertArrayEquals("Oh no", phrase, retrievedPhrase); +// String newTemp = new String(retrievedPhrase); +// Assert.assertEquals(temp, newTemp); +// +// } +// +// @Test +// public void setNewGetNew() { +// //set get phrase +// byte[] phrase = "axis husband project any sea patch drip tip spirit tide bring belt".getBytes(); +// try { +// boolean b = BRKeyStore.putPhrase(phrase, mActivityRule.getActivity(), 0); +// Assert.assertEquals(true, b); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// try { +// byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); +// Assert.assertNotNull(getPhrase); +// Assert.assertEquals(new String(getPhrase), new String(phrase)); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// +// } +// +// @Test +// public void setOldSetNew() { +// byte[] phrase = "axis husband project any sea patch drip tip spirit tide bring belt".getBytes(); +// BRKeyStore.AliasObject obj = aliasObjectMap.get(PHRASE_ALIAS); +// try { +// boolean b = BRKeyStore._setOldData(mActivityRule.getActivity(), phrase, obj.alias, obj.datafileName, obj.ivFileName, 0, true); +// Assert.assertEquals(true, b); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// try { +// byte[] getPhrase = BRKeyStore._getOldData(mActivityRule.getActivity(), obj.alias, obj.datafileName, obj.ivFileName, 0); +// Assert.assertNotNull(getPhrase); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// +// try { +// boolean b = BRKeyStore.putPhrase(phrase, mActivityRule.getActivity(), 0); +// Assert.assertEquals(true, b); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// +// try { +// byte[] getPhrase = BRKeyStore._getOldData(mActivityRule.getActivity(), obj.alias, obj.datafileName, obj.ivFileName, 0); +// Assert.assertNull(getPhrase); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// +// try { +// byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); +// Assert.assertNotNull(getPhrase); +// Assert.assertEquals(new String(getPhrase), new String(phrase)); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// +// } +// +// @Test +// public void setOldGetNew() { +// +// //set get phrase +// byte[] phrase = "axis husband project any sea patch drip tip spirit tide bring belt".getBytes(); +// BRKeyStore.AliasObject obj = aliasObjectMap.get(PHRASE_ALIAS); +// try { +// boolean s = BRKeyStore._setOldData(mActivityRule.getActivity(), phrase, obj.alias, obj.datafileName, obj.ivFileName, 0, true); +// Assert.assertEquals(true, s); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// try { +// byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); +// Assert.assertNotNull(getPhrase); +// Assert.assertEquals(new String(getPhrase), new String(phrase)); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// try { +// byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); +// Assert.assertNotNull(getPhrase); +// Assert.assertEquals(new String(getPhrase), new String(phrase)); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// try { +// byte[] getPhrase = BRKeyStore._getOldData(mActivityRule.getActivity(), obj.alias, obj.datafileName, obj.ivFileName, 0); +// Assert.assertNull(getPhrase); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// try { +// byte[] getPhrase = BRKeyStore.getPhrase(mActivityRule.getActivity(), 0); +// Assert.assertNotNull(getPhrase); +// Assert.assertEquals(new String(getPhrase), new String(phrase)); +// } catch (UserNotAuthenticatedException e) { +// e.printStackTrace(); +// Assert.fail(); +// } +// +// } +// +// @Test +// public void allMultiThreading() { +// for (int i = 0; i < 10; i++) { +// setup(); +// setNewGetNew(); +// setup(); +// setOldGetNew(); +// setup(); +// setOldSetNew(); +// setup(); +// +// } +// +// } +// +//} diff --git a/app/src/test/java/com/litewallet/tools/security/ReplicatedKVStoreTest.kt b/app/src/test/java/com/litewallet/tools/security/ReplicatedKVStoreTest.kt new file mode 100644 index 000000000..058fa5a54 --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/security/ReplicatedKVStoreTest.kt @@ -0,0 +1,4 @@ +package com.litewallet.tools.security + +//TODO: pls fill this +class ReplicatedKVStoreTest \ No newline at end of file diff --git a/app/src/test/java/com/litewallet/tools/util/BRConstantsTest.kt b/app/src/test/java/com/litewallet/tools/util/BRConstantsTest.kt new file mode 100644 index 000000000..ce7bf0bfe --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/util/BRConstantsTest.kt @@ -0,0 +1,108 @@ +package com.litewallet.tools.util + +import com.breadwallet.tools.util.BRConstants +import org.junit.Assert +import org.junit.Assert.assertSame + +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import io.mockk.verifyOrder +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test + +//TODO: migrate from [com.litewallet.analytics.ConstantsTests] +class BRConstantsTest { + + @Test + fun `validate Litecoin symbol constant`() { + assertSame(BRConstants.litecoinLowercase,"ł") + assertSame(BRConstants.litecoinUppercase,"Ł") + } +} + + +//package com.litewallet.analytics; +//import androidx.test.ext.junit.runners.AndroidJUnit4; +//import android.util.Log; +//import androidx.test.ext.junit.rules.ActivityScenarioRule; +// +//import com.breadwallet.presenter.activities.intro.IntroActivity; +//import com.breadwallet.tools.util.BRConstants; +// +//import org.junit.After; +//import org.junit.Assert; +//import org.junit.Before; +//import org.junit.Rule; +//import org.junit.Test; +//import org.junit.runner.RunWith; +// +//import java.net.URI; +// +//@Deprecated +//@RunWith(AndroidJUnit4.class) +// public class ConstantsTests { +// public static final String TAG = ConstantsTests.class.getName(); +// @Rule +// public ActivityScenarioRule mActivityRule = new ActivityScenarioRule<>(IntroActivity.class); +// @Before +// public void setUp() { +// Log.e(TAG, "setUp: "); +// } +// +// @After +// public void tearDown() { +// } +// @Test +// public void testLitecoinSymbolConstants() { +// Assert.assertSame(BRConstants.litecoinLowercase,"ł"); +// Assert.assertSame(BRConstants.litecoinUppercase,"Ł"); +// } +// @Test +// public void testAppExternalURLConstants() { +// Assert.assertSame(BRConstants.TWITTER_LINK,"https://twitter.com/Litewallet_App"); +// Assert.assertSame(BRConstants.INSTAGRAM_LINK,"https://www.instagram.com/litewallet.app"); +// Assert.assertSame(BRConstants.WEB_LINK,"https://litewallet.io"); +// Assert.assertSame(BRConstants.TOS_LINK,"https://litewallet.io/privacy"); +// Assert.assertSame(BRConstants.CUSTOMER_SUPPORT_LINK,"https://support.litewallet.io/hc/en-us/requests/new"); +// Assert.assertSame(BRConstants.BITREFILL_AFFILIATE_LINK,"https://www.bitrefill.com/"); +// } +// @Test +// public void testFirebaseAnalyticsConstants() { +// Assert.assertSame(BRConstants._20191105_AL,"app_launched"); +// Assert.assertSame(BRConstants._20191105_VSC,"visit_send_controller"); +// Assert.assertSame(BRConstants._20202116_VRC,"visit_receive_controller"); +// Assert.assertSame(BRConstants._20191105_DSL,"did_send_ltc"); +// Assert.assertSame(BRConstants._20191105_DTBT,"did_tap_buy_tab"); +// Assert.assertSame(BRConstants._20200111_RNI,"rate_not_initialized"); +// Assert.assertSame(BRConstants._20200111_FNI,"feeperkb_not_initialized"); +// Assert.assertSame(BRConstants._20200111_TNI,"transaction_not_initialized"); +// Assert.assertSame(BRConstants._20200111_WNI,"wallet_not_initialized"); +// Assert.assertSame(BRConstants._20200111_PNI,"phrase_not_initialized"); +// Assert.assertSame(BRConstants._20200111_UTST,"unable_to_sign_transaction"); +// Assert.assertSame(BRConstants._20200112_ERR,"error"); +// Assert.assertSame(BRConstants._20200112_DSR,"did_start_resync"); +// Assert.assertSame(BRConstants._20200125_DSRR,"did_show_review_request"); +// Assert.assertSame(BRConstants._20201118_DTGS,"did_tap_get_support"); +// Assert.assertSame(BRConstants._20200217_DUWP,"did_unlock_with_pin"); +// Assert.assertSame(BRConstants._20200217_DUWB,"did_unlock_with_biometrics"); +// Assert.assertSame(BRConstants._20200301_DUDFPK,"did_use_default_fee_per_kb"); +// Assert.assertSame(BRConstants._20201121_SIL,"started_IFPS_lookup"); +// Assert.assertSame(BRConstants._20201121_DRIA,"did_resolve_IPFS_address"); +// Assert.assertSame(BRConstants._20201121_FRIA,"failed_resolve_IPFS_address"); +// Assert.assertSame(BRConstants._20200207_DTHB,"did_tap_header_balance"); +// Assert.assertSame(BRConstants._20210427_HCIEEH,"heartbeat_check_if_event_even_happens"); +// Assert.assertSame(BRConstants._20220822_UTOU,"user_tapped_on_ud"); +// Assert.assertSame(BRConstants._20230131_NENR,"no_error_nominal_response"); +// Assert.assertSame(BRConstants._20230407_DCS,"did_complete_sync"); +// Assert.assertSame(BRConstants._20240123_RAGI,"registered_android_general_interest"); +// Assert.assertSame(BRConstants._20231225_UAP,"user_accepted_push"); +// Assert.assertSame(BRConstants._20240101_US,"user_signup"); +// Assert.assertSame(BRConstants._20241006_DRR,"did_request_rating"); +// Assert.assertSame(BRConstants._20241006_UCR,"user_completed_rating"); +// } +//} diff --git a/app/src/test/java/com/litewallet/tools/util/Bip39ReaderTest.kt b/app/src/test/java/com/litewallet/tools/util/Bip39ReaderTest.kt new file mode 100644 index 000000000..923a3b5d8 --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/util/Bip39ReaderTest.kt @@ -0,0 +1,4 @@ +package com.litewallet.tools.util + +//TODO: pls fill this +class Bip39ReaderTest \ No newline at end of file diff --git a/app/src/test/java/com/litewallet/tools/util/LocaleHelperTest.kt b/app/src/test/java/com/litewallet/tools/util/LocaleHelperTest.kt new file mode 100644 index 000000000..78b2bfa99 --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/util/LocaleHelperTest.kt @@ -0,0 +1,64 @@ +package com.litewallet.tools.util + +import android.content.Context +import com.breadwallet.entities.Language +import com.breadwallet.tools.util.LocaleHelper +import io.mockk.mockk +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse +import org.junit.Assert.assertTrue + +import io.mockk.coVerify +import io.mockk.every +import io.mockk.mockk +import io.mockk.spyk +import io.mockk.verify +import io.mockk.verifyOrder +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking +import org.junit.Assert.assertEquals +import org.junit.Test + +class LocaleHelperTest { + + @Test + fun `given LocaleHelper instance, then should validate default value`() { + val context: Context = mockk(relaxed = true) + + LocaleHelper.init(context) + + val currentLocale = LocaleHelper.instance.currentLocale + + assertTrue(currentLocale == Language.ENGLISH) + assertEquals("en", currentLocale.code) + assertEquals("English", currentLocale.title) + assertEquals("Select language", currentLocale.desc) + } + + @Test + fun `getLocale invoked, should return Locale object`() { + val localeIndonesian = LocaleHelper.getLocale(Language.INDONESIAN) + + assertEquals("id", localeIndonesian.language) + + val localeChineseSimplified = LocaleHelper.getLocale(Language.CHINESE_SIMPLIFIED) + + assertEquals("zh", localeChineseSimplified.language) + assertEquals("CN", localeChineseSimplified.country) + } + + @Test + fun `setLocaleIfNeeded invoked, should update current locale`() { + val context: Context = mockk(relaxed = true) + LocaleHelper.init(context) + + val currentLocale = LocaleHelper.instance.currentLocale + assertTrue(currentLocale == Language.ENGLISH) + + var changed = LocaleHelper.instance.setLocaleIfNeeded(Language.INDONESIAN) + assertTrue(changed) + + changed = LocaleHelper.instance.setLocaleIfNeeded(Language.INDONESIAN) + assertFalse(changed) + } +} \ No newline at end of file diff --git a/app/src/test/java/com/litewallet/tools/util/SeedWordTests.kt b/app/src/test/java/com/litewallet/tools/util/SeedWordTests.kt new file mode 100644 index 000000000..e7f7c12a0 --- /dev/null +++ b/app/src/test/java/com/litewallet/tools/util/SeedWordTests.kt @@ -0,0 +1,89 @@ +package com.litewallet.tools.util + + +class SeedWordTests { +} + +// +// +//package com.litewallet; +// +//import android.util.Log; +// +//import com.breadwallet.tools.util.Bip39Reader; +//import com.breadwallet.wallet.BRWalletManager; +// +//import org.apache.commons.io.IOUtils; +//import org.junit.Assert; +//import org.junit.Test; +//import org.junit.runner.RunWith; +// +//import java.io.IOException; +//import java.io.InputStream; +//import java.util.ArrayList; +//import java.util.Arrays; +//import java.util.List; +// +//import static org.hamcrest.Matchers.is; +//import static org.hamcrest.Matchers.notNullValue; +//import static org.junit.Assert.assertThat; +// +//import androidx.test.ext.junit.runners.AndroidJUnit4; +//import androidx.test.filters.LargeTest; +// +//@RunWith(AndroidJUnit4.class) +// @LargeTest +// +// public class PaperKeyTests { +// private static final String TAG = PaperKeyTests.class.getName(); +// public static final List PAPER_KEY_ENG = Arrays.asList("stick","sword","keen","afraid","smile","sting","huge","relax","nominee","arena","area","gift"); +// +// @Test +// public void testWordsValid() { +// List wordList = getAllWords(); +// Assert.assertEquals(wordList.size(), 2048); +// Assert.assertSame(PAPER_KEY_ENG.containsAll(wordList),true); +// } +// @Test +// public void testPaperKeyValidation() { +// List list = getAllWords(); +// assertThat(list.size(), is(2048)); +// } +// +// private List getAllWords() { +// List result = new ArrayList<>(); +// List names = new ArrayList<>(); +// names.add("en-BIP39Words.txt"); +// +// for (String fileName : names) { +// InputStream in = null; +// try { +// in = getClass().getResourceAsStream(fileName); +// String str = IOUtils.toString(in); +// String lines[] = str.split("\\r?\\n"); +// result.addAll(Arrays.asList(lines)); +// } catch (IOException e) { +// Log.e(TAG, "getAllWords: " + fileName + ", ", e); +// } finally { +// if (in != null) try { +// in.close(); +// } catch (IOException e) { +// e.printStackTrace(); +// } +// } +// } +// List cleanList = new ArrayList<>(); +// for (String s : result) { +// String cleanWord = Bip39Reader.cleanWord(s); +// cleanList.add(cleanWord); +// } +// assertThat(cleanList.size(), is(2048)); +// return cleanList; +// } +// +// private boolean isValid(String phrase, List words) { +// +// return BRWalletManager.getInstance().validateRecoveryPhrase((String[]) words.toArray(), phrase); +// } +// +// } From fb7e1e500e75bea9229146e76caec4fbd0860bd3 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 21 Dec 2024 22:44:18 +0000 Subject: [PATCH 05/35] code and version bump - Removed example test --- app/build.gradle | 4 +- .../com/litewallet/example/ExampleTest.kt | 125 ------------------ 2 files changed, 2 insertions(+), 127 deletions(-) delete mode 100644 app/src/test/java/com/litewallet/example/ExampleTest.kt diff --git a/app/build.gradle b/app/build.gradle index bb5e8827f..11696e78e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,8 +69,8 @@ android { applicationId = 'com.loafwallet' minSdkVersion 29 targetSdkVersion 34 - versionCode 20241220 - versionName "v2.13.0" + versionCode 20241221 + versionName "v2.14.0" multiDexEnabled true archivesBaseName = "${versionName}(${versionCode})" diff --git a/app/src/test/java/com/litewallet/example/ExampleTest.kt b/app/src/test/java/com/litewallet/example/ExampleTest.kt deleted file mode 100644 index 45a00b03f..000000000 --- a/app/src/test/java/com/litewallet/example/ExampleTest.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.litewallet.example - -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import io.mockk.verifyOrder -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Test - -class ExampleTest { - - @Test - fun `add invoked with valid mocked values , should return correct value as expected`() { - val dependencyOne = mockk() - val dependencyTwo = mockk() - - every { dependencyOne.value } returns 10 - every { dependencyTwo.value } returns 3 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = 13 - val result = system.add() - assertEquals(expected, result) - verify { - dependencyOne.value - dependencyTwo.value - } - } - - @Test - fun `add invoked with valid mocked value for dependencyOne, should return correct value as expected`() { - val dependencyOne = mockk() - val dependencyTwo = mockk(relaxed = true) - - every { dependencyOne.value } returns 7 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = 7 - val result = system.add() - assertEquals(expected, result) - verify { - dependencyOne.value - dependencyTwo.value - } - } - - @Test - fun `add invoked with valid mocked value for dependencyOne and actual value for dependencyTwo , should return correct value as expected`() { - val dependencyOne = mockk() - val dependencyTwo = spyk(DependencyTwo(3)) - - every { dependencyOne.value } returns 7 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = 10 - val result = system.add() - assertEquals(expected, result) - verify { - dependencyOne.value - dependencyTwo.value - } - } - - - @Test - fun `calc invoked with valid mocked values, should return correct value as expected and verify by order`() { - val dependencyOne = mockk() - val dependencyTwo = mockk() - - every { dependencyOne.value } returns 7 - every { dependencyTwo.value } returns 4 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = 17 - val result = system.calc() - assertEquals(expected, result) - verifyOrder { - system.multiply() - system.add() - } - } - - @Test - fun `basic example coroutines`() = runBlocking { - val dependencyOne = mockk() - val dependencyTwo = mockk() - - every { dependencyOne.value } returns 9 - every { dependencyTwo.value } returns 11 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = listOf(9, 11) - val result = system.fetch() - assertEquals(expected, result) - coVerify { system.fetch() } - } - -} - -class DependencyOne(val value: Int) -class DependencyTwo(val value: Int) - -class SystemUnderTest( - private val dependencyOne: DependencyOne, - private val dependencyTwo: DependencyTwo -) { - fun add(): Int = dependencyOne.value + dependencyTwo.value - fun multiply(): Int = dependencyOne.value * dependencyTwo.value - - fun calc(): Int = multiply() - add() - - suspend fun fetch(): List { - delay(3000) - return listOf(dependencyOne.value, dependencyTwo.value) - } -} \ No newline at end of file From 51137761d3b572a91252298b86194cd088daa219 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 21 Dec 2024 22:55:39 +0000 Subject: [PATCH 06/35] Moved Arabic location in List --- .../entities/IntroLanguageResource.kt | 32 +++++++++---------- .../java/com/breadwallet/entities/Language.kt | 4 +-- .../litewallet/tools/util/LocaleHelperTest.kt | 2 +- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt b/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt index a009364b2..06d59956c 100644 --- a/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt +++ b/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt @@ -25,6 +25,22 @@ class IntroLanguageResource { "¿Estás seguro de que quieres cambiar el idioma a español?", Language.SPANISH, ), + IntroLanguage( + Language.ARABIC.code, + Language.ARABIC.title, + "حدد مناقشة الطريقة الأكثر أمانًا وأمانًا لاستخدام Litecoin.", + R.raw.arabic, + "هل أنت متأكد أنك تريد تغيير اللغة إلى الإندونيسية؟", + Language.ARABIC + ), + IntroLanguage( + Language.FRENCH.code, + Language.FRENCH.title, + "La façon la plus sécurisée et sûre d'utiliser Litecoin.", + R.raw.french, + "Êtes-vous sûr de vouloir changer la langue en français ?", + Language.FRENCH + ), IntroLanguage( Language.INDONESIAN.code, Language.INDONESIAN.title, @@ -73,14 +89,6 @@ class IntroLanguageResource { "언어를 한국어로 변경하시겠습니까?", Language.KOREAN ), - IntroLanguage( - Language.FRENCH.code, - Language.FRENCH.title, - "La façon la plus sécurisée et sûre d'utiliser Litecoin.", - R.raw.french, - "Êtes-vous sûr de vouloir changer la langue en français ?", - Language.FRENCH - ), IntroLanguage( Language.TURKISH.code, Language.TURKISH.title, @@ -112,14 +120,6 @@ class IntroLanguageResource { R.raw.russian, "Вы уверены, что хотите сменить язык на русский?", Language.RUSSIAN - ), - IntroLanguage( - Language.ARABIC.code, - Language.ARABIC.title, - "حدد مناقشة الطريقة الأكثر أمانًا وأمانًا لاستخدام Litecoin.", - R.raw.arabic, - "هل أنت متأكد أنك تريد تغيير اللغة إلى الإندونيسية؟", - Language.ARABIC ) ) } diff --git a/app/src/main/java/com/breadwallet/entities/Language.kt b/app/src/main/java/com/breadwallet/entities/Language.kt index 69adc0262..191e160af 100644 --- a/app/src/main/java/com/breadwallet/entities/Language.kt +++ b/app/src/main/java/com/breadwallet/entities/Language.kt @@ -14,6 +14,7 @@ enum class Language( GERMAN("de", "Deutsch", "Sprache auswählen"), SPANISH("es", "Español", "Seleccione el idioma"), FRENCH("fr", "Français", "Sélectionner la langue"), + ARABIC("ar", "عربي", "اختر اللغة"), INDONESIAN("in", "Indonesia", "Pilih bahasa"), ITALIAN("it", "Italiano", "Seleziona la lingua"), PORTUGUESE("pt", "Português", "Selecione o idioma"), @@ -23,8 +24,7 @@ enum class Language( KOREAN("ko", "한국어", "언어 선택"), JAPANESE("ja", "日本語", "言語を選択する"), CHINESE_SIMPLIFIED("zh-CN", "简化字", "选择语言"), - CHINESE_TRADITIONAL("zh-TW", "繁體字", "選擇語言"), - ARABIC("ar", "عربي", "اختر اللغة") + CHINESE_TRADITIONAL("zh-TW", "繁體字", "選擇語言") ; companion object { diff --git a/app/src/test/java/com/litewallet/tools/util/LocaleHelperTest.kt b/app/src/test/java/com/litewallet/tools/util/LocaleHelperTest.kt index e1bef5b25..eb6dcfe6a 100644 --- a/app/src/test/java/com/litewallet/tools/util/LocaleHelperTest.kt +++ b/app/src/test/java/com/litewallet/tools/util/LocaleHelperTest.kt @@ -78,6 +78,7 @@ class LocaleHelperTest { "de", "es", "fr", + "ar", "in", "it", "pt", @@ -88,7 +89,6 @@ class LocaleHelperTest { "ja", "zh-CN", "zh-TW", - "ar" ), langCodes ) From 28ab5987cc9c73b823b6a0386d72abbf5e01b200 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 21 Dec 2024 23:01:25 +0000 Subject: [PATCH 07/35] renamed config workflow --- .circleci/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index d8a282950..8b324d907 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -50,6 +50,6 @@ jobs: # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: - test-and-build: + build-and-test: jobs: - - unit-test + - all-unit-tests From 18183ae3a5c031f99b4e9270195a17952c7e1cdd Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 21 Dec 2024 23:02:37 +0000 Subject: [PATCH 08/35] Update config.yml polish config --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8b324d907..8bd7f3588 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -8,7 +8,7 @@ orbs: # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: - unit-test: + all-unit-tests: executor: name: android/android_docker resource_class: large From 0034f00132a39c8bdd269cbe85222d8f7c209f07 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 21 Dec 2024 23:46:09 +0000 Subject: [PATCH 09/35] resolved conflict --- .../java/com/breadwallet/entities/IntroLanguageResource.kt | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt b/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt index 85a79736a..06d59956c 100644 --- a/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt +++ b/app/src/main/java/com/breadwallet/entities/IntroLanguageResource.kt @@ -24,7 +24,6 @@ class IntroLanguageResource { R.raw.spanish, "¿Estás seguro de que quieres cambiar el idioma a español?", Language.SPANISH, -<<<<<<< HEAD ), IntroLanguage( Language.ARABIC.code, @@ -41,8 +40,6 @@ class IntroLanguageResource { R.raw.french, "Êtes-vous sûr de vouloir changer la langue en français ?", Language.FRENCH -======= ->>>>>>> main ), IntroLanguage( Language.INDONESIAN.code, From b457c2f3653a0d7baa7ad02556dcccf78679acfe Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 21 Dec 2024 23:48:06 +0000 Subject: [PATCH 10/35] removed example test --- .../com/litewallet/example/ExampleTest.kt | 125 ------------------ 1 file changed, 125 deletions(-) delete mode 100644 app/src/test/java/com/litewallet/example/ExampleTest.kt diff --git a/app/src/test/java/com/litewallet/example/ExampleTest.kt b/app/src/test/java/com/litewallet/example/ExampleTest.kt deleted file mode 100644 index 45a00b03f..000000000 --- a/app/src/test/java/com/litewallet/example/ExampleTest.kt +++ /dev/null @@ -1,125 +0,0 @@ -package com.litewallet.example - -import io.mockk.coVerify -import io.mockk.every -import io.mockk.mockk -import io.mockk.spyk -import io.mockk.verify -import io.mockk.verifyOrder -import kotlinx.coroutines.delay -import kotlinx.coroutines.runBlocking -import org.junit.Assert.assertEquals -import org.junit.Test - -class ExampleTest { - - @Test - fun `add invoked with valid mocked values , should return correct value as expected`() { - val dependencyOne = mockk() - val dependencyTwo = mockk() - - every { dependencyOne.value } returns 10 - every { dependencyTwo.value } returns 3 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = 13 - val result = system.add() - assertEquals(expected, result) - verify { - dependencyOne.value - dependencyTwo.value - } - } - - @Test - fun `add invoked with valid mocked value for dependencyOne, should return correct value as expected`() { - val dependencyOne = mockk() - val dependencyTwo = mockk(relaxed = true) - - every { dependencyOne.value } returns 7 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = 7 - val result = system.add() - assertEquals(expected, result) - verify { - dependencyOne.value - dependencyTwo.value - } - } - - @Test - fun `add invoked with valid mocked value for dependencyOne and actual value for dependencyTwo , should return correct value as expected`() { - val dependencyOne = mockk() - val dependencyTwo = spyk(DependencyTwo(3)) - - every { dependencyOne.value } returns 7 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = 10 - val result = system.add() - assertEquals(expected, result) - verify { - dependencyOne.value - dependencyTwo.value - } - } - - - @Test - fun `calc invoked with valid mocked values, should return correct value as expected and verify by order`() { - val dependencyOne = mockk() - val dependencyTwo = mockk() - - every { dependencyOne.value } returns 7 - every { dependencyTwo.value } returns 4 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = 17 - val result = system.calc() - assertEquals(expected, result) - verifyOrder { - system.multiply() - system.add() - } - } - - @Test - fun `basic example coroutines`() = runBlocking { - val dependencyOne = mockk() - val dependencyTwo = mockk() - - every { dependencyOne.value } returns 9 - every { dependencyTwo.value } returns 11 - - val system = SystemUnderTest(dependencyOne, dependencyTwo) - - val expected = listOf(9, 11) - val result = system.fetch() - assertEquals(expected, result) - coVerify { system.fetch() } - } - -} - -class DependencyOne(val value: Int) -class DependencyTwo(val value: Int) - -class SystemUnderTest( - private val dependencyOne: DependencyOne, - private val dependencyTwo: DependencyTwo -) { - fun add(): Int = dependencyOne.value + dependencyTwo.value - fun multiply(): Int = dependencyOne.value * dependencyTwo.value - - fun calc(): Int = multiply() - add() - - suspend fun fetch(): List { - delay(3000) - return listOf(dependencyOne.value, dependencyTwo.value) - } -} \ No newline at end of file From 6a7e8a52d6d024899f803bcde1c5226303413a9c Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 21 Dec 2024 23:55:10 +0000 Subject: [PATCH 11/35] code bump --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 11696e78e..7a13fa823 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,7 +69,7 @@ android { applicationId = 'com.loafwallet' minSdkVersion 29 targetSdkVersion 34 - versionCode 20241221 + versionCode 20241222 versionName "v2.14.0" multiDexEnabled true archivesBaseName = "${versionName}(${versionCode})" From 8f40256558afce6c584aaec0e42be0edb42721f2 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:29:14 +0000 Subject: [PATCH 12/35] Test --- .circleci/circle_env.sh | 27 +++++++++++++++++++++++++++ .circleci/config.yml | 16 ++++++++++++---- .gitmodules | 3 +++ litewallet-data | 1 + 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100755 .circleci/circle_env.sh create mode 160000 litewallet-data diff --git a/.circleci/circle_env.sh b/.circleci/circle_env.sh new file mode 100755 index 000000000..759b22c3f --- /dev/null +++ b/.circleci/circle_env.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +function copyEnvironmentVariablesToGradleProperties { + DIR="$(pwd)" + GRADLE_PROPERTIES=${DIR}"/gradle.properties" + export GRADLE_PROPERTIES + echo "Gradle Properties should exist at $GRADLE_PROPERTIES" + + if [[ ! -f "$GRADLE_PROPERTIES" ]]; then + echo "Gradle Properties does not exist" + + echo "Creating Gradle Properties file..." + touch $GRADLE_PROPERTIES + + echo "Writing keys to gradle.properties..." + echo "RELEASE_KEYSTORE_FILE=$RELEASE_KEYSTORE_FILE" >> ${GRADLE_PROPERTIES} + echo "RELEASE_KEYSTORE_PASSWORD=$RELEASE_KEYSTORE_PASSWORD" >> ${GRADLE_PROPERTIES} + echo "RELEASE_KEY_ALIAS=$RELEASE_KEY_ALIAS" >> ${GRADLE_PROPERTIES} + echo "RELEASE_KEY_PASSWORD=$RELEASE_KEY_PASSWORD" >> ${GRADLE_PROPERTIES} + fi +} + + + +function fetchKeystore { + sudo gpg --passphrase ${RELEASE_KEYSTORE_ENCRYPTION_KEY} --pinentry-mode loopback -o "app/$RELEASE_KEYSTORE_FILE" -d "upload-binary/$RELEASE_KEYSTORE_FILE.gpg" +} \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index 8bd7f3588..7dca8a53b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,12 +18,17 @@ jobs: - run: name: "litewallet-android unit test setup..." command: "echo Building tests for litewallet-android! && ls" + fetch_submodules: &fetch_submodules + - run: + name: "Initialize submodules" + command: "git submodule init && git submodule update --init --recursive" + fetch_keystore: &fetch_keystore + - run: + name: Fetch keystore + command: source .circleci/circle_env.sh && fetchKeystore - run: name: "Default for gradle.properties" command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" - - run: - name: "Initialize submodule" - command: "git submodule init && git submodule update --init --recursive" - run: name: "Export google-services.json to env" command: echo 'export $GOOGLE_SERVICES_JSON="$GOOGLE_SERVICES_JSON"' >> $BASH_ENV @@ -45,7 +50,10 @@ jobs: path: ~/test-results - store_artifacts: path: ~/test-results/junit - + create_release_credits: &create_release_credits + - run: + name: Create fake credits + command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows diff --git a/.gitmodules b/.gitmodules index 1945a5379..84bbdcbe1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "app/src/main/jni/loafwallet-core"] path = app/src/main/jni/loafwallet-core url = https://github.com/litecoin-foundation/loafwallet-core.git +[submodule "litewallet-data"] + path = litewallet-data + url = https://github.com/gruntsoftware/litewallet-data.git diff --git a/litewallet-data b/litewallet-data new file mode 160000 index 000000000..e13a9fef8 --- /dev/null +++ b/litewallet-data @@ -0,0 +1 @@ +Subproject commit e13a9fef8a190d906046b40fc02ce552aba4d1dd From 6b17e926207d060b89962e3bf319af5f18eba8c7 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:34:06 +0000 Subject: [PATCH 13/35] removed tabs --- .circleci/config.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7dca8a53b..732de0255 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,13 +19,13 @@ jobs: name: "litewallet-android unit test setup..." command: "echo Building tests for litewallet-android! && ls" fetch_submodules: &fetch_submodules - - run: - name: "Initialize submodules" - command: "git submodule init && git submodule update --init --recursive" + - run: + name: "Initialize submodules" + command: "git submodule init && git submodule update --init --recursive" fetch_keystore: &fetch_keystore - run: - name: Fetch keystore - command: source .circleci/circle_env.sh && fetchKeystore + name: Fetch keystore + command: source .circleci/circle_env.sh && fetchKeystore - run: name: "Default for gradle.properties" command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" @@ -50,14 +50,14 @@ jobs: path: ~/test-results - store_artifacts: path: ~/test-results/junit - create_release_credits: &create_release_credits - - run: - name: Create fake credits - command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties + create_release_credits: &create_release_credits + - run: + name: Create fake credits + command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: build-and-test: jobs: - - all-unit-tests + - all-unit-tests \ No newline at end of file From 79e12d8cf1135f4fddc9a29116edc5d13537f7e7 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:36:53 +0000 Subject: [PATCH 14/35] remvoed more tabs --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 732de0255..fa707e4fc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,7 @@ jobs: command: ./gradlew testLitewalletDebugUnitTest - android/save_gradle_cache - run: - name: Save test results + name: Saving test results command: | mkdir -p ~/test-results/junit/ find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; From 5d7e9e699a8f0199e944a2f1737d249e9fef4660 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:39:22 +0000 Subject: [PATCH 15/35] found the tab --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fa707e4fc..f92d9b393 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - run: name: "litewallet-android unit test setup..." command: "echo Building tests for litewallet-android! && ls" - fetch_submodules: &fetch_submodules + fetch_submodules: &fetch_submodules - run: name: "Initialize submodules" command: "git submodule init && git submodule update --init --recursive" From 7a213cab3a518db5ce81838970b3e94323ade7ec Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:40:49 +0000 Subject: [PATCH 16/35] comment out test --- .circleci/config.yml | 158 +++++++++++++++++++++++++++---------------- 1 file changed, 98 insertions(+), 60 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f92d9b393..8e1364d81 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,63 +1,101 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/2.0/configuration-reference -version: 2.1 +android_docker: &android_docker + docker: + - image: circleci/android:api-27-node8-alpha +fetch_submodules: &fetch_submodules + run: + name: Update submodules + command: git submodule sync && git submodule update --init +create_release_credits: &create_release_credits + run: + name: Create fake credits + command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties +gradle_execution_rights: &gradle_execution_rights + run: + name: Setup gradlew execution rights + command: chmod +x gradlew +fetch_keystore: &fetch_keystore + run: + name: Fetch keystore + command: source .circleci/circle_env.sh && fetchKeystore +build_and_sign: + <<: *android_docker + steps: + - checkout + - *fetch_submodules + - *fetch_keystore + - *create_release_credits + - *gradle_execution_rights + - run: + name: Assemble beta release + command: ./gradlew assembleRelease -orbs: - android: circleci/android@3.0.2 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/2.0/configuration-reference/#jobs -jobs: - all-unit-tests: - executor: - name: android/android_docker - resource_class: large - tag: 2024.07.1-ndk - steps: - - checkout - - run: - name: "litewallet-android unit test setup..." - command: "echo Building tests for litewallet-android! && ls" - fetch_submodules: &fetch_submodules - - run: - name: "Initialize submodules" - command: "git submodule init && git submodule update --init --recursive" - fetch_keystore: &fetch_keystore - - run: - name: Fetch keystore - command: source .circleci/circle_env.sh && fetchKeystore - - run: - name: "Default for gradle.properties" - command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" - - run: - name: "Export google-services.json to env" - command: echo 'export $GOOGLE_SERVICES_JSON="$GOOGLE_SERVICES_JSON"' >> $BASH_ENV - - run: - name: "decode $GOOGLE_SERVICES_JSON to google-services.json" - command: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json - - android/restore_gradle_cache - - run: - name: "Execute Unit Test" - command: ./gradlew testLitewalletDebugUnitTest - - android/save_gradle_cache - - run: - name: Saving test results - command: | - mkdir -p ~/test-results/junit/ - find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; - when: always - - store_test_results: - path: ~/test-results - - store_artifacts: - path: ~/test-results/junit - create_release_credits: &create_release_credits - - run: - name: Create fake credits - command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties - -# Invoke jobs via workflows -# See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: - build-and-test: + version: 2 + build_and_sign: jobs: - - all-unit-tests \ No newline at end of file + - build_and_sign + +# +# # Use the latest 2.1 version of CircleCI pipeline process engine. +# # See: https://circleci.com/docs/2.0/configuration-reference +# version: 2.1 +# +# orbs: +# android: circleci/android@3.0.2 +# +# # Define a job to be invoked later in a workflow. +# # See: https://circleci.com/docs/2.0/configuration-reference/#jobs +# jobs: +# all-unit-tests: +# executor: +# name: android/android_docker +# resource_class: large +# tag: 2024.07.1-ndk +# steps: +# - checkout +# - run: +# name: "litewallet-android unit test setup..." +# command: "echo Building tests for litewallet-android! && ls" +# fetch_submodules: &fetch_submodules +# - run: +# name: "Initialize submodules" +# command: "git submodule init && git submodule update --init --recursive" +# fetch_keystore: &fetch_keystore +# - run: +# name: Fetch keystore +# command: source .circleci/circle_env.sh && fetchKeystore +# - run: +# name: "Default for gradle.properties" +# command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" +# - run: +# name: "Export google-services.json to env" +# command: echo 'export $GOOGLE_SERVICES_JSON="$GOOGLE_SERVICES_JSON"' >> $BASH_ENV +# - run: +# name: "decode $GOOGLE_SERVICES_JSON to google-services.json" +# command: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json +# - android/restore_gradle_cache +# - run: +# name: "Execute Unit Test" +# command: ./gradlew testLitewalletDebugUnitTest +# - android/save_gradle_cache +# - run: +# name: Saving test results +# command: | +# mkdir -p ~/test-results/junit/ +# find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; +# when: always +# - store_test_results: +# path: ~/test-results +# - store_artifacts: +# path: ~/test-results/junit +# create_release_credits: &create_release_credits +# - run: +# name: Create fake credits +# command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties +# +# # Invoke jobs via workflows +# # See: https://circleci.com/docs/2.0/configuration-reference/#workflows +# workflows: +# build-and-test: +# jobs: +# - all-unit-tests \ No newline at end of file From 12bea30f911f8599262b42b2272ffc17359b8977 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:43:21 +0000 Subject: [PATCH 17/35] fix config? --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8e1364d81..5bf6dde65 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,11 +29,13 @@ build_and_sign: name: Assemble beta release command: ./gradlew assembleRelease +# Invoke jobs via workflows +# See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: version: 2 - build_and_sign: + build-and-sign: jobs: - - build_and_sign + - build_and_sign # # # Use the latest 2.1 version of CircleCI pipeline process engine. From 4e7eefb27f6f94d925dbff8a3a4ec1bbc0eb6402 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:44:23 +0000 Subject: [PATCH 18/35] remvoed tab --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5bf6dde65..586d40f01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ workflows: version: 2 build-and-sign: jobs: - - build_and_sign + - build_and_sign # # # Use the latest 2.1 version of CircleCI pipeline process engine. From 63a08e1ab62a46e7f2dbd26fbd032ab680503597 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:47:09 +0000 Subject: [PATCH 19/35] found tab --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 586d40f01..2f09267e0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ workflows: version: 2 build-and-sign: jobs: - - build_and_sign + - build_and_sign # # # Use the latest 2.1 version of CircleCI pipeline process engine. From b2eb9d6ce5209a1bc3fefbe0af2917fac536e5c2 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:51:37 +0000 Subject: [PATCH 20/35] added indent --- .circleci/config.yml | 62 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2f09267e0..1eeb7bff5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,33 +1,35 @@ -android_docker: &android_docker - docker: - - image: circleci/android:api-27-node8-alpha -fetch_submodules: &fetch_submodules - run: - name: Update submodules - command: git submodule sync && git submodule update --init -create_release_credits: &create_release_credits - run: - name: Create fake credits - command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties -gradle_execution_rights: &gradle_execution_rights - run: - name: Setup gradlew execution rights - command: chmod +x gradlew -fetch_keystore: &fetch_keystore - run: - name: Fetch keystore - command: source .circleci/circle_env.sh && fetchKeystore -build_and_sign: - <<: *android_docker - steps: - - checkout - - *fetch_submodules - - *fetch_keystore - - *create_release_credits - - *gradle_execution_rights - - run: - name: Assemble beta release - command: ./gradlew assembleRelease +jobs: + build_and_sign: + android_docker: &android_docker + docker: + - image: circleci/android:api-27-node8-alpha + fetch_submodules: &fetch_submodules + run: + name: Update submodules + command: git submodule sync && git submodule update --init + create_release_credits: &create_release_credits + run: + name: Create fake credits + command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties + gradle_execution_rights: &gradle_execution_rights + run: + name: Setup gradlew execution rights + command: chmod +x gradlew + fetch_keystore: &fetch_keystore + run: + name: Fetch keystore + command: source .circleci/circle_env.sh && fetchKeystore + build_and_sign: + <<: *android_docker + steps: + - checkout + - *fetch_submodules + - *fetch_keystore + - *create_release_credits + - *gradle_execution_rights + - run: + name: Assemble beta release + command: ./gradlew assembleRelease # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows From 7c1101bc913dfb336345675287893c20bd59a3dd Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 19:56:49 +0000 Subject: [PATCH 21/35] wipe configs reset the signing key --- .circleci/config.yml | 32 ++++++++++++++++---------------- .gitignore | 1 + app/build.gradle | 2 -- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1eeb7bff5..66a9d7d3b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,35 +1,35 @@ jobs: build_and_sign: - android_docker: &android_docker - docker: - - image: circleci/android:api-27-node8-alpha - fetch_submodules: &fetch_submodules - run: + android_docker: &android_docker + docker: + - image: circleci/android:api-27-node8-alpha + fetch_submodules: &fetch_submodules + run: name: Update submodules command: git submodule sync && git submodule update --init - create_release_credits: &create_release_credits - run: + create_release_credits: &create_release_credits + run: name: Create fake credits command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties - gradle_execution_rights: &gradle_execution_rights - run: + gradle_execution_rights: &gradle_execution_rights + run: name: Setup gradlew execution rights command: chmod +x gradlew - fetch_keystore: &fetch_keystore - run: + fetch_keystore: &fetch_keystore + run: name: Fetch keystore command: source .circleci/circle_env.sh && fetchKeystore - build_and_sign: - <<: *android_docker - steps: + build_and_sign: + <<: *android_docker + steps: - checkout - *fetch_submodules - *fetch_keystore - *create_release_credits - *gradle_execution_rights - run: - name: Assemble beta release - command: ./gradlew assembleRelease + name: Assemble beta release + command: ./gradlew assembleRelease # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows diff --git a/.gitignore b/.gitignore index 4a1b1f202..0f6301960 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,4 @@ app/src/litewalletDebug/google-services.json androidTestResultsUserPreferences.xml .idea/deploymentTargetSelector.xml .idea/inspectionProfiles/Project_Default.xml +/app/upload-binary/* diff --git a/app/build.gradle b/app/build.gradle index 7a13fa823..41651bc7d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -74,8 +74,6 @@ android { multiDexEnabled true archivesBaseName = "${versionName}(${versionCode})" - buildConfigField "String", "INFURA_KEY", "\"set the infura key here\"" - // Similar to other properties in the defaultConfig block, // you can configure the ndk block for each product flavor // in your build configuration. From 10313505047fefa8b0defda15c4f2ec87105f8fb Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:29:14 +0000 Subject: [PATCH 22/35] Test --- .circleci/circle_env.sh | 27 +++++++++++++++++++++++++++ .circleci/config.yml | 16 ++++++++++++---- .gitmodules | 3 +++ litewallet-data | 1 + 4 files changed, 43 insertions(+), 4 deletions(-) create mode 100755 .circleci/circle_env.sh create mode 160000 litewallet-data diff --git a/.circleci/circle_env.sh b/.circleci/circle_env.sh new file mode 100755 index 000000000..759b22c3f --- /dev/null +++ b/.circleci/circle_env.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +function copyEnvironmentVariablesToGradleProperties { + DIR="$(pwd)" + GRADLE_PROPERTIES=${DIR}"/gradle.properties" + export GRADLE_PROPERTIES + echo "Gradle Properties should exist at $GRADLE_PROPERTIES" + + if [[ ! -f "$GRADLE_PROPERTIES" ]]; then + echo "Gradle Properties does not exist" + + echo "Creating Gradle Properties file..." + touch $GRADLE_PROPERTIES + + echo "Writing keys to gradle.properties..." + echo "RELEASE_KEYSTORE_FILE=$RELEASE_KEYSTORE_FILE" >> ${GRADLE_PROPERTIES} + echo "RELEASE_KEYSTORE_PASSWORD=$RELEASE_KEYSTORE_PASSWORD" >> ${GRADLE_PROPERTIES} + echo "RELEASE_KEY_ALIAS=$RELEASE_KEY_ALIAS" >> ${GRADLE_PROPERTIES} + echo "RELEASE_KEY_PASSWORD=$RELEASE_KEY_PASSWORD" >> ${GRADLE_PROPERTIES} + fi +} + + + +function fetchKeystore { + sudo gpg --passphrase ${RELEASE_KEYSTORE_ENCRYPTION_KEY} --pinentry-mode loopback -o "app/$RELEASE_KEYSTORE_FILE" -d "upload-binary/$RELEASE_KEYSTORE_FILE.gpg" +} \ No newline at end of file diff --git a/.circleci/config.yml b/.circleci/config.yml index 8bd7f3588..7dca8a53b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,12 +18,17 @@ jobs: - run: name: "litewallet-android unit test setup..." command: "echo Building tests for litewallet-android! && ls" + fetch_submodules: &fetch_submodules + - run: + name: "Initialize submodules" + command: "git submodule init && git submodule update --init --recursive" + fetch_keystore: &fetch_keystore + - run: + name: Fetch keystore + command: source .circleci/circle_env.sh && fetchKeystore - run: name: "Default for gradle.properties" command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" - - run: - name: "Initialize submodule" - command: "git submodule init && git submodule update --init --recursive" - run: name: "Export google-services.json to env" command: echo 'export $GOOGLE_SERVICES_JSON="$GOOGLE_SERVICES_JSON"' >> $BASH_ENV @@ -45,7 +50,10 @@ jobs: path: ~/test-results - store_artifacts: path: ~/test-results/junit - + create_release_credits: &create_release_credits + - run: + name: Create fake credits + command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows diff --git a/.gitmodules b/.gitmodules index 1945a5379..84bbdcbe1 100644 --- a/.gitmodules +++ b/.gitmodules @@ -4,3 +4,6 @@ [submodule "app/src/main/jni/loafwallet-core"] path = app/src/main/jni/loafwallet-core url = https://github.com/litecoin-foundation/loafwallet-core.git +[submodule "litewallet-data"] + path = litewallet-data + url = https://github.com/gruntsoftware/litewallet-data.git diff --git a/litewallet-data b/litewallet-data new file mode 160000 index 000000000..e13a9fef8 --- /dev/null +++ b/litewallet-data @@ -0,0 +1 @@ +Subproject commit e13a9fef8a190d906046b40fc02ce552aba4d1dd From efc8fd6d34b272e5e6fecd33bd89111ef16bc599 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:34:06 +0000 Subject: [PATCH 23/35] removed tabs --- .circleci/config.yml | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 7dca8a53b..732de0255 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -19,13 +19,13 @@ jobs: name: "litewallet-android unit test setup..." command: "echo Building tests for litewallet-android! && ls" fetch_submodules: &fetch_submodules - - run: - name: "Initialize submodules" - command: "git submodule init && git submodule update --init --recursive" + - run: + name: "Initialize submodules" + command: "git submodule init && git submodule update --init --recursive" fetch_keystore: &fetch_keystore - run: - name: Fetch keystore - command: source .circleci/circle_env.sh && fetchKeystore + name: Fetch keystore + command: source .circleci/circle_env.sh && fetchKeystore - run: name: "Default for gradle.properties" command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" @@ -50,14 +50,14 @@ jobs: path: ~/test-results - store_artifacts: path: ~/test-results/junit - create_release_credits: &create_release_credits - - run: - name: Create fake credits - command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties + create_release_credits: &create_release_credits + - run: + name: Create fake credits + command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: build-and-test: jobs: - - all-unit-tests + - all-unit-tests \ No newline at end of file From bd0ef60c37d8d90ca090aaa9a7082117854930b8 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:36:53 +0000 Subject: [PATCH 24/35] remvoed more tabs --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 732de0255..fa707e4fc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -41,7 +41,7 @@ jobs: command: ./gradlew testLitewalletDebugUnitTest - android/save_gradle_cache - run: - name: Save test results + name: Saving test results command: | mkdir -p ~/test-results/junit/ find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; From cb93f220c2f9faf61db1a0d3d03895176df8de2f Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:39:22 +0000 Subject: [PATCH 25/35] found the tab --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index fa707e4fc..f92d9b393 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -18,7 +18,7 @@ jobs: - run: name: "litewallet-android unit test setup..." command: "echo Building tests for litewallet-android! && ls" - fetch_submodules: &fetch_submodules + fetch_submodules: &fetch_submodules - run: name: "Initialize submodules" command: "git submodule init && git submodule update --init --recursive" From 7160a63247a3612dee35b6f6a72f11cb7ec68639 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:40:49 +0000 Subject: [PATCH 26/35] comment out test --- .circleci/config.yml | 158 +++++++++++++++++++++++++++---------------- 1 file changed, 98 insertions(+), 60 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index f92d9b393..8e1364d81 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,63 +1,101 @@ -# Use the latest 2.1 version of CircleCI pipeline process engine. -# See: https://circleci.com/docs/2.0/configuration-reference -version: 2.1 +android_docker: &android_docker + docker: + - image: circleci/android:api-27-node8-alpha +fetch_submodules: &fetch_submodules + run: + name: Update submodules + command: git submodule sync && git submodule update --init +create_release_credits: &create_release_credits + run: + name: Create fake credits + command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties +gradle_execution_rights: &gradle_execution_rights + run: + name: Setup gradlew execution rights + command: chmod +x gradlew +fetch_keystore: &fetch_keystore + run: + name: Fetch keystore + command: source .circleci/circle_env.sh && fetchKeystore +build_and_sign: + <<: *android_docker + steps: + - checkout + - *fetch_submodules + - *fetch_keystore + - *create_release_credits + - *gradle_execution_rights + - run: + name: Assemble beta release + command: ./gradlew assembleRelease -orbs: - android: circleci/android@3.0.2 - -# Define a job to be invoked later in a workflow. -# See: https://circleci.com/docs/2.0/configuration-reference/#jobs -jobs: - all-unit-tests: - executor: - name: android/android_docker - resource_class: large - tag: 2024.07.1-ndk - steps: - - checkout - - run: - name: "litewallet-android unit test setup..." - command: "echo Building tests for litewallet-android! && ls" - fetch_submodules: &fetch_submodules - - run: - name: "Initialize submodules" - command: "git submodule init && git submodule update --init --recursive" - fetch_keystore: &fetch_keystore - - run: - name: Fetch keystore - command: source .circleci/circle_env.sh && fetchKeystore - - run: - name: "Default for gradle.properties" - command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" - - run: - name: "Export google-services.json to env" - command: echo 'export $GOOGLE_SERVICES_JSON="$GOOGLE_SERVICES_JSON"' >> $BASH_ENV - - run: - name: "decode $GOOGLE_SERVICES_JSON to google-services.json" - command: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json - - android/restore_gradle_cache - - run: - name: "Execute Unit Test" - command: ./gradlew testLitewalletDebugUnitTest - - android/save_gradle_cache - - run: - name: Saving test results - command: | - mkdir -p ~/test-results/junit/ - find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; - when: always - - store_test_results: - path: ~/test-results - - store_artifacts: - path: ~/test-results/junit - create_release_credits: &create_release_credits - - run: - name: Create fake credits - command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties - -# Invoke jobs via workflows -# See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: - build-and-test: + version: 2 + build_and_sign: jobs: - - all-unit-tests \ No newline at end of file + - build_and_sign + +# +# # Use the latest 2.1 version of CircleCI pipeline process engine. +# # See: https://circleci.com/docs/2.0/configuration-reference +# version: 2.1 +# +# orbs: +# android: circleci/android@3.0.2 +# +# # Define a job to be invoked later in a workflow. +# # See: https://circleci.com/docs/2.0/configuration-reference/#jobs +# jobs: +# all-unit-tests: +# executor: +# name: android/android_docker +# resource_class: large +# tag: 2024.07.1-ndk +# steps: +# - checkout +# - run: +# name: "litewallet-android unit test setup..." +# command: "echo Building tests for litewallet-android! && ls" +# fetch_submodules: &fetch_submodules +# - run: +# name: "Initialize submodules" +# command: "git submodule init && git submodule update --init --recursive" +# fetch_keystore: &fetch_keystore +# - run: +# name: Fetch keystore +# command: source .circleci/circle_env.sh && fetchKeystore +# - run: +# name: "Default for gradle.properties" +# command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" +# - run: +# name: "Export google-services.json to env" +# command: echo 'export $GOOGLE_SERVICES_JSON="$GOOGLE_SERVICES_JSON"' >> $BASH_ENV +# - run: +# name: "decode $GOOGLE_SERVICES_JSON to google-services.json" +# command: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json +# - android/restore_gradle_cache +# - run: +# name: "Execute Unit Test" +# command: ./gradlew testLitewalletDebugUnitTest +# - android/save_gradle_cache +# - run: +# name: Saving test results +# command: | +# mkdir -p ~/test-results/junit/ +# find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; +# when: always +# - store_test_results: +# path: ~/test-results +# - store_artifacts: +# path: ~/test-results/junit +# create_release_credits: &create_release_credits +# - run: +# name: Create fake credits +# command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties +# +# # Invoke jobs via workflows +# # See: https://circleci.com/docs/2.0/configuration-reference/#workflows +# workflows: +# build-and-test: +# jobs: +# - all-unit-tests \ No newline at end of file From 6e2753a3656cb7db292e94ce6f5c37bb2dd2e50e Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:43:21 +0000 Subject: [PATCH 27/35] fix config? --- .circleci/config.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 8e1364d81..5bf6dde65 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -29,11 +29,13 @@ build_and_sign: name: Assemble beta release command: ./gradlew assembleRelease +# Invoke jobs via workflows +# See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: version: 2 - build_and_sign: + build-and-sign: jobs: - - build_and_sign + - build_and_sign # # # Use the latest 2.1 version of CircleCI pipeline process engine. From 6119928edc3e7f9bf3d8b2cd6fdf946e20d9ed51 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:44:23 +0000 Subject: [PATCH 28/35] remvoed tab --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 5bf6dde65..586d40f01 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ workflows: version: 2 build-and-sign: jobs: - - build_and_sign + - build_and_sign # # # Use the latest 2.1 version of CircleCI pipeline process engine. From 744e247111c34c5a9e41fbceca372495b1d5d31f Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:47:09 +0000 Subject: [PATCH 29/35] found tab --- .circleci/config.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 586d40f01..2f09267e0 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -35,7 +35,7 @@ workflows: version: 2 build-and-sign: jobs: - - build_and_sign + - build_and_sign # # # Use the latest 2.1 version of CircleCI pipeline process engine. From fffd5f292b33b39d1ed5a22efc93e2312b269ccf Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 20:51:37 +0000 Subject: [PATCH 30/35] added indent --- .circleci/config.yml | 62 +++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 30 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 2f09267e0..1eeb7bff5 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,33 +1,35 @@ -android_docker: &android_docker - docker: - - image: circleci/android:api-27-node8-alpha -fetch_submodules: &fetch_submodules - run: - name: Update submodules - command: git submodule sync && git submodule update --init -create_release_credits: &create_release_credits - run: - name: Create fake credits - command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties -gradle_execution_rights: &gradle_execution_rights - run: - name: Setup gradlew execution rights - command: chmod +x gradlew -fetch_keystore: &fetch_keystore - run: - name: Fetch keystore - command: source .circleci/circle_env.sh && fetchKeystore -build_and_sign: - <<: *android_docker - steps: - - checkout - - *fetch_submodules - - *fetch_keystore - - *create_release_credits - - *gradle_execution_rights - - run: - name: Assemble beta release - command: ./gradlew assembleRelease +jobs: + build_and_sign: + android_docker: &android_docker + docker: + - image: circleci/android:api-27-node8-alpha + fetch_submodules: &fetch_submodules + run: + name: Update submodules + command: git submodule sync && git submodule update --init + create_release_credits: &create_release_credits + run: + name: Create fake credits + command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties + gradle_execution_rights: &gradle_execution_rights + run: + name: Setup gradlew execution rights + command: chmod +x gradlew + fetch_keystore: &fetch_keystore + run: + name: Fetch keystore + command: source .circleci/circle_env.sh && fetchKeystore + build_and_sign: + <<: *android_docker + steps: + - checkout + - *fetch_submodules + - *fetch_keystore + - *create_release_credits + - *gradle_execution_rights + - run: + name: Assemble beta release + command: ./gradlew assembleRelease # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows From 225799bc2810a052c3529a8d51e2fcf44c45e904 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 19:56:49 +0000 Subject: [PATCH 31/35] wipe configs reset the signing key --- .circleci/config.yml | 32 ++++++++++++++++---------------- .gitignore | 1 + app/build.gradle | 2 -- 3 files changed, 17 insertions(+), 18 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 1eeb7bff5..66a9d7d3b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,35 +1,35 @@ jobs: build_and_sign: - android_docker: &android_docker - docker: - - image: circleci/android:api-27-node8-alpha - fetch_submodules: &fetch_submodules - run: + android_docker: &android_docker + docker: + - image: circleci/android:api-27-node8-alpha + fetch_submodules: &fetch_submodules + run: name: Update submodules command: git submodule sync && git submodule update --init - create_release_credits: &create_release_credits - run: + create_release_credits: &create_release_credits + run: name: Create fake credits command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties - gradle_execution_rights: &gradle_execution_rights - run: + gradle_execution_rights: &gradle_execution_rights + run: name: Setup gradlew execution rights command: chmod +x gradlew - fetch_keystore: &fetch_keystore - run: + fetch_keystore: &fetch_keystore + run: name: Fetch keystore command: source .circleci/circle_env.sh && fetchKeystore - build_and_sign: - <<: *android_docker - steps: + build_and_sign: + <<: *android_docker + steps: - checkout - *fetch_submodules - *fetch_keystore - *create_release_credits - *gradle_execution_rights - run: - name: Assemble beta release - command: ./gradlew assembleRelease + name: Assemble beta release + command: ./gradlew assembleRelease # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows diff --git a/.gitignore b/.gitignore index 4a1b1f202..0f6301960 100644 --- a/.gitignore +++ b/.gitignore @@ -104,3 +104,4 @@ app/src/litewalletDebug/google-services.json androidTestResultsUserPreferences.xml .idea/deploymentTargetSelector.xml .idea/inspectionProfiles/Project_Default.xml +/app/upload-binary/* diff --git a/app/build.gradle b/app/build.gradle index 11696e78e..34c47f4eb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -74,8 +74,6 @@ android { multiDexEnabled true archivesBaseName = "${versionName}(${versionCode})" - buildConfigField "String", "INFURA_KEY", "\"set the infura key here\"" - // Similar to other properties in the defaultConfig block, // you can configure the ndk block for each product flavor // in your build configuration. From ed1e34ec7311d85b19c8b59a90ae1daa42d31c6e Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sat, 21 Dec 2024 23:55:10 +0000 Subject: [PATCH 32/35] code bump --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 34c47f4eb..41651bc7d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -69,7 +69,7 @@ android { applicationId = 'com.loafwallet' minSdkVersion 29 targetSdkVersion 34 - versionCode 20241221 + versionCode 20241222 versionName "v2.14.0" multiDexEnabled true archivesBaseName = "${versionName}(${versionCode})" From d8bacf2ade7ecf8898f66f027764e414c99b0209 Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 22:15:07 +0000 Subject: [PATCH 33/35] Restored config --- .circleci/config.yml | 148 ++++++++++++++----------------------------- 1 file changed, 49 insertions(+), 99 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index 66a9d7d3b..d8a282950 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,105 +1,55 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/2.0/configuration-reference +version: 2.1 + +orbs: + android: circleci/android@3.0.2 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: - build_and_sign: - android_docker: &android_docker - docker: - - image: circleci/android:api-27-node8-alpha - fetch_submodules: &fetch_submodules - run: - name: Update submodules - command: git submodule sync && git submodule update --init - create_release_credits: &create_release_credits - run: - name: Create fake credits - command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties - gradle_execution_rights: &gradle_execution_rights - run: - name: Setup gradlew execution rights - command: chmod +x gradlew - fetch_keystore: &fetch_keystore - run: - name: Fetch keystore - command: source .circleci/circle_env.sh && fetchKeystore - build_and_sign: - <<: *android_docker - steps: - - checkout - - *fetch_submodules - - *fetch_keystore - - *create_release_credits - - *gradle_execution_rights - - run: - name: Assemble beta release - command: ./gradlew assembleRelease + unit-test: + executor: + name: android/android_docker + resource_class: large + tag: 2024.07.1-ndk + steps: + - checkout + - run: + name: "litewallet-android unit test setup..." + command: "echo Building tests for litewallet-android! && ls" + - run: + name: "Default for gradle.properties" + command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" + - run: + name: "Initialize submodule" + command: "git submodule init && git submodule update --init --recursive" + - run: + name: "Export google-services.json to env" + command: echo 'export $GOOGLE_SERVICES_JSON="$GOOGLE_SERVICES_JSON"' >> $BASH_ENV + - run: + name: "decode $GOOGLE_SERVICES_JSON to google-services.json" + command: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json + - android/restore_gradle_cache + - run: + name: "Execute Unit Test" + command: ./gradlew testLitewalletDebugUnitTest + - android/save_gradle_cache + - run: + name: Save test results + command: | + mkdir -p ~/test-results/junit/ + find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; + when: always + - store_test_results: + path: ~/test-results + - store_artifacts: + path: ~/test-results/junit + # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: - version: 2 - build-and-sign: + test-and-build: jobs: - - build_and_sign - -# -# # Use the latest 2.1 version of CircleCI pipeline process engine. -# # See: https://circleci.com/docs/2.0/configuration-reference -# version: 2.1 -# -# orbs: -# android: circleci/android@3.0.2 -# -# # Define a job to be invoked later in a workflow. -# # See: https://circleci.com/docs/2.0/configuration-reference/#jobs -# jobs: -# all-unit-tests: -# executor: -# name: android/android_docker -# resource_class: large -# tag: 2024.07.1-ndk -# steps: -# - checkout -# - run: -# name: "litewallet-android unit test setup..." -# command: "echo Building tests for litewallet-android! && ls" -# fetch_submodules: &fetch_submodules -# - run: -# name: "Initialize submodules" -# command: "git submodule init && git submodule update --init --recursive" -# fetch_keystore: &fetch_keystore -# - run: -# name: Fetch keystore -# command: source .circleci/circle_env.sh && fetchKeystore -# - run: -# name: "Default for gradle.properties" -# command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" -# - run: -# name: "Export google-services.json to env" -# command: echo 'export $GOOGLE_SERVICES_JSON="$GOOGLE_SERVICES_JSON"' >> $BASH_ENV -# - run: -# name: "decode $GOOGLE_SERVICES_JSON to google-services.json" -# command: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json -# - android/restore_gradle_cache -# - run: -# name: "Execute Unit Test" -# command: ./gradlew testLitewalletDebugUnitTest -# - android/save_gradle_cache -# - run: -# name: Saving test results -# command: | -# mkdir -p ~/test-results/junit/ -# find . -type f -regex ".*/build/test-results/.*xml" -exec cp {} ~/test-results/junit/ \; -# when: always -# - store_test_results: -# path: ~/test-results -# - store_artifacts: -# path: ~/test-results/junit -# create_release_credits: &create_release_credits -# - run: -# name: Create fake credits -# command: source .circleci/circle_env.sh && copyEnvironmentVariablesToGradleProperties -# -# # Invoke jobs via workflows -# # See: https://circleci.com/docs/2.0/configuration-reference/#workflows -# workflows: -# build-and-test: -# jobs: -# - all-unit-tests \ No newline at end of file + - unit-test From 8ca741b6e50eaa0be2eb3ad475b9bd42b16a471b Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 22:37:10 +0000 Subject: [PATCH 34/35] fixed config --- .circleci/config.yml | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/.circleci/config.yml b/.circleci/config.yml index a96e18439..ac6915d3c 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -1,5 +1,14 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/2.0/configuration-reference +version: 2.1 + +orbs: + android: circleci/android@3.0.2 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: - unit-test: + all-unit-tests: executor: name: android/android_docker resource_class: large @@ -9,12 +18,12 @@ jobs: - run: name: "litewallet-android unit test setup..." command: "echo Building tests for litewallet-android! && ls" + - run: + name: "Initialize submodules" + command: "git submodule init && git submodule update --init --recursive" - run: name: "Default for gradle.properties" command: "echo \"RELEASE_STORE_FILE=/\nRELEASE_STORE_PASSWORD=\nRELEASE_KEY_ALIAS=\nRELEASE_KEY_PASSWORD=\nandroid.useAndroidX=true\nandroid.enableJetifier=true\" >> gradle.properties && ls && cat gradle.properties" - - run: - name: "Initialize submodule" - command: "git submodule init && git submodule update --init --recursive" - run: name: "Export google-services.json to env" command: echo 'export $GOOGLE_SERVICES_JSON="$GOOGLE_SERVICES_JSON"' >> $BASH_ENV @@ -23,7 +32,7 @@ jobs: command: echo "$GOOGLE_SERVICES_JSON" | base64 --decode > app/google-services.json - android/restore_gradle_cache - run: - name: "Execute Unit Test" + name: "Execute Unit Test" command: ./gradlew testLitewalletDebugUnitTest - android/save_gradle_cache - run: @@ -37,10 +46,9 @@ jobs: - store_artifacts: path: ~/test-results/junit - # Invoke jobs via workflows # See: https://circleci.com/docs/2.0/configuration-reference/#workflows workflows: test-and-build: jobs: - - unit-test + - all-unit-tests From daf9a233b40acea71be9d9e7570b58aa550c930b Mon Sep 17 00:00:00 2001 From: Kerry Washington Date: Sun, 22 Dec 2024 22:50:20 +0000 Subject: [PATCH 35/35] removed lwdata --- litewallet-data | 1 - 1 file changed, 1 deletion(-) delete mode 160000 litewallet-data diff --git a/litewallet-data b/litewallet-data deleted file mode 160000 index e13a9fef8..000000000 --- a/litewallet-data +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e13a9fef8a190d906046b40fc02ce552aba4d1dd