modify code to be simpler
[pub/Android/ownCloud.git] / src / com / owncloud / android / ui / activity / PassCodeActivity.java
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
25 import java.util.Arrays;
26
27 import android.content.SharedPreferences;
28 import android.os.Bundle;
29 import android.preference.PreferenceManager;
30 import android.support.v7.app.ActionBar;
31 import android.support.v7.app.ActionBarActivity;
32 import android.text.Editable;
33 import android.text.TextWatcher;
34 import android.view.KeyEvent;
35 import android.view.View;
36 import android.view.View.OnClickListener;
37 import android.widget.Button;
38 import android.widget.EditText;
39 import android.widget.TextView;
40 import android.widget.Toast;
41
42 import com.owncloud.android.R;
43 import com.owncloud.android.lib.common.utils.Log_OC;
44 import com.owncloud.android.utils.DisplayUtils;
45
46 public class PassCodeActivity extends ActionBarActivity {
47
48
49 private static final String TAG = PassCodeActivity.class.getSimpleName();
50
51 public final static String ACTION_ENABLE = PassCodeActivity.class.getCanonicalName() +
52 ".ENABLE";
53 public final static String ACTION_DISABLE = PassCodeActivity.class.getCanonicalName() +
54 ".DISABLE";
55 public final static String ACTION_REQUEST = PassCodeActivity.class.getCanonicalName() +
56 ".REQUEST";
57
58 private Button mBCancel;
59 private TextView mPassCodeHdr;
60 private TextView mPassCodeHdrExplanation;
61 private EditText[] mPassCodeEditTexts = new EditText[4];
62
63 private String [] mPassCodeDigits = {"","","",""};
64 private static String KEY_PASSCODE_DIGITS = "PASSCODE_DIGITS";
65 private boolean mConfirmingPassCode = false;
66 private static String KEY_CONFIRMING_PASSCODE = "CONFIRMING_PASSCODE";
67
68 private boolean mBChange = true; // to control that only one blocks jump
69
70
71 /**
72 * Initializes the activity.
73 *
74 * An intent with a valid ACTION is expected; if none is found, an
75 * {@link IllegalArgumentException} will be thrown.
76 *
77 * @param savedInstanceState Previously saved state - irrelevant in this case
78 */
79 protected void onCreate(Bundle savedInstanceState) {
80 super.onCreate(savedInstanceState);
81 setContentView(R.layout.passcodelock);
82
83 mBCancel = (Button) findViewById(R.id.cancel);
84 mPassCodeHdr = (TextView) findViewById(R.id.header);
85 mPassCodeHdrExplanation = (TextView) findViewById(R.id.explanation);
86 mPassCodeEditTexts[0] = (EditText) findViewById(R.id.txt0);
87 mPassCodeEditTexts[0].requestFocus();
88 getWindow().setSoftInputMode(
89 android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
90 mPassCodeEditTexts[1] = (EditText) findViewById(R.id.txt1);
91 mPassCodeEditTexts[2] = (EditText) findViewById(R.id.txt2);
92 mPassCodeEditTexts[3] = (EditText) findViewById(R.id.txt3);
93
94 if (ACTION_REQUEST.equals(getIntent().getAction())) {
95 /// this is a pass code request; the user has to input the right value
96 mPassCodeHdr.setText(R.string.pass_code_enter_pass_code);
97 mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
98 setCancelButtonEnabled(false); // no option to cancel
99
100 } else if (ACTION_ENABLE.equals(getIntent().getAction())) {
101 if (savedInstanceState != null) {
102 mConfirmingPassCode = savedInstanceState.getBoolean(PassCodeActivity.KEY_CONFIRMING_PASSCODE);
103 mPassCodeDigits = savedInstanceState.getStringArray(PassCodeActivity.KEY_PASSCODE_DIGITS);
104 }
105 if(mConfirmingPassCode){
106 //the app was in the passcodeconfirmation
107 requestPassCodeConfirmation();
108 }else{
109 /// pass code preference has just been activated in Preferences;
110 // will receive and confirm pass code value
111 mPassCodeHdr.setText(R.string.pass_code_configure_your_pass_code);
112 //mPassCodeHdr.setText(R.string.pass_code_enter_pass_code);
113 // TODO choose a header, check iOS
114 mPassCodeHdrExplanation.setVisibility(View.VISIBLE);
115 setCancelButtonEnabled(true);
116 }
117
118 } else if (ACTION_DISABLE.equals(getIntent().getAction())) {
119 /// pass code preference has just been disabled in Preferences;
120 // will confirm user knows pass code, then remove it
121 mPassCodeHdr.setText(R.string.pass_code_remove_your_pass_code);
122 mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
123 setCancelButtonEnabled(true);
124
125 } else {
126 throw new IllegalArgumentException("A valid ACTION is needed in the Intent passed to "
127 + TAG);
128 }
129
130 setTextListeners();
131
132 ActionBar actionBar = getSupportActionBar();
133 actionBar.setIcon(DisplayUtils.getSeasonalIconId());
134 }
135
136
137 /**
138 * Enables or disables the cancel button to allow the user interrupt the ACTION
139 * requested to the activity.
140 *
141 * @param enabled 'True' makes the cancel button available, 'false' hides it.
142 */
143 protected void setCancelButtonEnabled(boolean enabled){
144 if(enabled){
145 mBCancel.setVisibility(View.VISIBLE);
146 mBCancel.setOnClickListener(new OnClickListener() {
147 @Override
148 public void onClick(View v) {
149 revertActionAndExit();
150 }
151 });
152 } else {
153 mBCancel.setVisibility(View.GONE);
154 mBCancel.setVisibility(View.INVISIBLE);
155 mBCancel.setOnClickListener(null);
156 }
157 }
158
159
160 /**
161 * Binds the appropiate listeners to the input boxes receiving each digit of the pass code.
162 */
163 protected void setTextListeners() {
164
165 /// First input field
166 mPassCodeEditTexts[0].addTextChangedListener(new PassCodeDigitTextWatcher(0, false));
167
168
169 /*------------------------------------------------
170 * SECOND BOX
171 -------------------------------------------------*/
172 mPassCodeEditTexts[1].addTextChangedListener(new PassCodeDigitTextWatcher(1, false));
173
174 mPassCodeEditTexts[1].setOnKeyListener(new View.OnKeyListener() {
175
176 @Override
177 public boolean onKey(View v, int keyCode, KeyEvent event) {
178 if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) { // TODO WIP: event should be
179 // used to control what's exactly happening with DEL, not any custom field...
180 mPassCodeEditTexts[0].setText("");
181 mPassCodeEditTexts[0].requestFocus();
182 if (!mConfirmingPassCode)
183 mPassCodeDigits[0] = "";
184 mBChange = false;
185
186 } else if (!mBChange) {
187 mBChange = true;
188 }
189 return false;
190 }
191 });
192
193 mPassCodeEditTexts[1].setOnFocusChangeListener(new View.OnFocusChangeListener() {
194
195 @Override
196 public void onFocusChange(View v, boolean hasFocus) {
197 /// TODO WIP: should take advantage of hasFocus to reduce processing
198 if (mPassCodeEditTexts[0].getText().toString().equals("")) { // TODO WIP validation
199 // could be done in a global way, with a single OnFocusChangeListener for all the
200 // input fields
201 mPassCodeEditTexts[0].requestFocus();
202 }
203 }
204 });
205
206
207 /*------------------------------------------------
208 * THIRD BOX
209 -------------------------------------------------*/
210 mPassCodeEditTexts[2].addTextChangedListener(new PassCodeDigitTextWatcher(2, false));
211
212 mPassCodeEditTexts[2].setOnKeyListener(new View.OnKeyListener() {
213
214 @Override
215 public boolean onKey(View v, int keyCode, KeyEvent event) {
216 if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) {
217 mPassCodeEditTexts[1].requestFocus();
218 if (!mConfirmingPassCode)
219 mPassCodeDigits[1] = "";
220 mPassCodeEditTexts[1].setText("");
221 mBChange = false;
222
223 } else if (!mBChange) {
224 mBChange = true;
225
226 }
227 return false;
228 }
229 });
230
231 mPassCodeEditTexts[2].setOnFocusChangeListener(new View.OnFocusChangeListener() {
232
233 @Override
234 public void onFocusChange(View v, boolean hasFocus) {
235 if (mPassCodeEditTexts[0].getText().toString().equals("")) {
236 mPassCodeEditTexts[0].requestFocus();
237 } else if (mPassCodeEditTexts[1].getText().toString().equals("")) {
238 mPassCodeEditTexts[1].requestFocus();
239 }
240 }
241 });
242
243
244 /*------------------------------------------------
245 * FOURTH BOX
246 -------------------------------------------------*/
247 mPassCodeEditTexts[3].addTextChangedListener(new PassCodeDigitTextWatcher(3, true));
248
249 mPassCodeEditTexts[3].setOnKeyListener(new View.OnKeyListener() {
250
251 @Override
252 public boolean onKey(View v, int keyCode, KeyEvent event) {
253 if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) {
254 mPassCodeEditTexts[2].requestFocus();
255 if (!mConfirmingPassCode)
256 mPassCodeDigits[2] = "";
257 mPassCodeEditTexts[2].setText("");
258 mBChange = false;
259
260 } else if (!mBChange) {
261 mBChange = true;
262 }
263 return false;
264 }
265 });
266
267 mPassCodeEditTexts[3].setOnFocusChangeListener(new View.OnFocusChangeListener() {
268
269 @Override
270 public void onFocusChange(View v, boolean hasFocus) {
271
272 if (mPassCodeEditTexts[0].getText().toString().equals("")) {
273 mPassCodeEditTexts[0].requestFocus();
274 } else if (mPassCodeEditTexts[1].getText().toString().equals("")) {
275 mPassCodeEditTexts[1].requestFocus();
276 } else if (mPassCodeEditTexts[2].getText().toString().equals("")) {
277 mPassCodeEditTexts[2].requestFocus();
278 }
279
280 }
281 });
282
283 } // end setTextListener
284
285
286 /**
287 * Processes the pass code entered by the user just after the last digit was in.
288 *
289 * Takes into account the action requested to the activity, the currently saved pass code and
290 * the previously typed pass code, if any.
291 */
292 private void processFullPassCode() {
293 if (ACTION_REQUEST.equals(getIntent().getAction())) {
294 if (checkPassCode()) {
295 /// pass code accepted in request, user is allowed to access the app
296 finish();
297
298 } else {
299 showErrorAndRestart(R.string.pass_code_wrong, R.string.pass_code_enter_pass_code,
300 View.INVISIBLE);
301 }
302
303 } else if (ACTION_DISABLE.equals(getIntent().getAction())) {
304 if (checkPassCode()) {
305 /// pass code accepted when disabling, pass code is removed
306 SharedPreferences.Editor appPrefs = PreferenceManager
307 .getDefaultSharedPreferences(getApplicationContext()).edit();
308 appPrefs.putBoolean("set_pincode", false); // TODO remove; this should be
309 // unnecessary, was done before entering in the activity
310 appPrefs.commit();
311
312 Toast.makeText(PassCodeActivity.this, R.string.pass_code_removed, Toast.LENGTH_LONG).show();
313 finish();
314
315 } else {
316 showErrorAndRestart(R.string.pass_code_wrong, R.string.pass_code_enter_pass_code,
317 View.INVISIBLE);
318 }
319
320 } else if (ACTION_ENABLE.equals(getIntent().getAction())) {
321 /// enabling pass code
322 if (!mConfirmingPassCode) {
323 requestPassCodeConfirmation();
324
325 } else if (confirmPassCode()) {
326 /// confirmed: user typed the same pass code twice
327 savePassCodeAndExit();
328
329 } else {
330 showErrorAndRestart(
331 R.string.pass_code_mismatch, R.string.pass_code_configure_your_pass_code, View.VISIBLE
332 );
333 }
334 }
335 }
336
337
338 private void showErrorAndRestart(int errorMessage, int headerMessage,
339 int explanationVisibility) {
340 Arrays.fill(mPassCodeDigits, null);
341 CharSequence errorSeq = getString(errorMessage);
342 Toast.makeText(this, errorSeq, Toast.LENGTH_LONG).show();
343 mPassCodeHdr.setText(headerMessage); // TODO check if really needed
344 mPassCodeHdrExplanation.setVisibility(explanationVisibility); // TODO check if really needed
345 clearBoxes();
346 }
347
348
349 /**
350 * Ask to the user for retyping the pass code just entered before saving it as the current pass
351 * code.
352 */
353 protected void requestPassCodeConfirmation(){
354 clearBoxes();
355 mPassCodeHdr.setText(R.string.pass_code_reenter_your_pass_code);
356 mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
357 mConfirmingPassCode = true;
358 }
359
360 /**
361 * Compares pass code entered by the user with the value currently saved in the app.
362 *
363 * @return 'True' if entered pass code equals to the saved one.
364 */
365 protected boolean checkPassCode(){
366 SharedPreferences appPrefs = PreferenceManager
367 .getDefaultSharedPreferences(getApplicationContext());
368
369 String savedPassCodeDigits[] = new String[4];
370 savedPassCodeDigits[0] = appPrefs.getString("PrefPinCode1", null);
371 savedPassCodeDigits[1] = appPrefs.getString("PrefPinCode2", null);
372 savedPassCodeDigits[2] = appPrefs.getString("PrefPinCode3", null);
373 savedPassCodeDigits[3] = appPrefs.getString("PrefPinCode4", null);
374
375 boolean result = true;
376 for (int i = 0; i < mPassCodeDigits.length && result; i++) {
377 result = result && (mPassCodeDigits[i] != null) &&
378 mPassCodeDigits[i].equals(savedPassCodeDigits[i]);
379 }
380 return result;
381 }
382
383 /**
384 * Compares pass code retyped by the user in the input fields with the value entered just
385 * before.
386 *
387 * @return 'True' if retyped pass code equals to the entered before.
388 */
389 protected boolean confirmPassCode(){
390 mConfirmingPassCode = false;
391
392 boolean result = true;
393 for (int i = 0; i < mPassCodeEditTexts.length && result; i++) {
394 result = result &&
395 ((mPassCodeEditTexts[i].getText().toString()).equals(mPassCodeDigits[i]));
396 }
397 return result;
398 }
399
400 /**
401 * Sets the input fields to empty strings and puts the focus on the first one.
402 */
403 protected void clearBoxes(){
404 for (int i=0; i < mPassCodeEditTexts.length; i++) {
405 mPassCodeEditTexts[i].setText("");
406 }
407 mPassCodeEditTexts[0].requestFocus();
408 }
409
410 /**
411 * Overrides click on the BACK arrow to correctly cancel ACTION_ENABLE or ACTION_DISABLE, while
412 * preventing than ACTION_REQUEST may be worked around.
413 *
414 * @param keyCode Key code of the key that triggered the down event.
415 * @param event Event triggered.
416 * @return 'True' when the key event was processed by this method.
417 */
418 @Override
419 public boolean onKeyDown(int keyCode, KeyEvent event){
420 if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount()== 0){
421 if (ACTION_ENABLE.equals(getIntent().getAction()) ||
422 ACTION_DISABLE.equals(getIntent().getAction())) {
423 revertActionAndExit();
424 }
425 return true;
426 }
427 return super.onKeyDown(keyCode, event);
428 }
429
430 /**
431 * Saves the pass code input by the user as the current pass code.
432 */
433 protected void savePassCodeAndExit() {
434 SharedPreferences.Editor appPrefs = PreferenceManager
435 .getDefaultSharedPreferences(getApplicationContext()).edit();
436
437 appPrefs.putString("PrefPinCode1", mPassCodeDigits[0]);
438 appPrefs.putString("PrefPinCode2", mPassCodeDigits[1]);
439 appPrefs.putString("PrefPinCode3", mPassCodeDigits[2]);
440 appPrefs.putString("PrefPinCode4", mPassCodeDigits[3]);
441 appPrefs.putBoolean("set_pincode", true); /// TODO remove; unnecessary,
442 // Preferences did it before entering here
443 appPrefs.commit();
444
445 Toast.makeText(this, R.string.pass_code_stored, Toast.LENGTH_LONG).show();
446 finish();
447 }
448
449 /**
450 * Cancellation of ACTION_ENABLE or ACTION_DISABLE; reverts the enable or disable action done by
451 * {@link Preferences}, then finishes.
452 */
453 protected void revertActionAndExit() {
454 SharedPreferences.Editor appPrefsE = PreferenceManager
455 .getDefaultSharedPreferences(getApplicationContext()).edit();
456
457 SharedPreferences appPrefs = PreferenceManager
458 .getDefaultSharedPreferences(getApplicationContext());
459
460 boolean state = appPrefs.getBoolean("set_pincode", false);
461 appPrefsE.putBoolean("set_pincode", !state);
462 // TODO WIP: this is reverting the value of the preference because it was changed BEFORE
463 // entering
464 // TODO in this activity; was the PreferenceCheckBox in the caller who did it
465 appPrefsE.commit();
466 finish();
467 }
468
469 @Override
470 public void onSaveInstanceState(Bundle outState) {
471 super.onSaveInstanceState(outState);
472 outState.putBoolean(PassCodeActivity.KEY_CONFIRMING_PASSCODE, mConfirmingPassCode);
473 outState.putStringArray(PassCodeActivity.KEY_PASSCODE_DIGITS, mPassCodeDigits);
474 }
475
476
477 private class PassCodeDigitTextWatcher implements TextWatcher {
478
479 private int mIndex = -1;
480 private boolean mLastOne = false;
481
482 /**
483 * Constructor
484 *
485 * @param index Position in the pass code of the input field that will be bound to
486 * this watcher.
487 * @param lastOne 'True' means that watcher corresponds to the last position of the
488 * pass code.
489 */
490 public PassCodeDigitTextWatcher(int index, boolean lastOne) {
491 mIndex = index;
492 mLastOne = lastOne;
493 if (mIndex < 0) {
494 throw new IllegalArgumentException(
495 "Invalid index in " + PassCodeDigitTextWatcher.class.getSimpleName() +
496 " constructor"
497 );
498 }
499 }
500
501 private int next() {
502 return mLastOne ? 0 : mIndex + 1;
503 }
504
505 /**
506 * Performs several actions when the user types a digit in an input field:
507 * - saves the input digit to the state of the activity; this will allow retyping the
508 * pass code to confirm it.
509 * - moves the focus automatically to the next field
510 * - for the last field, triggers the processing of the full pass code
511 *
512 * @param s
513 */
514 @Override
515 public void afterTextChanged(Editable s) {
516 if (s.length() > 0) {
517 if (!mConfirmingPassCode) {
518 mPassCodeDigits[mIndex] = mPassCodeEditTexts[mIndex].getText().toString();
519 }
520 mPassCodeEditTexts[next()].requestFocus();
521
522 if (mLastOne) {
523 processFullPassCode();
524 }
525
526 } else {
527 Log_OC.d(TAG, "Text box " + mIndex + " was cleaned");
528 }
529 }
530
531 @Override
532 public void beforeTextChanged(CharSequence s, int start, int count, int after) {
533 // nothing to do
534 }
535
536 @Override
537 public void onTextChanged(CharSequence s, int start, int before, int count) {
538 // nothing to do
539 }
540
541 }
542
543
544 }