Cleaned code in PassCodeActivity
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / PassCodeActivity.java
1 /**
2 * ownCloud Android client application
3 *
4 * Copyright (C) 2011 Bartek Przybylski
5 * Copyright (C) 2015 ownCloud Inc.
6 *
7 * This program is free software: you can redistribute it and/or modify
8 * it under the terms of the GNU General Public License version 2,
9 * as published by the Free Software Foundation.
10 *
11 * This program is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
15 *
16 * You should have received a copy of the GNU General Public License
17 * along with this program. If not, see <http://www.gnu.org/licenses/>.
18 *
19 */
20 package com.owncloud.android.ui.activity;
21
22 import java.util.Arrays;
23
24 import android.content.SharedPreferences;
25 import android.os.Bundle;
26 import android.preference.PreferenceManager;
27 import android.text.Editable;
28 import android.text.TextWatcher;
29 import android.view.KeyEvent;
30 import android.view.View;
31 import android.view.View.OnClickListener;
32 import android.view.View.OnFocusChangeListener;
33 import android.view.View.OnKeyListener;
34 import android.widget.Button;
35 import android.widget.EditText;
36 import android.widget.TextView;
37 import android.widget.Toast;
38
39 import com.actionbarsherlock.app.ActionBar;
40 import com.actionbarsherlock.app.SherlockFragmentActivity;
41 import com.owncloud.android.R;
42 import com.owncloud.android.lib.common.utils.Log_OC;
43 import com.owncloud.android.utils.DisplayUtils;
44
45 public class PassCodeActivity extends SherlockFragmentActivity {
46
47
48 private static final String TAG = PassCodeActivity.class.getSimpleName();
49
50 public final static String ACTION_ENABLE = PassCodeActivity.class.getCanonicalName() + ".ENABLE";
51 public final static String ACTION_DISABLE = PassCodeActivity.class.getCanonicalName() + ".DISABLE";
52 public final static String ACTION_REQUEST = PassCodeActivity.class.getCanonicalName() + ".REQUEST";
53
54 private Button mBCancel;
55 private TextView mPassCodeHdr;
56 private TextView mPassCodeHdrExplanation;
57 private EditText mText0;
58 private EditText mText1;
59 private EditText mText2;
60 private EditText mText3;
61
62 private String [] mPassCodeDigits = {"","","",""};
63 private boolean mConfirmingPassCode = false;
64
65 private boolean mBChange = true; // to control that only one blocks jump
66
67
68 /**
69 * Initializes the activity.
70 *
71 * An intent with a valid ACTION is expected; if none is found, an {@link IllegalArgumentException} will be thrown.
72 *
73 * @param savedInstanceState Previously saved state - irrelevant in this case
74 */
75 protected void onCreate(Bundle savedInstanceState) {
76 super.onCreate(savedInstanceState);
77 setContentView(R.layout.passcodelock);
78
79 mBCancel = (Button) findViewById(R.id.cancel);
80 mPassCodeHdr = (TextView) findViewById(R.id.header);
81 mPassCodeHdrExplanation = (TextView) findViewById(R.id.explanation);
82 mText0 = (EditText) findViewById(R.id.txt0);
83 mText0.requestFocus();
84 getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
85 mText1 = (EditText) findViewById(R.id.txt1);
86 mText2 = (EditText) findViewById(R.id.txt2);
87 mText3 = (EditText) findViewById(R.id.txt3);
88
89 if (ACTION_REQUEST.equals(getIntent().getAction())) {
90 /// this is a pass code request; the user has to input the right value
91 mPassCodeHdr.setText(R.string.pass_code_enter_pass_code);
92 mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
93 setCancelButtonEnabled(false); // no option to cancel
94
95 } else if (ACTION_ENABLE.equals(getIntent().getAction())) {
96 /// pass code preference has just been activated in Preferences; will receive and confirm pass code value
97 mPassCodeHdr.setText(R.string.pass_code_configure_your_pass_code);
98 //mPassCodeHdr.setText(R.string.pass_code_enter_pass_code); // TODO choose a header, check iOS
99 mPassCodeHdrExplanation.setVisibility(View.VISIBLE);
100 setCancelButtonEnabled(true);
101
102 } else if (ACTION_DISABLE.equals(getIntent().getAction())) {
103 /// pass code preference has just been disabled in Preferences;
104 // will confirm user knows pass code, then remove it
105 mPassCodeHdr.setText(R.string.pass_code_remove_your_pass_code);
106 mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
107 setCancelButtonEnabled(true);
108
109 } else {
110 throw new IllegalArgumentException("A valid ACTION is needed in the Intent passed to " + TAG);
111 }
112
113 setTextListeners();
114
115 ActionBar actionBar = getSupportActionBar();
116 actionBar.setIcon(DisplayUtils.getSeasonalIconId());
117 }
118
119
120 protected void setCancelButtonEnabled(boolean enabled){
121 if(enabled){
122 mBCancel.setVisibility(View.VISIBLE);
123 mBCancel.setOnClickListener(new OnClickListener() {
124 @Override
125 public void onClick(View v) {
126 revertActionAndExit();
127 }
128 });
129 } else {
130 mBCancel.setVisibility(View.GONE);
131 mBCancel.setVisibility(View.INVISIBLE);
132 mBCancel.setOnClickListener(null);
133 }
134
135 }
136
137
138 /*
139 *
140 */
141 protected void setTextListeners(){
142
143 /*------------------------------------------------
144 * FIRST BOX
145 -------------------------------------------------*/
146
147 mText0.addTextChangedListener(new TextWatcher() {
148
149 @Override
150 public void onTextChanged(CharSequence s, int start, int before,
151 int count) {
152 }
153
154 @Override
155 public void beforeTextChanged(CharSequence s, int start, int count,
156 int after) {
157 }
158
159 @Override
160 public void afterTextChanged(Editable s) {
161 if (s.length() > 0) {
162 if (!mConfirmingPassCode) {
163 mPassCodeDigits[0] = mText0.getText().toString();
164 }
165 mText1.requestFocus();
166 } else {
167 Log_OC.w(TAG, "Input in text box 0 resulted in empty string");
168 }
169 }
170 });
171
172
173
174 /*------------------------------------------------
175 * SECOND BOX
176 -------------------------------------------------*/
177 mText1.addTextChangedListener(new TextWatcher() {
178
179 @Override
180 public void onTextChanged(CharSequence s, int start, int before,
181 int count) {
182 }
183
184 @Override
185 public void beforeTextChanged(CharSequence s, int start, int count,
186 int after) {
187 }
188
189 @Override
190 public void afterTextChanged(Editable s) {
191 if (s.length() > 0) {
192 if (!mConfirmingPassCode) {
193 mPassCodeDigits[1] = mText1.getText().toString();
194 }
195 mText2.requestFocus();
196 } else {
197 Log_OC.w(TAG, "Input in text box 1 resulted in empty string");
198 }
199 }
200 });
201
202 mText1.setOnKeyListener(new OnKeyListener() {
203
204 @Override
205 public boolean onKey(View v, int keyCode, KeyEvent event) {
206 if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) { // TODO WIP: event should be used to control what's exactly happening with DEL, not any custom field...
207 mText0.setText("");
208 mText0.requestFocus();
209 if (!mConfirmingPassCode)
210 mPassCodeDigits[0] = ""; // TODO WIP: what is this for??
211 mBChange = false;
212
213 } else if (!mBChange) {
214 mBChange = true;
215 }
216 return false;
217 }
218 });
219
220 mText1.setOnFocusChangeListener(new OnFocusChangeListener() {
221
222 @Override
223 public void onFocusChange(View v, boolean hasFocus) {
224 mText1.setCursorVisible(true); // TODO WIP this could be made static, or just nothing, since default is true...
225 if (mText0.getText().toString().equals("")) { // TODO WIP is this really needed? when?
226 mText1.setSelected(false);
227 mText1.setCursorVisible(false); // TODO WIP really this is a problem?
228 mText0.requestFocus(); // TODO WIP how many focus requests do we need?
229 mText0.setSelected(true); // TODO WIP what is this for?
230 mText0.setSelection(0); // TODO WIP what is THIS for?
231 }
232
233 }
234 });
235
236
237 /*------------------------------------------------
238 * THIRD BOX
239 -------------------------------------------------*/
240 /// TODO WIP yeah, let's repeat all the code again...
241 mText2.addTextChangedListener(new TextWatcher() {
242
243 @Override
244 public void onTextChanged(CharSequence s, int start, int before,
245 int count) {
246 }
247
248 @Override
249 public void beforeTextChanged(CharSequence s, int start, int count,
250 int after) {
251 }
252
253 @Override
254 public void afterTextChanged(Editable s) {
255 if (s.length() > 0) {
256 if (!mConfirmingPassCode) {
257 mPassCodeDigits[2] = mText2.getText().toString();
258 }
259 mText3.requestFocus();
260 } else {
261 Log_OC.w(TAG, "Input in text box 2 resulted in empty string");
262 }
263 }
264 });
265
266 mText2.setOnKeyListener(new OnKeyListener() {
267
268 @Override
269 public boolean onKey(View v, int keyCode, KeyEvent event) {
270 if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) {
271 mText1.requestFocus();
272 if (!mConfirmingPassCode)
273 mPassCodeDigits[1] = "";
274 mText1.setText("");
275 mBChange = false;
276
277 } else if (!mBChange) {
278 mBChange = true;
279
280 }
281 return false;
282 }
283 });
284
285 mText2.setOnFocusChangeListener(new OnFocusChangeListener() {
286
287 @Override
288 public void onFocusChange(View v, boolean hasFocus) {
289 /// TODO WIP: hasFocus is there for some reason; for instance, doing NOTHING if this is not my business, instead of considering all the possible cases in every edit text
290 mText2.setCursorVisible(true);
291 if (mText0.getText().toString().equals("")) {
292 mText2.setSelected(false);
293 mText2.setCursorVisible(false);
294 mText0.requestFocus();
295 mText0.setSelected(true);
296 mText0.setSelection(0);
297 } else if (mText1.getText().toString().equals("")) {
298 mText2.setSelected(false);
299 mText2.setCursorVisible(false);
300 mText1.requestFocus();
301 mText1.setSelected(true);
302 mText1.setSelection(0);
303 }
304
305 }
306 });
307
308
309 /*------------------------------------------------
310 * FOURTH BOX
311 -------------------------------------------------*/
312 mText3.addTextChangedListener(new TextWatcher() {
313
314 @Override
315 public void onTextChanged(CharSequence s, int start, int before,
316 int count) {
317 }
318
319 @Override
320 public void beforeTextChanged(CharSequence s, int start, int count,
321 int after) {
322 }
323
324 @Override
325 public void afterTextChanged(Editable s) {
326 if (s.length() > 0) {
327
328 if (!mConfirmingPassCode) {
329 mPassCodeDigits[3] = mText3.getText().toString();
330 }
331 mText0.requestFocus();
332
333 processFullPassCode();
334
335 } else {
336 Log_OC.w(TAG, "Input in text box 3 resulted in empty string");
337 }
338 }
339 });
340
341
342 mText3.setOnKeyListener(new OnKeyListener() {
343
344 @Override
345 public boolean onKey(View v, int keyCode, KeyEvent event) {
346 if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) {
347 mText2.requestFocus();
348 if (!mConfirmingPassCode)
349 mPassCodeDigits[2] = "";
350 mText2.setText("");
351 mBChange = false;
352
353 } else if (!mBChange) {
354 mBChange = true;
355 }
356 return false;
357 }
358 });
359
360 mText3.setOnFocusChangeListener(new OnFocusChangeListener() {
361
362 @Override
363 public void onFocusChange(View v, boolean hasFocus) {
364 mText3.setCursorVisible(true);
365
366 if (mText0.getText().toString().equals("")) {
367 mText3.setSelected(false);
368 mText3.setCursorVisible(false);
369 mText0.requestFocus();
370 mText0.setSelected(true);
371 mText0.setSelection(0);
372 } else if (mText1.getText().toString().equals("")) {
373 mText3.setSelected(false);
374 mText3.setCursorVisible(false);
375 mText1.requestFocus();
376 mText1.setSelected(true);
377 mText1.setSelection(0);
378 } else if (mText2.getText().toString().equals("")) {
379 mText3.setSelected(false);
380 mText3.setCursorVisible(false);
381 mText2.requestFocus();
382 mText2.setSelected(true);
383 mText2.setSelection(0);
384 }
385
386 }
387 });
388
389
390
391 } // end setTextListener
392
393
394 /**
395 * Processes the pass code entered by the user just after the last digit was in.
396 *
397 * Takes into account the action requested to the activity, the currently saved pass code and the previously
398 * typed pass code, if any.
399 */
400 private void processFullPassCode() {
401 if (ACTION_REQUEST.equals(getIntent().getAction())) {
402 if (checkPassCode()) {
403 /// pass code accepted in request, user is allowed to access the app
404 finish();
405
406 } else {
407 showErrorAndRestart(R.string.common_error, R.string.pass_code_enter_pass_code, View.INVISIBLE);
408 /// TODO better error message
409 }
410
411 } else if (ACTION_DISABLE.equals(getIntent().getAction())) {
412 if (checkPassCode()) {
413 /// pass code accepted when disabling, pass code is removed
414 SharedPreferences.Editor appPrefs = PreferenceManager
415 .getDefaultSharedPreferences(getApplicationContext()).edit();
416 appPrefs.putBoolean("set_pincode", false); // TODO remove; this should be unnecessary, was done before entering in the activity
417 appPrefs.commit();
418
419 Toast.makeText(PassCodeActivity.this, R.string.pass_code_removed, Toast.LENGTH_LONG).show();
420 finish();
421
422 } else {
423 showErrorAndRestart(R.string.common_error, R.string.pass_code_enter_pass_code, View.INVISIBLE);
424 /// TODO better error message
425 }
426
427 } else if (ACTION_ENABLE.equals(getIntent().getAction())) {
428 /// enabling pass code
429 if (!mConfirmingPassCode) {
430 requestPassCodeConfirmation();
431
432 } else if (confirmPassCode()) {
433 /// confirmed: user typed the same pass code twice
434 savePassCodeAndExit();
435
436 } else {
437 showErrorAndRestart(
438 R.string.pass_code_mismatch, R.string.pass_code_configure_your_pass_code, View.VISIBLE
439 );
440 }
441 }
442 }
443
444
445 private void showErrorAndRestart(int errorMessage, int headerMessage, int explanationVisibility) {
446 Arrays.fill(mPassCodeDigits, null);
447 CharSequence errorSeq = getString(errorMessage);
448 Toast.makeText(this, errorSeq, Toast.LENGTH_LONG).show();
449 mPassCodeHdr.setText(headerMessage); // TODO check if really needed
450 mPassCodeHdrExplanation.setVisibility(explanationVisibility); // TODO check if really needed
451 clearBoxes();
452 }
453
454
455 /**
456 * Ask to the user for retyping the pass code just entered before saving it as the current pass code.
457 */
458 protected void requestPassCodeConfirmation(){
459 clearBoxes();
460 mPassCodeHdr.setText(R.string.pass_code_reenter_your_pass_code);
461 mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
462 mConfirmingPassCode = true;
463 }
464
465 /**
466 * Compares pass code entered by the user with the value currently saved in the app.
467 *
468 * @return 'True' if entered pass code equals to the saved one.
469 */
470 protected boolean checkPassCode(){
471 SharedPreferences appPrefs = PreferenceManager
472 .getDefaultSharedPreferences(getApplicationContext());
473
474 String savedPassCodeDigits[] = new String[4];
475 savedPassCodeDigits[0] = appPrefs.getString("PrefPinCode1", null);
476 savedPassCodeDigits[1] = appPrefs.getString("PrefPinCode2", null);
477 savedPassCodeDigits[2] = appPrefs.getString("PrefPinCode3", null);
478 savedPassCodeDigits[3] = appPrefs.getString("PrefPinCode4", null);
479
480 return (
481 mPassCodeDigits[0].equals(savedPassCodeDigits[0]) &&
482 mPassCodeDigits[1].equals(savedPassCodeDigits[0]) &&
483 mPassCodeDigits[2].equals(savedPassCodeDigits[0]) &&
484 mPassCodeDigits[3].equals(savedPassCodeDigits[0])
485 );
486 }
487
488 /**
489 * Compares pass code retyped by the user with the value entered just before.
490 *
491 * @return 'True' if retyped pass code equals to the entered before.
492 */
493 protected boolean confirmPassCode(){
494 mConfirmingPassCode = false;
495
496 String retypedPassCodeDigits[] = new String[4];
497 retypedPassCodeDigits[0] = mText0.getText().toString();
498 retypedPassCodeDigits[1] = mText1.getText().toString();
499 retypedPassCodeDigits[2] = mText2.getText().toString();
500 retypedPassCodeDigits[3] = mText3.getText().toString();
501
502 return (
503 mPassCodeDigits[0].equals(retypedPassCodeDigits[0]) &&
504 mPassCodeDigits[1].equals(retypedPassCodeDigits[0]) &&
505 mPassCodeDigits[2].equals(retypedPassCodeDigits[0]) &&
506 mPassCodeDigits[3].equals(retypedPassCodeDigits[0])
507 );
508 }
509
510 /**
511 * Sets the input fields to empty strings and puts the focus on the first one.
512 */
513 protected void clearBoxes(){
514 mText0.setText("");
515 mText1.setText("");
516 mText2.setText("");
517 mText3.setText("");
518 mText0.requestFocus();
519 }
520
521 /**
522 * Overrides click on the BACK arrow to correctly cancel ACTION_ENABLE or ACTION_DISABLE, while preventing
523 * than ACTION_REQUEST may be worked around.
524 *
525 * @param keyCode Key code of the key that triggered the down event.
526 * @param event Event triggered.
527 * @return 'True' when the key event was processed by this method.
528 */
529 @Override
530 public boolean onKeyDown(int keyCode, KeyEvent event){
531 if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount()== 0){
532 if (ACTION_ENABLE.equals(getIntent().getAction()) || ACTION_DISABLE.equals(getIntent().getAction())) {
533 revertActionAndExit();
534 }
535 return true;
536 }
537 return super.onKeyDown(keyCode, event);
538 }
539
540 /**
541 * Saves the pass code input by the user as the current pass code.
542 */
543 protected void savePassCodeAndExit() {
544 SharedPreferences.Editor appPrefs = PreferenceManager
545 .getDefaultSharedPreferences(getApplicationContext()).edit();
546
547 appPrefs.putString("PrefPinCode1", mPassCodeDigits[0]);
548 appPrefs.putString("PrefPinCode2", mPassCodeDigits[1]);
549 appPrefs.putString("PrefPinCode3", mPassCodeDigits[2]);
550 appPrefs.putString("PrefPinCode4", mPassCodeDigits[3]);
551 appPrefs.putBoolean("set_pincode", true); /// TODO remove; unnecessary, Preferences did it before entering here
552 appPrefs.commit();
553
554 Toast.makeText(this, R.string.pass_code_stored, Toast.LENGTH_LONG).show();
555 finish();
556 }
557
558 /**
559 * Cancellation of ACTION_ENABLE or ACTION_DISABLE; reverts the enable or disable action done by
560 * {@link Preferences}, then finishes.
561 */
562 protected void revertActionAndExit() {
563 SharedPreferences.Editor appPrefsE = PreferenceManager
564 .getDefaultSharedPreferences(getApplicationContext()).edit();
565
566 SharedPreferences appPrefs = PreferenceManager
567 .getDefaultSharedPreferences(getApplicationContext());
568
569 boolean state = appPrefs.getBoolean("set_pincode", false);
570 appPrefsE.putBoolean("set_pincode", !state);
571 // TODO WIP: this is reverting the value of the preference because it was changed BEFORE entering
572 // TODO in this activity; was the PreferenceCheckBox in the caller who did it
573 appPrefsE.commit();
574 finish();
575 }
576
577 }