PageRenderTime 102ms CodeModel.GetById 5ms RepoModel.GetById 0ms app.codeStats 1ms

/app/src/main/java/info/blockchain/wallet/viewModel/PinEntryViewModel.java

https://gitlab.com/github-cloud-corporation/My-Wallet-V3-Android
Java | 385 lines | 300 code | 67 blank | 18 comment | 49 complexity | 4d36b2222142e50b71ae7a3f1a78e706 MD5 | raw file
  1. package info.blockchain.wallet.viewModel;
  2. import android.content.Intent;
  3. import android.os.Bundle;
  4. import android.support.annotation.NonNull;
  5. import android.support.annotation.Nullable;
  6. import android.support.annotation.StringRes;
  7. import android.support.annotation.UiThread;
  8. import android.support.annotation.VisibleForTesting;
  9. import android.view.View;
  10. import android.widget.TextView;
  11. import info.blockchain.wallet.callbacks.DialogButtonCallback;
  12. import info.blockchain.wallet.datamanagers.AuthDataManager;
  13. import info.blockchain.wallet.payload.PayloadManager;
  14. import info.blockchain.wallet.util.AppUtil;
  15. import info.blockchain.wallet.util.CharSequenceX;
  16. import info.blockchain.wallet.util.PrefsUtil;
  17. import info.blockchain.wallet.util.StringUtils;
  18. import info.blockchain.wallet.util.ViewUtils;
  19. import info.blockchain.wallet.view.helpers.ToastCustom;
  20. import java.util.ArrayList;
  21. import java.util.List;
  22. import javax.inject.Inject;
  23. import piuk.blockchain.android.R;
  24. import piuk.blockchain.android.di.Injector;
  25. import rx.exceptions.Exceptions;
  26. import rx.subscriptions.CompositeSubscription;
  27. import static info.blockchain.wallet.view.CreateWalletFragment.KEY_INTENT_EMAIL;
  28. import static info.blockchain.wallet.view.CreateWalletFragment.KEY_INTENT_PASSWORD;
  29. public class PinEntryViewModel implements ViewModel {
  30. private static final int PIN_LENGTH = 4;
  31. private static final int MAX_ATTEMPTS = 4;
  32. private DataListener mDataListener;
  33. @Inject protected AuthDataManager mAuthDataManager;
  34. @Inject protected AppUtil mAppUtil;
  35. @Inject protected PrefsUtil mPrefsUtil;
  36. @Inject protected PayloadManager mPayloadManager;
  37. @Inject protected StringUtils mStringUtils;
  38. @VisibleForTesting CompositeSubscription mCompositeSubscription;
  39. private String email;
  40. private CharSequenceX password;
  41. @VisibleForTesting String mUserEnteredPin = "";
  42. @VisibleForTesting String mUserEnteredConfirmationPin;
  43. @VisibleForTesting boolean bAllowExit = true;
  44. public interface DataListener {
  45. Intent getPageIntent();
  46. TextView[] getPinBoxArray();
  47. void showProgressDialog(@StringRes int messageId, @Nullable String suffix);
  48. void showToast(@StringRes int message, @ToastCustom.ToastType String toastType);
  49. void dismissProgressDialog();
  50. void showMaxAttemptsDialog();
  51. void showValidationDialog();
  52. void showCommonPinWarning(DialogButtonCallback callback);
  53. void showWalletVersionNotSupportedDialog(int walletVersion);
  54. void goToUpgradeWalletActivity();
  55. void restartPage();
  56. void setTitleString(@StringRes int title);
  57. void setTitleVisibility(@ViewUtils.Visibility int visibility);
  58. void clearPinBoxes();
  59. }
  60. public PinEntryViewModel(DataListener listener) {
  61. Injector.getInstance().getAppComponent().inject(this);
  62. mDataListener = listener;
  63. mCompositeSubscription = new CompositeSubscription();
  64. }
  65. public void onViewReady() {
  66. if (mDataListener.getPageIntent() != null) {
  67. Bundle extras = mDataListener.getPageIntent().getExtras();
  68. if (extras != null) {
  69. if (extras.containsKey(KEY_INTENT_EMAIL)) {
  70. email = extras.getString(KEY_INTENT_EMAIL);
  71. }
  72. if (extras.containsKey(KEY_INTENT_PASSWORD)) {
  73. //noinspection ConstantConditions
  74. password = new CharSequenceX(extras.getString(KEY_INTENT_PASSWORD));
  75. }
  76. if (password != null && password.length() > 0 && email != null && !email.isEmpty()) {
  77. // Previous page was CreateWalletFragment
  78. bAllowExit = false;
  79. saveLoginAndPassword();
  80. mDataListener.showProgressDialog(R.string.create_wallet, "...");
  81. createWallet();
  82. }
  83. }
  84. }
  85. checkPinFails();
  86. }
  87. public void onDeleteClicked() {
  88. if (mUserEnteredPin.length() > 0) {
  89. // Remove last char from pin string
  90. mUserEnteredPin = mUserEnteredPin.substring(0, mUserEnteredPin.length() - 1);
  91. // Clear last box
  92. mDataListener.getPinBoxArray()[mUserEnteredPin.length()].setBackgroundResource(R.drawable.rounded_view_blue_white_border);
  93. }
  94. }
  95. public void padClicked(View view) {
  96. if (mUserEnteredPin.length() == PIN_LENGTH) {
  97. return;
  98. }
  99. // Append tapped #
  100. mUserEnteredPin = mUserEnteredPin + view.getTag().toString().substring(0, 1);
  101. mDataListener.getPinBoxArray()[mUserEnteredPin.length() - 1].setBackgroundResource(R.drawable.rounded_view_dark_blue);
  102. // Perform appropriate action if PIN_LENGTH has been reached
  103. if (mUserEnteredPin.length() == PIN_LENGTH) {
  104. // Throw error on '0000' to avoid server-side type issue
  105. if (mUserEnteredPin.equals("0000")) {
  106. showErrorToast(R.string.zero_pin);
  107. clearPinViewAndReset();
  108. return;
  109. }
  110. // Only show warning on first entry and if user is creating a new PIN
  111. if (isCreatingNewPin() && isPinCommon(mUserEnteredPin) && mUserEnteredConfirmationPin == null) {
  112. mDataListener.showCommonPinWarning(new DialogButtonCallback() {
  113. @Override
  114. public void onPositiveClicked() {
  115. clearPinViewAndReset();
  116. }
  117. @Override
  118. public void onNegativeClicked() {
  119. validateAndConfirmPin();
  120. }
  121. });
  122. } else {
  123. validateAndConfirmPin();
  124. }
  125. }
  126. }
  127. private void validateAndConfirmPin() {
  128. // Validate
  129. if (!mPrefsUtil.getValue(PrefsUtil.KEY_PIN_IDENTIFIER, "").isEmpty()) {
  130. mDataListener.setTitleVisibility(View.INVISIBLE);
  131. validatePIN(mUserEnteredPin);
  132. } else if (mUserEnteredConfirmationPin == null) {
  133. // End of Create - Change to Confirm
  134. mUserEnteredConfirmationPin = mUserEnteredPin;
  135. mUserEnteredPin = "";
  136. mDataListener.setTitleString(R.string.confirm_pin);
  137. clearPinBoxes();
  138. } else if (mUserEnteredConfirmationPin.equals(mUserEnteredPin)) {
  139. // End of Confirm - Pin is confirmed
  140. createNewPin(mUserEnteredPin);
  141. } else {
  142. // End of Confirm - Pin Mismatch
  143. showErrorToast(R.string.pin_mismatch_error);
  144. mDataListener.setTitleString(R.string.create_pin);
  145. clearPinViewAndReset();
  146. }
  147. }
  148. private void clearPinViewAndReset() {
  149. clearPinBoxes();
  150. mUserEnteredConfirmationPin = null;
  151. }
  152. public void clearPinBoxes() {
  153. mUserEnteredPin = "";
  154. mDataListener.clearPinBoxes();
  155. }
  156. @SuppressWarnings("WeakerAccess")
  157. @VisibleForTesting
  158. void updatePayload(CharSequenceX password) {
  159. mDataListener.showProgressDialog(R.string.decrypting_wallet, null);
  160. mCompositeSubscription.add(
  161. mAuthDataManager.updatePayload(
  162. mPrefsUtil.getValue(PrefsUtil.KEY_SHARED_KEY, ""),
  163. mPrefsUtil.getValue(PrefsUtil.KEY_GUID, ""),
  164. password)
  165. .doOnTerminate(() -> mDataListener.dismissProgressDialog())
  166. .subscribe(aVoid -> {
  167. mAppUtil.setSharedKey(mPayloadManager.getPayload().getSharedKey());
  168. double walletVersion = mPayloadManager.getVersion();
  169. if (walletVersion > PayloadManager.SUPPORTED_ENCRYPTION_VERSION) {
  170. mDataListener.showWalletVersionNotSupportedDialog((int) walletVersion);
  171. } else {
  172. setAccountLabelIfNecessary();
  173. if (!mPayloadManager.getPayload().isUpgraded()) {
  174. mDataListener.goToUpgradeWalletActivity();
  175. } else {
  176. mAppUtil.restartAppWithVerifiedPin();
  177. }
  178. }
  179. }, throwable -> mAppUtil.clearCredentialsAndRestart()));
  180. }
  181. public void validatePassword(CharSequenceX password) {
  182. mDataListener.showProgressDialog(R.string.validating_password, null);
  183. mCompositeSubscription.add(
  184. mAuthDataManager.updatePayload(
  185. mPrefsUtil.getValue(PrefsUtil.KEY_SHARED_KEY, ""),
  186. mPrefsUtil.getValue(PrefsUtil.KEY_GUID, ""),
  187. password)
  188. .doOnSubscribe(() -> mPayloadManager.setTempPassword(new CharSequenceX("")))
  189. .subscribe(o -> {
  190. mDataListener.showToast(R.string.pin_4_strikes_password_accepted, ToastCustom.TYPE_OK);
  191. mPrefsUtil.removeValue(PrefsUtil.KEY_PIN_FAILS);
  192. mPrefsUtil.removeValue(PrefsUtil.KEY_PIN_IDENTIFIER);
  193. mDataListener.restartPage();
  194. mDataListener.dismissProgressDialog();
  195. }, throwable -> {
  196. showErrorToast(R.string.invalid_password);
  197. mDataListener.showValidationDialog();
  198. }));
  199. }
  200. private void createNewPin(String pin) {
  201. mDataListener.showProgressDialog(R.string.creating_pin, null);
  202. mCompositeSubscription.add(
  203. mAuthDataManager.createPin(mPayloadManager.getTempPassword(), pin)
  204. .subscribe(createSuccessful -> {
  205. mDataListener.dismissProgressDialog();
  206. if (createSuccessful) {
  207. mPrefsUtil.setValue(PrefsUtil.KEY_PIN_FAILS, 0);
  208. updatePayload(mPayloadManager.getTempPassword());
  209. } else {
  210. throw Exceptions.propagate(new Throwable("Pin create failed"));
  211. }
  212. }, throwable -> {
  213. showErrorToast(R.string.create_pin_failed);
  214. mPrefsUtil.clear();
  215. mAppUtil.restartApp();
  216. }));
  217. }
  218. private void validatePIN(String pin) {
  219. mDataListener.showProgressDialog(R.string.validating_pin, null);
  220. mAuthDataManager.validatePin(pin)
  221. .subscribe(password -> {
  222. mDataListener.dismissProgressDialog();
  223. if (password != null) {
  224. mPrefsUtil.setValue(PrefsUtil.KEY_PIN_FAILS, 0);
  225. updatePayload(password);
  226. } else {
  227. incrementFailureCount();
  228. }
  229. }, throwable -> {
  230. showErrorToast(R.string.unexpected_error);
  231. mDataListener.restartPage();
  232. });
  233. }
  234. public void incrementFailureCount() {
  235. int fails = mPrefsUtil.getValue(PrefsUtil.KEY_PIN_FAILS, 0);
  236. mPrefsUtil.setValue(PrefsUtil.KEY_PIN_FAILS, ++fails);
  237. showErrorToast(R.string.invalid_pin);
  238. mDataListener.restartPage();
  239. }
  240. // Check user's password if PIN fails >= 4
  241. private void checkPinFails() {
  242. int fails = mPrefsUtil.getValue(PrefsUtil.KEY_PIN_FAILS, 0);
  243. if (fails >= MAX_ATTEMPTS) {
  244. showErrorToast(R.string.pin_4_strikes);
  245. mPayloadManager.getPayload().stepNumber = 0;
  246. mDataListener.showMaxAttemptsDialog();
  247. }
  248. }
  249. private void saveLoginAndPassword() {
  250. mPrefsUtil.setValue(PrefsUtil.KEY_EMAIL, email);
  251. mPayloadManager.setEmail(email);
  252. mPayloadManager.setTempPassword(password);
  253. }
  254. private void setAccountLabelIfNecessary() {
  255. if (mAppUtil.isNewlyCreated()
  256. && mPayloadManager.getPayload().getHdWallet() != null
  257. && (mPayloadManager.getPayload().getHdWallet().getAccounts().get(0).getLabel() == null
  258. || mPayloadManager.getPayload().getHdWallet().getAccounts().get(0).getLabel().isEmpty())) {
  259. mPayloadManager.getPayload().getHdWallet().getAccounts().get(0).setLabel(mStringUtils.getString(R.string.default_wallet_name));
  260. }
  261. }
  262. private void createWallet() {
  263. mCompositeSubscription.add(
  264. mAuthDataManager.createHdWallet(password.toString(), mStringUtils.getString(R.string.default_wallet_name))
  265. .doAfterTerminate(() -> mDataListener.dismissProgressDialog())
  266. .subscribe(payload -> {
  267. if (payload != null) {
  268. // Successfully created and saved
  269. mPrefsUtil.setValue(PrefsUtil.KEY_GUID, payload.getGuid());
  270. mAppUtil.setSharedKey(payload.getSharedKey());
  271. } else {
  272. showErrorToast(R.string.remote_save_ko);
  273. }
  274. }, throwable -> showErrorToastAndRestartApp(R.string.hd_error)));
  275. }
  276. private boolean isPinCommon(String pin) {
  277. List<String> commonPins = new ArrayList<String>() {{
  278. add("1234");
  279. add("1111");
  280. add("1212");
  281. add("7777");
  282. add("1004");
  283. }};
  284. return commonPins.contains(pin);
  285. }
  286. public void resetApp() {
  287. mAppUtil.clearCredentialsAndRestart();
  288. }
  289. public boolean allowExit() {
  290. return bAllowExit;
  291. }
  292. public boolean isCreatingNewPin() {
  293. return mPrefsUtil.getValue(PrefsUtil.KEY_PIN_IDENTIFIER, "").isEmpty();
  294. }
  295. @UiThread
  296. private void showErrorToast(@StringRes int message) {
  297. mDataListener.dismissProgressDialog();
  298. mDataListener.showToast(message, ToastCustom.TYPE_ERROR);
  299. }
  300. @UiThread
  301. private void showErrorToastAndRestartApp(@StringRes int message) {
  302. mDataListener.dismissProgressDialog();
  303. mDataListener.showToast(message, ToastCustom.TYPE_ERROR);
  304. resetApp();
  305. }
  306. @NonNull
  307. public AppUtil getAppUtil() {
  308. return mAppUtil;
  309. }
  310. @Override
  311. public void destroy() {
  312. // Clear all subscriptions so that:
  313. // 1) all processes stop and no memory is leaked
  314. // 2) processes don't try to update a null View
  315. // 3) background processes don't leak memory
  316. mCompositeSubscription.clear();
  317. }
  318. }