2 * ownCloud Android client application
4 * Copyright (C) 2011 Bartek Przybylski
5 * Copyright (C) 2015 ownCloud Inc.
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.
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.
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/>.
20 package com
.owncloud
.android
.ui
.activity
;
22 import java
.util
.Arrays
;
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
;
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
;
45 public class PassCodeActivity
extends SherlockFragmentActivity
{
48 private static final String TAG
= PassCodeActivity
.class.getSimpleName();
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";
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
;
62 private String
[] mPassCodeDigits
= {"","","",""};
63 private boolean mConfirmingPassCode
= false
;
65 private boolean mBChange
= true
; // to control that only one blocks jump
69 * Initializes the activity.
71 * An intent with a valid ACTION is expected; if none is found, an {@link IllegalArgumentException} will be thrown.
73 * @param savedInstanceState Previously saved state - irrelevant in this case
75 protected void onCreate(Bundle savedInstanceState
) {
76 super.onCreate(savedInstanceState
);
77 setContentView(R
.layout
.passcodelock
);
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
);
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
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
);
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
);
110 throw new IllegalArgumentException("A valid ACTION is needed in the Intent passed to " + TAG
);
115 ActionBar actionBar
= getSupportActionBar();
116 actionBar
.setIcon(DisplayUtils
.getSeasonalIconId());
120 protected void setCancelButtonEnabled(boolean enabled
){
122 mBCancel
.setVisibility(View
.VISIBLE
);
123 mBCancel
.setOnClickListener(new OnClickListener() {
125 public void onClick(View v
) {
126 revertActionAndExit();
130 mBCancel
.setVisibility(View
.GONE
);
131 mBCancel
.setVisibility(View
.INVISIBLE
);
132 mBCancel
.setOnClickListener(null
);
141 protected void setTextListeners(){
143 /*------------------------------------------------
145 -------------------------------------------------*/
147 mText0
.addTextChangedListener(new TextWatcher() {
150 public void onTextChanged(CharSequence s
, int start
, int before
,
155 public void beforeTextChanged(CharSequence s
, int start
, int count
,
160 public void afterTextChanged(Editable s
) {
161 if (s
.length() > 0) {
162 if (!mConfirmingPassCode
) {
163 mPassCodeDigits
[0] = mText0
.getText().toString();
165 mText1
.requestFocus();
167 Log_OC
.w(TAG
, "Input in text box 0 resulted in empty string");
174 /*------------------------------------------------
176 -------------------------------------------------*/
177 mText1
.addTextChangedListener(new TextWatcher() {
180 public void onTextChanged(CharSequence s
, int start
, int before
,
185 public void beforeTextChanged(CharSequence s
, int start
, int count
,
190 public void afterTextChanged(Editable s
) {
191 if (s
.length() > 0) {
192 if (!mConfirmingPassCode
) {
193 mPassCodeDigits
[1] = mText1
.getText().toString();
195 mText2
.requestFocus();
197 Log_OC
.w(TAG
, "Input in text box 1 resulted in empty string");
202 mText1
.setOnKeyListener(new OnKeyListener() {
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...
208 mText0
.requestFocus();
209 if (!mConfirmingPassCode
)
210 mPassCodeDigits
[0] = ""; // TODO WIP: what is this for??
213 } else if (!mBChange
) {
220 mText1
.setOnFocusChangeListener(new OnFocusChangeListener() {
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?
237 /*------------------------------------------------
239 -------------------------------------------------*/
240 /// TODO WIP yeah, let's repeat all the code again...
241 mText2
.addTextChangedListener(new TextWatcher() {
244 public void onTextChanged(CharSequence s
, int start
, int before
,
249 public void beforeTextChanged(CharSequence s
, int start
, int count
,
254 public void afterTextChanged(Editable s
) {
255 if (s
.length() > 0) {
256 if (!mConfirmingPassCode
) {
257 mPassCodeDigits
[2] = mText2
.getText().toString();
259 mText3
.requestFocus();
261 Log_OC
.w(TAG
, "Input in text box 2 resulted in empty string");
266 mText2
.setOnKeyListener(new OnKeyListener() {
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] = "";
277 } else if (!mBChange
) {
285 mText2
.setOnFocusChangeListener(new OnFocusChangeListener() {
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);
309 /*------------------------------------------------
311 -------------------------------------------------*/
312 mText3
.addTextChangedListener(new TextWatcher() {
315 public void onTextChanged(CharSequence s
, int start
, int before
,
320 public void beforeTextChanged(CharSequence s
, int start
, int count
,
325 public void afterTextChanged(Editable s
) {
326 if (s
.length() > 0) {
328 if (!mConfirmingPassCode
) {
329 mPassCodeDigits
[3] = mText3
.getText().toString();
331 mText0
.requestFocus();
333 processFullPassCode();
336 Log_OC
.w(TAG
, "Input in text box 3 resulted in empty string");
342 mText3
.setOnKeyListener(new OnKeyListener() {
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] = "";
353 } else if (!mBChange
) {
360 mText3
.setOnFocusChangeListener(new OnFocusChangeListener() {
363 public void onFocusChange(View v
, boolean hasFocus
) {
364 mText3
.setCursorVisible(true
);
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);
391 } // end setTextListener
395 * Processes the pass code entered by the user just after the last digit was in.
397 * Takes into account the action requested to the activity, the currently saved pass code and the previously
398 * typed pass code, if any.
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
407 showErrorAndRestart(R
.string
.common_error
, R
.string
.pass_code_enter_pass_code
, View
.INVISIBLE
);
408 /// TODO better error message
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
419 Toast
.makeText(PassCodeActivity
.this, R
.string
.pass_code_removed
, Toast
.LENGTH_LONG
).show();
423 showErrorAndRestart(R
.string
.common_error
, R
.string
.pass_code_enter_pass_code
, View
.INVISIBLE
);
424 /// TODO better error message
427 } else if (ACTION_ENABLE
.equals(getIntent().getAction())) {
428 /// enabling pass code
429 if (!mConfirmingPassCode
) {
430 requestPassCodeConfirmation();
432 } else if (confirmPassCode()) {
433 /// confirmed: user typed the same pass code twice
434 savePassCodeAndExit();
438 R
.string
.pass_code_mismatch
, R
.string
.pass_code_configure_your_pass_code
, View
.VISIBLE
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
456 * Ask to the user for retyping the pass code just entered before saving it as the current pass code.
458 protected void requestPassCodeConfirmation(){
460 mPassCodeHdr
.setText(R
.string
.pass_code_reenter_your_pass_code
);
461 mPassCodeHdrExplanation
.setVisibility(View
.INVISIBLE
);
462 mConfirmingPassCode
= true
;
466 * Compares pass code entered by the user with the value currently saved in the app.
468 * @return 'True' if entered pass code equals to the saved one.
470 protected boolean checkPassCode(){
471 SharedPreferences appPrefs
= PreferenceManager
472 .getDefaultSharedPreferences(getApplicationContext());
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
);
481 mPassCodeDigits
[0].equals(savedPassCodeDigits
[0]) &&
482 mPassCodeDigits
[1].equals(savedPassCodeDigits
[0]) &&
483 mPassCodeDigits
[2].equals(savedPassCodeDigits
[0]) &&
484 mPassCodeDigits
[3].equals(savedPassCodeDigits
[0])
489 * Compares pass code retyped by the user with the value entered just before.
491 * @return 'True' if retyped pass code equals to the entered before.
493 protected boolean confirmPassCode(){
494 mConfirmingPassCode
= false
;
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();
503 mPassCodeDigits
[0].equals(retypedPassCodeDigits
[0]) &&
504 mPassCodeDigits
[1].equals(retypedPassCodeDigits
[0]) &&
505 mPassCodeDigits
[2].equals(retypedPassCodeDigits
[0]) &&
506 mPassCodeDigits
[3].equals(retypedPassCodeDigits
[0])
511 * Sets the input fields to empty strings and puts the focus on the first one.
513 protected void clearBoxes(){
518 mText0
.requestFocus();
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.
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.
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();
537 return super.onKeyDown(keyCode
, event
);
541 * Saves the pass code input by the user as the current pass code.
543 protected void savePassCodeAndExit() {
544 SharedPreferences
.Editor appPrefs
= PreferenceManager
545 .getDefaultSharedPreferences(getApplicationContext()).edit();
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
554 Toast
.makeText(this, R
.string
.pass_code_stored
, Toast
.LENGTH_LONG
).show();
559 * Cancellation of ACTION_ENABLE or ACTION_DISABLE; reverts the enable or disable action done by
560 * {@link Preferences}, then finishes.
562 protected void revertActionAndExit() {
563 SharedPreferences
.Editor appPrefsE
= PreferenceManager
564 .getDefaultSharedPreferences(getApplicationContext()).edit();
566 SharedPreferences appPrefs
= PreferenceManager
567 .getDefaultSharedPreferences(getApplicationContext());
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