123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546 |
- /*
- * ownCloud Android client application
- *
- * @author Bartek Przybylski
- * @author masensio
- * @author David A. Velasco
- * Copyright (C) 2011 Bartek Przybylski
- * Copyright (C) 2015 ownCloud Inc.
- *
- * This program is free software: you can redistribute it and/or modify
- * it under the terms of the GNU General Public License version 2,
- * as published by the Free Software Foundation.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program. If not, see <http://www.gnu.org/licenses/>.
- *
- */
- package com.owncloud.android.ui.activity;
- import android.content.Intent;
- import android.content.SharedPreferences;
- import android.graphics.PorterDuff;
- import android.os.Bundle;
- import android.preference.PreferenceManager;
- import android.support.design.widget.Snackbar;
- import android.support.v7.app.AppCompatActivity;
- import android.text.Editable;
- import android.text.TextWatcher;
- import android.view.KeyEvent;
- import android.view.View;
- import android.view.View.OnClickListener;
- import android.view.inputmethod.InputMethodManager;
- import android.widget.Button;
- import android.widget.EditText;
- import android.widget.TextView;
- import com.owncloud.android.R;
- import com.owncloud.android.lib.common.utils.Log_OC;
- import com.owncloud.android.utils.ThemeUtils;
- import java.util.Arrays;
- public class PassCodeActivity extends AppCompatActivity {
- private static final String TAG = PassCodeActivity.class.getSimpleName();
- public final static String ACTION_REQUEST_WITH_RESULT = "ACTION_REQUEST_WITH_RESULT";
- public final static String ACTION_CHECK_WITH_RESULT = "ACTION_CHECK_WITH_RESULT";
- public final static String ACTION_CHECK = "ACTION_CHECK";
- public final static String KEY_PASSCODE = "KEY_PASSCODE";
- public final static String KEY_CHECK_RESULT = "KEY_CHECK_RESULT";
- // NOTE: PREFERENCE_SET_PASSCODE must have the same value as preferences.xml-->android:key for passcode preference
- public final static String PREFERENCE_SET_PASSCODE = "set_pincode";
- public final static String PREFERENCE_PASSCODE_D = "PrefPinCode";
- public final static String PREFERENCE_PASSCODE_D1 = "PrefPinCode1";
- public final static String PREFERENCE_PASSCODE_D2 = "PrefPinCode2";
- public final static String PREFERENCE_PASSCODE_D3 = "PrefPinCode3";
- public final static String PREFERENCE_PASSCODE_D4 = "PrefPinCode4";
- private Button mBCancel;
- private TextView mPassCodeHdr;
- private TextView mPassCodeHdrExplanation;
- private EditText[] mPassCodeEditTexts = new EditText[4];
- private String [] mPassCodeDigits = {"","","",""};
- private static final String KEY_PASSCODE_DIGITS = "PASSCODE_DIGITS";
- private boolean mConfirmingPassCode = false;
- private static final String KEY_CONFIRMING_PASSCODE = "CONFIRMING_PASSCODE";
- private boolean mBChange = true; // to control that only one blocks jump
- /**
- * Initializes the activity.
- *
- * An intent with a valid ACTION is expected; if none is found, an
- * {@link IllegalArgumentException} will be thrown.
- *
- * @param savedInstanceState Previously saved state - irrelevant in this case
- */
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.passcodelock);
- int primaryColor = ThemeUtils.primaryColor(this);
- mBCancel = findViewById(R.id.cancel);
- mBCancel.getBackground().setColorFilter(primaryColor, PorterDuff.Mode.SRC_ATOP);
- mPassCodeHdr = findViewById(R.id.header);
- mPassCodeHdrExplanation = findViewById(R.id.explanation);
- mPassCodeEditTexts[0] = findViewById(R.id.txt0);
- mPassCodeEditTexts[0].setTextColor(primaryColor);
- mPassCodeEditTexts[0].getBackground().setColorFilter(primaryColor, PorterDuff.Mode.SRC_ATOP);
- mPassCodeEditTexts[0].requestFocus();
- getWindow().setSoftInputMode(android.view.WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
- mPassCodeEditTexts[1] = findViewById(R.id.txt1);
- mPassCodeEditTexts[1].setTextColor(primaryColor);
- mPassCodeEditTexts[1].getBackground().setColorFilter(primaryColor, PorterDuff.Mode.SRC_ATOP);
- mPassCodeEditTexts[2] = findViewById(R.id.txt2);
- mPassCodeEditTexts[2].setTextColor(primaryColor);
- mPassCodeEditTexts[2].getBackground().setColorFilter(primaryColor, PorterDuff.Mode.SRC_ATOP);
- mPassCodeEditTexts[3] = findViewById(R.id.txt3);
- mPassCodeEditTexts[3].setTextColor(primaryColor);
- mPassCodeEditTexts[3].getBackground().setColorFilter(primaryColor, PorterDuff.Mode.SRC_ATOP);
- if (ACTION_CHECK.equals(getIntent().getAction())) {
- /// this is a pass code request; the user has to input the right value
- mPassCodeHdr.setText(R.string.pass_code_enter_pass_code);
- mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
- setCancelButtonEnabled(false); // no option to cancel
- } else if (ACTION_REQUEST_WITH_RESULT.equals(getIntent().getAction())) {
- if (savedInstanceState != null) {
- mConfirmingPassCode = savedInstanceState.getBoolean(PassCodeActivity.KEY_CONFIRMING_PASSCODE);
- mPassCodeDigits = savedInstanceState.getStringArray(PassCodeActivity.KEY_PASSCODE_DIGITS);
- }
- if(mConfirmingPassCode){
- //the app was in the passcodeconfirmation
- requestPassCodeConfirmation();
- }else{
- /// pass code preference has just been activated in Preferences;
- // will receive and confirm pass code value
- mPassCodeHdr.setText(R.string.pass_code_configure_your_pass_code);
- //mPassCodeHdr.setText(R.string.pass_code_enter_pass_code);
- // TODO choose a header, check iOS
- mPassCodeHdrExplanation.setVisibility(View.VISIBLE);
- setCancelButtonEnabled(true);
- }
- } else if (ACTION_CHECK_WITH_RESULT.equals(getIntent().getAction())) {
- /// pass code preference has just been disabled in Preferences;
- // will confirm user knows pass code, then remove it
- mPassCodeHdr.setText(R.string.pass_code_remove_your_pass_code);
- mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
- setCancelButtonEnabled(true);
- } else {
- throw new IllegalArgumentException("A valid ACTION is needed in the Intent passed to "
- + TAG);
- }
- setTextListeners();
- }
- /**
- * Enables or disables the cancel button to allow the user interrupt the ACTION
- * requested to the activity.
- *
- * @param enabled 'True' makes the cancel button available, 'false' hides it.
- */
- protected void setCancelButtonEnabled(boolean enabled){
- if(enabled){
- mBCancel.setVisibility(View.VISIBLE);
- mBCancel.setOnClickListener(new OnClickListener() {
- @Override
- public void onClick(View v) {
- finish();
- }
- });
- } else {
- mBCancel.setVisibility(View.GONE);
- mBCancel.setVisibility(View.INVISIBLE);
- mBCancel.setOnClickListener(null);
- }
- }
- /**
- * Binds the appropriate listeners to the input boxes receiving each digit of the pass code.
- */
- protected void setTextListeners() {
-
- /// First input field
- mPassCodeEditTexts[0].addTextChangedListener(new PassCodeDigitTextWatcher(0, false));
- /*------------------------------------------------
- * SECOND BOX
- -------------------------------------------------*/
- mPassCodeEditTexts[1].addTextChangedListener(new PassCodeDigitTextWatcher(1, false));
- mPassCodeEditTexts[1].setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) { // TODO WIP: event should be
- // used to control what's exactly happening with DEL, not any custom field...
- mPassCodeEditTexts[0].setText("");
- mPassCodeEditTexts[0].requestFocus();
- if (!mConfirmingPassCode) {
- mPassCodeDigits[0] = "";
- }
- mBChange = false;
- } else if (!mBChange) {
- mBChange = true;
- }
- return false;
- }
- });
- mPassCodeEditTexts[1].setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- /// TODO WIP: should take advantage of hasFocus to reduce processing
- if (mPassCodeEditTexts[0].getText().toString().equals("")) { // TODO WIP validation
- // could be done in a global way, with a single OnFocusChangeListener for all the
- // input fields
- mPassCodeEditTexts[0].requestFocus();
- }
- }
- });
-
-
- /*------------------------------------------------
- * THIRD BOX
- -------------------------------------------------*/
- mPassCodeEditTexts[2].addTextChangedListener(new PassCodeDigitTextWatcher(2, false));
- mPassCodeEditTexts[2].setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) {
- mPassCodeEditTexts[1].requestFocus();
- if (!mConfirmingPassCode) {
- mPassCodeDigits[1] = "";
- }
- mPassCodeEditTexts[1].setText("");
- mBChange = false;
- } else if (!mBChange) {
- mBChange = true;
- }
- return false;
- }
- });
- mPassCodeEditTexts[2].setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (mPassCodeEditTexts[0].getText().toString().equals("")) {
- mPassCodeEditTexts[0].requestFocus();
- } else if (mPassCodeEditTexts[1].getText().toString().equals("")) {
- mPassCodeEditTexts[1].requestFocus();
- }
- }
- });
- /*------------------------------------------------
- * FOURTH BOX
- -------------------------------------------------*/
- mPassCodeEditTexts[3].addTextChangedListener(new PassCodeDigitTextWatcher(3, true));
- mPassCodeEditTexts[3].setOnKeyListener(new View.OnKeyListener() {
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (keyCode == KeyEvent.KEYCODE_DEL && mBChange) {
- mPassCodeEditTexts[2].requestFocus();
- if (!mConfirmingPassCode) {
- mPassCodeDigits[2] = "";
- }
- mPassCodeEditTexts[2].setText("");
- mBChange = false;
- } else if (!mBChange) {
- mBChange = true;
- }
- return false;
- }
- });
- mPassCodeEditTexts[3].setOnFocusChangeListener(new View.OnFocusChangeListener() {
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (mPassCodeEditTexts[0].getText().toString().equals("")) {
- mPassCodeEditTexts[0].requestFocus();
- } else if (mPassCodeEditTexts[1].getText().toString().equals("")) {
- mPassCodeEditTexts[1].requestFocus();
- } else if (mPassCodeEditTexts[2].getText().toString().equals("")) {
- mPassCodeEditTexts[2].requestFocus();
- }
- }
- });
- } // end setTextListener
- /**
- * Processes the pass code entered by the user just after the last digit was in.
- *
- * Takes into account the action requested to the activity, the currently saved pass code and
- * the previously typed pass code, if any.
- */
- private void processFullPassCode() {
- if (ACTION_CHECK.equals(getIntent().getAction())) {
- if (checkPassCode()) {
- /// pass code accepted in request, user is allowed to access the app
- hideSoftKeyboard();
- finish();
- } else {
- showErrorAndRestart(R.string.pass_code_wrong, R.string.pass_code_enter_pass_code,
- View.INVISIBLE);
- }
- } else if (ACTION_CHECK_WITH_RESULT.equals(getIntent().getAction())) {
- if (checkPassCode()) {
- Intent resultIntent = new Intent();
- resultIntent.putExtra(KEY_CHECK_RESULT, true);
- setResult(RESULT_OK, resultIntent);
- hideSoftKeyboard();
- finish();
- } else {
- showErrorAndRestart(R.string.pass_code_wrong, R.string.pass_code_enter_pass_code,
- View.INVISIBLE);
- }
- } else if (ACTION_REQUEST_WITH_RESULT.equals(getIntent().getAction())) {
- /// enabling pass code
- if (!mConfirmingPassCode) {
- requestPassCodeConfirmation();
- } else if (confirmPassCode()) {
- /// confirmed: user typed the same pass code twice
- savePassCodeAndExit();
- } else {
- showErrorAndRestart(
- R.string.pass_code_mismatch, R.string.pass_code_configure_your_pass_code, View.VISIBLE
- );
- }
- }
- }
- private void hideSoftKeyboard() {
- View focusedView = getCurrentFocus();
- if (focusedView != null) {
- InputMethodManager inputMethodManager =
- (InputMethodManager) getSystemService(INPUT_METHOD_SERVICE);
- inputMethodManager.hideSoftInputFromWindow(
- focusedView.getWindowToken(),
- 0
- );
- }
- }
- private void showErrorAndRestart(int errorMessage, int headerMessage,
- int explanationVisibility) {
- Arrays.fill(mPassCodeDigits, null);
- Snackbar.make(findViewById(android.R.id.content), getString(errorMessage), Snackbar.LENGTH_LONG).show();
- mPassCodeHdr.setText(headerMessage); // TODO check if really needed
- mPassCodeHdrExplanation.setVisibility(explanationVisibility); // TODO check if really needed
- clearBoxes();
- }
- /**
- * Ask to the user for retyping the pass code just entered before saving it as the current pass
- * code.
- */
- protected void requestPassCodeConfirmation(){
- clearBoxes();
- mPassCodeHdr.setText(R.string.pass_code_reenter_your_pass_code);
- mPassCodeHdrExplanation.setVisibility(View.INVISIBLE);
- mConfirmingPassCode = true;
- }
- /**
- * Compares pass code entered by the user with the value currently saved in the app.
- *
- * @return 'True' if entered pass code equals to the saved one.
- */
- protected boolean checkPassCode(){
- SharedPreferences appPrefs = PreferenceManager
- .getDefaultSharedPreferences(getApplicationContext());
- String savedPassCodeDigits[] = new String[4];
- savedPassCodeDigits[0] = appPrefs.getString(PREFERENCE_PASSCODE_D1, null);
- savedPassCodeDigits[1] = appPrefs.getString(PREFERENCE_PASSCODE_D2, null);
- savedPassCodeDigits[2] = appPrefs.getString(PREFERENCE_PASSCODE_D3, null);
- savedPassCodeDigits[3] = appPrefs.getString(PREFERENCE_PASSCODE_D4, null);
- boolean result = true;
- for (int i = 0; i < mPassCodeDigits.length && result; i++) {
- result = (mPassCodeDigits[i] != null) &&
- mPassCodeDigits[i].equals(savedPassCodeDigits[i]);
- }
- return result;
- }
- /**
- * Compares pass code retyped by the user in the input fields with the value entered just
- * before.
- *
- * @return 'True' if retyped pass code equals to the entered before.
- */
- protected boolean confirmPassCode(){
- mConfirmingPassCode = false;
- boolean result = true;
- for (int i = 0; i < mPassCodeEditTexts.length && result; i++) {
- result = ((mPassCodeEditTexts[i].getText().toString()).equals(mPassCodeDigits[i]));
- }
- return result;
- }
- /**
- * Sets the input fields to empty strings and puts the focus on the first one.
- */
- protected void clearBoxes(){
- for (EditText mPassCodeEditText : mPassCodeEditTexts) {
- mPassCodeEditText.setText("");
- }
- mPassCodeEditTexts[0].requestFocus();
- }
- /**
- * Overrides click on the BACK arrow to correctly cancel ACTION_ENABLE or ACTION_DISABLE, while
- * preventing than ACTION_CHECK may be worked around.
- *
- * @param keyCode Key code of the key that triggered the down event.
- * @param event Event triggered.
- * @return 'True' when the key event was processed by this method.
- */
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event){
- if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount()== 0){
- if (ACTION_REQUEST_WITH_RESULT.equals(getIntent().getAction()) ||
- ACTION_CHECK_WITH_RESULT.equals(getIntent().getAction())) {
- finish();
- } // else, do nothing, but report that the key was consumed to stay alive
- return true;
- }
- return super.onKeyDown(keyCode, event);
- }
- /**
- * Saves the pass code input by the user as the current pass code.
- */
- protected void savePassCodeAndExit() {
- Intent resultIntent = new Intent();
- resultIntent.putExtra(KEY_PASSCODE,
- mPassCodeDigits[0] + mPassCodeDigits[1] + mPassCodeDigits[2] + mPassCodeDigits[3]);
- setResult(RESULT_OK, resultIntent);
- finish();
- }
- @Override
- public void onSaveInstanceState(Bundle outState) {
- super.onSaveInstanceState(outState);
- outState.putBoolean(PassCodeActivity.KEY_CONFIRMING_PASSCODE, mConfirmingPassCode);
- outState.putStringArray(PassCodeActivity.KEY_PASSCODE_DIGITS, mPassCodeDigits);
- }
- private class PassCodeDigitTextWatcher implements TextWatcher {
- private int mIndex = -1;
- private boolean mLastOne = false;
- /**
- * Constructor
- *
- * @param index Position in the pass code of the input field that will be bound to
- * this watcher.
- * @param lastOne 'True' means that watcher corresponds to the last position of the
- * pass code.
- */
- PassCodeDigitTextWatcher(int index, boolean lastOne) {
- mIndex = index;
- mLastOne = lastOne;
- if (mIndex < 0) {
- throw new IllegalArgumentException(
- "Invalid index in " + PassCodeDigitTextWatcher.class.getSimpleName() +
- " constructor"
- );
- }
- }
- private int next() {
- return mLastOne ? 0 : mIndex + 1;
- }
- /**
- * Performs several actions when the user types a digit in an input field:
- * - saves the input digit to the state of the activity; this will allow retyping the
- * pass code to confirm it.
- * - moves the focus automatically to the next field
- * - for the last field, triggers the processing of the full pass code
- *
- * @param s Changed text
- */
- @Override
- public void afterTextChanged(Editable s) {
- if (s.length() > 0) {
- if (!mConfirmingPassCode) {
- mPassCodeDigits[mIndex] = mPassCodeEditTexts[mIndex].getText().toString();
- }
- mPassCodeEditTexts[next()].requestFocus();
- if (mLastOne) {
- processFullPassCode();
- }
- } else {
- Log_OC.d(TAG, "Text box " + mIndex + " was cleaned");
- }
- }
- @Override
- public void beforeTextChanged(CharSequence s, int start, int count, int after) {
- // nothing to do
- }
- @Override
- public void onTextChanged(CharSequence s, int start, int before, int count) {
- // nothing to do
- }
- }
- }
|