PassCodeActivity.java 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537
  1. /**
  2. * ownCloud Android client application
  3. *
  4. * @author Bartek Przybylski
  5. * @author masensio
  6. * @author David A. Velasco
  7. * Copyright (C) 2011 Bartek Przybylski
  8. * Copyright (C) 2015 ownCloud Inc.
  9. *
  10. * This program is free software: you can redistribute it and/or modify
  11. * it under the terms of the GNU General Public License version 2,
  12. * as published by the Free Software Foundation.
  13. *
  14. * This program is distributed in the hope that it will be useful,
  15. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  17. * GNU General Public License for more details.
  18. *
  19. * You should have received a copy of the GNU General Public License
  20. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  21. *
  22. */
  23. package com.owncloud.android.ui.activity;
  24. import java.util.Arrays;
  25. import android.content.Intent;
  26. import android.content.SharedPreferences;
  27. import android.os.Bundle;
  28. import android.preference.PreferenceManager;
  29. import android.support.v7.app.AppCompatActivity;
  30. import android.text.Editable;
  31. import android.text.TextWatcher;
  32. import android.view.KeyEvent;
  33. import android.view.View;
  34. import android.view.View.OnClickListener;
  35. import android.widget.Button;
  36. import android.widget.EditText;
  37. import android.widget.TextView;
  38. import android.widget.Toast;
  39. import com.owncloud.android.R;
  40. import com.owncloud.android.lib.common.utils.Log_OC;
  41. import java.util.Arrays;
  42. public class PassCodeActivity extends AppCompatActivity {
  43. private static final String TAG = PassCodeActivity.class.getSimpleName();
  44. public final static String ACTION_REQUEST_WITH_RESULT = "ACTION_REQUEST_WITH_RESULT";
  45. public final static String ACTION_CHECK_WITH_RESULT = "ACTION_CHECK_WITH_RESULT";
  46. public final static String ACTION_CHECK = "ACTION_CHECK";
  47. public final static String KEY_PASSCODE = "KEY_PASSCODE";
  48. public final static String KEY_CHECK_RESULT = "KEY_CHECK_RESULT";
  49. // NOTE: PREFERENCE_SET_PASSCODE must have the same value as preferences.xml-->android:key for passcode preference
  50. public final static String PREFERENCE_SET_PASSCODE = "set_pincode";
  51. public final static String PREFERENCE_PASSCODE_D1 = "PrefPinCode1";
  52. public final static String PREFERENCE_PASSCODE_D2 = "PrefPinCode2";
  53. public final static String PREFERENCE_PASSCODE_D3 = "PrefPinCode3";
  54. public final static String PREFERENCE_PASSCODE_D4 = "PrefPinCode4";
  55. private Button mBCancel;
  56. private TextView mPassCodeHdr;
  57. private TextView mPassCodeHdrExplanation;
  58. private EditText[] mPassCodeEditTexts = new EditText[4];
  59. private String [] mPassCodeDigits = {"","","",""};
  60. private static String KEY_PASSCODE_DIGITS = "PASSCODE_DIGITS";
  61. private boolean mConfirmingPassCode = false;
  62. private static String KEY_CONFIRMING_PASSCODE = "CONFIRMING_PASSCODE";
  63. private boolean mBChange = true; // to control that only one blocks jump
  64. /**
  65. * Initializes the activity.
  66. *
  67. * An intent with a valid ACTION is expected; if none is found, an
  68. * {@link IllegalArgumentException} will be thrown.
  69. *
  70. * @param savedInstanceState Previously saved state - irrelevant in this case
  71. */
  72. protected void onCreate(Bundle savedInstanceState) {
  73. super.onCreate(savedInstanceState);
  74. setContentView(R.layout.passcodelock);
  75. mBCancel = (Button) findViewById(R.id.cancel);
  76. mPassCodeHdr = (TextView) findViewById(R.id.header);
  77. mPassCodeHdrExplanation = (TextView) findViewById(R.id.explanation);
  78. mPassCodeEditTexts[0] = (EditText) findViewById(R.id.txt0);
  79. mPassCodeEditTexts[0].requestFocus();
  80. getWindow().setSoftInputMode(
  81. android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
  82. mPassCodeEditTexts[1] = (EditText) findViewById(R.id.txt1);
  83. mPassCodeEditTexts[2] = (EditText) findViewById(R.id.txt2);
  84. mPassCodeEditTexts[3] = (EditText) findViewById(R.id.txt3);
  85. if (ACTION_CHECK.equals(getIntent().getAction())) {
  86. /// this is a pass code request; the user has to input the right value
  87. mPassCodeHdr.setText(R.string.pass_code_enter_pass_code);
  88. mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
  89. setCancelButtonEnabled(false); // no option to cancel
  90. } else if (ACTION_REQUEST_WITH_RESULT.equals(getIntent().getAction())) {
  91. if (savedInstanceState != null) {
  92. mConfirmingPassCode = savedInstanceState.getBoolean(PassCodeActivity.KEY_CONFIRMING_PASSCODE);
  93. mPassCodeDigits = savedInstanceState.getStringArray(PassCodeActivity.KEY_PASSCODE_DIGITS);
  94. }
  95. if(mConfirmingPassCode){
  96. //the app was in the passcodeconfirmation
  97. requestPassCodeConfirmation();
  98. }else{
  99. /// pass code preference has just been activated in Preferences;
  100. // will receive and confirm pass code value
  101. mPassCodeHdr.setText(R.string.pass_code_configure_your_pass_code);
  102. //mPassCodeHdr.setText(R.string.pass_code_enter_pass_code);
  103. // TODO choose a header, check iOS
  104. mPassCodeHdrExplanation.setVisibility(View.VISIBLE);
  105. setCancelButtonEnabled(true);
  106. }
  107. } else if (ACTION_CHECK_WITH_RESULT.equals(getIntent().getAction())) {
  108. /// pass code preference has just been disabled in Preferences;
  109. // will confirm user knows pass code, then remove it
  110. mPassCodeHdr.setText(R.string.pass_code_remove_your_pass_code);
  111. mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
  112. setCancelButtonEnabled(true);
  113. } else {
  114. throw new IllegalArgumentException("A valid ACTION is needed in the Intent passed to "
  115. + TAG);
  116. }
  117. setTextListeners();
  118. }
  119. /**
  120. * Enables or disables the cancel button to allow the user interrupt the ACTION
  121. * requested to the activity.
  122. *
  123. * @param enabled 'True' makes the cancel button available, 'false' hides it.
  124. */
  125. protected void setCancelButtonEnabled(boolean enabled){
  126. if(enabled){
  127. mBCancel.setVisibility(View.VISIBLE);
  128. mBCancel.setOnClickListener(new OnClickListener() {
  129. @Override
  130. public void onClick(View v) {
  131. revertActionAndExit();
  132. }
  133. });
  134. } else {
  135. mBCancel.setVisibility(View.GONE);
  136. mBCancel.setVisibility(View.INVISIBLE);
  137. mBCancel.setOnClickListener(null);
  138. }
  139. }
  140. /**
  141. * Binds the appropiate listeners to the input boxes receiving each digit of the pass code.
  142. */
  143. protected void setTextListeners() {
  144. /// First input field
  145. mPassCodeEditTexts[0].addTextChangedListener(new PassCodeDigitTextWatcher(0, false));
  146. /*------------------------------------------------
  147. * SECOND BOX
  148. -------------------------------------------------*/
  149. mPassCodeEditTexts[1].addTextChangedListener(new PassCodeDigitTextWatcher(1, false));
  150. mPassCodeEditTexts[1].setOnKeyListener(new View.OnKeyListener() {
  151. @Override
  152. public boolean onKey(View v, int keyCode, KeyEvent event) {
  153. if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) { // TODO WIP: event should be
  154. // used to control what's exactly happening with DEL, not any custom field...
  155. mPassCodeEditTexts[0].setText("");
  156. mPassCodeEditTexts[0].requestFocus();
  157. if (!mConfirmingPassCode)
  158. mPassCodeDigits[0] = "";
  159. mBChange = false;
  160. } else if (!mBChange) {
  161. mBChange = true;
  162. }
  163. return false;
  164. }
  165. });
  166. mPassCodeEditTexts[1].setOnFocusChangeListener(new View.OnFocusChangeListener() {
  167. @Override
  168. public void onFocusChange(View v, boolean hasFocus) {
  169. /// TODO WIP: should take advantage of hasFocus to reduce processing
  170. if (mPassCodeEditTexts[0].getText().toString().equals("")) { // TODO WIP validation
  171. // could be done in a global way, with a single OnFocusChangeListener for all the
  172. // input fields
  173. mPassCodeEditTexts[0].requestFocus();
  174. }
  175. }
  176. });
  177. /*------------------------------------------------
  178. * THIRD BOX
  179. -------------------------------------------------*/
  180. mPassCodeEditTexts[2].addTextChangedListener(new PassCodeDigitTextWatcher(2, false));
  181. mPassCodeEditTexts[2].setOnKeyListener(new View.OnKeyListener() {
  182. @Override
  183. public boolean onKey(View v, int keyCode, KeyEvent event) {
  184. if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) {
  185. mPassCodeEditTexts[1].requestFocus();
  186. if (!mConfirmingPassCode)
  187. mPassCodeDigits[1] = "";
  188. mPassCodeEditTexts[1].setText("");
  189. mBChange = false;
  190. } else if (!mBChange) {
  191. mBChange = true;
  192. }
  193. return false;
  194. }
  195. });
  196. mPassCodeEditTexts[2].setOnFocusChangeListener(new View.OnFocusChangeListener() {
  197. @Override
  198. public void onFocusChange(View v, boolean hasFocus) {
  199. if (mPassCodeEditTexts[0].getText().toString().equals("")) {
  200. mPassCodeEditTexts[0].requestFocus();
  201. } else if (mPassCodeEditTexts[1].getText().toString().equals("")) {
  202. mPassCodeEditTexts[1].requestFocus();
  203. }
  204. }
  205. });
  206. /*------------------------------------------------
  207. * FOURTH BOX
  208. -------------------------------------------------*/
  209. mPassCodeEditTexts[3].addTextChangedListener(new PassCodeDigitTextWatcher(3, true));
  210. mPassCodeEditTexts[3].setOnKeyListener(new View.OnKeyListener() {
  211. @Override
  212. public boolean onKey(View v, int keyCode, KeyEvent event) {
  213. if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) {
  214. mPassCodeEditTexts[2].requestFocus();
  215. if (!mConfirmingPassCode)
  216. mPassCodeDigits[2] = "";
  217. mPassCodeEditTexts[2].setText("");
  218. mBChange = false;
  219. } else if (!mBChange) {
  220. mBChange = true;
  221. }
  222. return false;
  223. }
  224. });
  225. mPassCodeEditTexts[3].setOnFocusChangeListener(new View.OnFocusChangeListener() {
  226. @Override
  227. public void onFocusChange(View v, boolean hasFocus) {
  228. if (mPassCodeEditTexts[0].getText().toString().equals("")) {
  229. mPassCodeEditTexts[0].requestFocus();
  230. } else if (mPassCodeEditTexts[1].getText().toString().equals("")) {
  231. mPassCodeEditTexts[1].requestFocus();
  232. } else if (mPassCodeEditTexts[2].getText().toString().equals("")) {
  233. mPassCodeEditTexts[2].requestFocus();
  234. }
  235. }
  236. });
  237. } // end setTextListener
  238. /**
  239. * Processes the pass code entered by the user just after the last digit was in.
  240. *
  241. * Takes into account the action requested to the activity, the currently saved pass code and
  242. * the previously typed pass code, if any.
  243. */
  244. private void processFullPassCode() {
  245. if (ACTION_CHECK.equals(getIntent().getAction())) {
  246. if (checkPassCode()) {
  247. /// pass code accepted in request, user is allowed to access the app
  248. finish();
  249. } else {
  250. showErrorAndRestart(R.string.pass_code_wrong, R.string.pass_code_enter_pass_code,
  251. View.INVISIBLE);
  252. }
  253. } else if (ACTION_CHECK_WITH_RESULT.equals(getIntent().getAction())) {
  254. if (checkPassCode()) {
  255. Intent resultIntent = new Intent();
  256. resultIntent.putExtra(KEY_CHECK_RESULT, true);
  257. setResult(RESULT_OK, resultIntent);
  258. finish();
  259. } else {
  260. showErrorAndRestart(R.string.pass_code_wrong, R.string.pass_code_enter_pass_code,
  261. View.INVISIBLE);
  262. }
  263. } else if (ACTION_REQUEST_WITH_RESULT.equals(getIntent().getAction())) {
  264. /// enabling pass code
  265. if (!mConfirmingPassCode) {
  266. requestPassCodeConfirmation();
  267. } else if (confirmPassCode()) {
  268. /// confirmed: user typed the same pass code twice
  269. savePassCodeAndExit();
  270. } else {
  271. showErrorAndRestart(
  272. R.string.pass_code_mismatch, R.string.pass_code_configure_your_pass_code, View.VISIBLE
  273. );
  274. }
  275. }
  276. }
  277. private void showErrorAndRestart(int errorMessage, int headerMessage,
  278. int explanationVisibility) {
  279. Arrays.fill(mPassCodeDigits, null);
  280. CharSequence errorSeq = getString(errorMessage);
  281. Toast.makeText(this, errorSeq, Toast.LENGTH_LONG).show();
  282. mPassCodeHdr.setText(headerMessage); // TODO check if really needed
  283. mPassCodeHdrExplanation.setVisibility(explanationVisibility); // TODO check if really needed
  284. clearBoxes();
  285. }
  286. /**
  287. * Ask to the user for retyping the pass code just entered before saving it as the current pass
  288. * code.
  289. */
  290. protected void requestPassCodeConfirmation(){
  291. clearBoxes();
  292. mPassCodeHdr.setText(R.string.pass_code_reenter_your_pass_code);
  293. mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
  294. mConfirmingPassCode = true;
  295. }
  296. /**
  297. * Compares pass code entered by the user with the value currently saved in the app.
  298. *
  299. * @return 'True' if entered pass code equals to the saved one.
  300. */
  301. protected boolean checkPassCode(){
  302. SharedPreferences appPrefs = PreferenceManager
  303. .getDefaultSharedPreferences(getApplicationContext());
  304. String savedPassCodeDigits[] = new String[4];
  305. savedPassCodeDigits[0] = appPrefs.getString(PREFERENCE_PASSCODE_D1, null);
  306. savedPassCodeDigits[1] = appPrefs.getString(PREFERENCE_PASSCODE_D2, null);
  307. savedPassCodeDigits[2] = appPrefs.getString(PREFERENCE_PASSCODE_D3, null);
  308. savedPassCodeDigits[3] = appPrefs.getString(PREFERENCE_PASSCODE_D4, null);
  309. boolean result = true;
  310. for (int i = 0; i < mPassCodeDigits.length && result; i++) {
  311. result = result && (mPassCodeDigits[i] != null) &&
  312. mPassCodeDigits[i].equals(savedPassCodeDigits[i]);
  313. }
  314. return result;
  315. }
  316. /**
  317. * Compares pass code retyped by the user in the input fields with the value entered just
  318. * before.
  319. *
  320. * @return 'True' if retyped pass code equals to the entered before.
  321. */
  322. protected boolean confirmPassCode(){
  323. mConfirmingPassCode = false;
  324. boolean result = true;
  325. for (int i = 0; i < mPassCodeEditTexts.length && result; i++) {
  326. result = result &&
  327. ((mPassCodeEditTexts[i].getText().toString()).equals(mPassCodeDigits[i]));
  328. }
  329. return result;
  330. }
  331. /**
  332. * Sets the input fields to empty strings and puts the focus on the first one.
  333. */
  334. protected void clearBoxes(){
  335. for (int i=0; i < mPassCodeEditTexts.length; i++) {
  336. mPassCodeEditTexts[i].setText("");
  337. }
  338. mPassCodeEditTexts[0].requestFocus();
  339. }
  340. /**
  341. * Overrides click on the BACK arrow to correctly cancel ACTION_ENABLE or ACTION_DISABLE, while
  342. * preventing than ACTION_CHECK may be worked around.
  343. *
  344. * @param keyCode Key code of the key that triggered the down event.
  345. * @param event Event triggered.
  346. * @return 'True' when the key event was processed by this method.
  347. */
  348. @Override
  349. public boolean onKeyDown(int keyCode, KeyEvent event){
  350. if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount()== 0){
  351. if (ACTION_REQUEST_WITH_RESULT.equals(getIntent().getAction()) ||
  352. ACTION_CHECK_WITH_RESULT.equals(getIntent().getAction())) {
  353. revertActionAndExit();
  354. }
  355. return true;
  356. }
  357. return super.onKeyDown(keyCode, event);
  358. }
  359. /**
  360. * Saves the pass code input by the user as the current pass code.
  361. */
  362. protected void savePassCodeAndExit() {
  363. Intent resultIntent = new Intent();
  364. resultIntent.putExtra(KEY_PASSCODE,
  365. mPassCodeDigits[0] + mPassCodeDigits[1] + mPassCodeDigits[2] + mPassCodeDigits[3]);
  366. setResult(RESULT_OK, resultIntent);
  367. finish();
  368. }
  369. /**
  370. * Cancellation of ACTION_ENABLE or ACTION_DISABLE; reverts the enable or disable action done by
  371. * {@link Preferences}, then finishes.
  372. */
  373. protected void revertActionAndExit() {
  374. SharedPreferences.Editor appPrefsE = PreferenceManager
  375. .getDefaultSharedPreferences(getApplicationContext()).edit();
  376. SharedPreferences appPrefs = PreferenceManager
  377. .getDefaultSharedPreferences(getApplicationContext());
  378. boolean state = appPrefs.getBoolean(PREFERENCE_SET_PASSCODE, false);
  379. appPrefsE.putBoolean(PREFERENCE_SET_PASSCODE, !state);
  380. // TODO WIP: this is reverting the value of the preference because it was changed BEFORE
  381. // entering
  382. // TODO in this activity; was the PreferenceCheckBox in the caller who did it
  383. appPrefsE.commit();
  384. finish();
  385. }
  386. @Override
  387. public void onSaveInstanceState(Bundle outState) {
  388. super.onSaveInstanceState(outState);
  389. outState.putBoolean(PassCodeActivity.KEY_CONFIRMING_PASSCODE, mConfirmingPassCode);
  390. outState.putStringArray(PassCodeActivity.KEY_PASSCODE_DIGITS, mPassCodeDigits);
  391. }
  392. private class PassCodeDigitTextWatcher implements TextWatcher {
  393. private int mIndex = -1;
  394. private boolean mLastOne = false;
  395. /**
  396. * Constructor
  397. *
  398. * @param index Position in the pass code of the input field that will be bound to
  399. * this watcher.
  400. * @param lastOne 'True' means that watcher corresponds to the last position of the
  401. * pass code.
  402. */
  403. public PassCodeDigitTextWatcher(int index, boolean lastOne) {
  404. mIndex = index;
  405. mLastOne = lastOne;
  406. if (mIndex < 0) {
  407. throw new IllegalArgumentException(
  408. "Invalid index in " + PassCodeDigitTextWatcher.class.getSimpleName() +
  409. " constructor"
  410. );
  411. }
  412. }
  413. private int next() {
  414. return mLastOne ? 0 : mIndex + 1;
  415. }
  416. /**
  417. * Performs several actions when the user types a digit in an input field:
  418. * - saves the input digit to the state of the activity; this will allow retyping the
  419. * pass code to confirm it.
  420. * - moves the focus automatically to the next field
  421. * - for the last field, triggers the processing of the full pass code
  422. *
  423. * @param s
  424. */
  425. @Override
  426. public void afterTextChanged(Editable s) {
  427. if (s.length() > 0) {
  428. if (!mConfirmingPassCode) {
  429. mPassCodeDigits[mIndex] = mPassCodeEditTexts[mIndex].getText().toString();
  430. }
  431. mPassCodeEditTexts[next()].requestFocus();
  432. if (mLastOne) {
  433. processFullPassCode();
  434. }
  435. } else {
  436. Log_OC.d(TAG, "Text box " + mIndex + " was cleaned");
  437. }
  438. }
  439. @Override
  440. public void beforeTextChanged(CharSequence s, int start, int count, int after) {
  441. // nothing to do
  442. }
  443. @Override
  444. public void onTextChanged(CharSequence s, int start, int before, int count) {
  445. // nothing to do
  446. }
  447. }
  448. }