org.qeo.android.security.OtcDialog.java Source code

Java tutorial

Introduction

Here is the source code for org.qeo.android.security.OtcDialog.java

Source

/*
 * Copyright (c) 2015 - Qeo LLC
 *
 * The source code form of this Qeo Open Source Project component is subject
 * to the terms of the Clear BSD license.
 *
 * You can redistribute it and/or modify it under the terms of the Clear BSD
 * License (http://directory.fsf.org/wiki/License:ClearBSD). See LICENSE file
 * for more details.
 *
 * The Qeo Open Source Project also includes third party Open Source Software.
 * See LICENSE file for more details.
 */

package org.qeo.android.security;

import java.util.logging.Logger;

import org.qeo.android.service.QeoDefaults;
import org.qeo.android.service.ui.R;

import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.ColorStateList;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v4.app.DialogFragment;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.view.LayoutInflater;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.webkit.URLUtil;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

/**
 * This is the class that creates the OTC security dialog during the Qeo security set-up phase.
 */
public class OtcDialog extends DialogFragment {
    private static final Logger LOG = Logger.getLogger("OtcDialog");
    private static OtcDialog sOtpDialogFragment = null;

    /** The intent request code used for the OTC dialog. */
    static final int QRCODE_ACTIVITY_REQUEST_CODE = 1;

    private Button mButtonAuthenticate;
    private Button mButtonCancel;
    private Button mButtonScanQrCode;
    private EditText mOtc;
    private ColorStateList mOtcOriginalTextColors;
    private EditText mUrl;
    private OtcActivity mActivity;

    /**
     * Get the OTC retrieval dialog.
     * 
     * @return OtpDialog the OTC retrieval dialog fragment
     */
    static synchronized OtcDialog getInstance() {
        if (null == sOtpDialogFragment) {
            sOtpDialogFragment = new OtcDialog();
            sOtpDialogFragment.setRetainInstance(true);
        }

        return sOtpDialogFragment;
    }

    @Override
    public void onCancel(DialogInterface dialog) {
        mButtonCancelListener.onClick(null);
        super.onCancel(dialog);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        View view = inflater.inflate(R.layout.fragment_otc, container);
        getDialog().setTitle(getResources().getString(R.string.otcDialogTitle));

        mActivity = (OtcActivity) getActivity();
        mButtonAuthenticate = (Button) view.findViewById(R.id.authenticate);
        mButtonCancel = (Button) view.findViewById(R.id.cancel);
        mButtonScanQrCode = (Button) view.findViewById(R.id.scan_qr);
        if (!isCameraAvailable(getActivity())) {
            mButtonScanQrCode.setVisibility(View.GONE);
        }

        mOtc = (EditText) view.findViewById(R.id.otc);
        mOtc.addTextChangedListener(mTextWatcher);
        mOtcOriginalTextColors = mOtc.getTextColors();
        mUrl = (EditText) view.findViewById(R.id.url);
        mUrl.setText(QeoDefaults.getPublicUrl());
        mUrl.addTextChangedListener(mTextWatcher);

        mButtonAuthenticate.setOnClickListener(mButtonAuthenticateListener);
        mButtonCancel.setOnClickListener(mButtonCancelListener);
        mButtonScanQrCode.setOnClickListener(mButtonScanQrCodeListener);

        updateUi();
        return view;
    }

    @Override
    public void onDestroyView() {
        // Avoid disappearing of dialog on rotation change. This is necessary since we use setRetainInstance(true).
        if (getDialog() != null && getRetainInstance()) {
            getDialog().setDismissMessage(null);
        }
        super.onDestroyView();
    }

    /**
     * isCameraAvailable check if the device has a camera for QR Code scanning.
     * 
     * @return boolean true if device has at least 1 camera
     */
    private boolean isCameraAvailable(Context context) {
        return (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA)
                || context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_FRONT));
    }

    private final OnClickListener mButtonScanQrCodeListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            Intent intent = new Intent("com.google.zxing.client.android.SCAN");
            SharedPreferences.Editor edit = getActivity()
                    .getSharedPreferences(getString(R.string.app_prefs), Context.MODE_PRIVATE).edit();
            edit.putBoolean(getString(R.string.qr_scan_code), true).apply();
            intent.putExtra("SCAN_MODE", "QR_CODE_MODE");
            try {
                getActivity().startActivityForResult(intent, QRCODE_ACTIVITY_REQUEST_CODE);
            } catch (ActivityNotFoundException e1) {
                LOG.fine("No QR Application found, proposing to download one");
                QrDownloadDialog d = new QrDownloadDialog();
                d.setCancelable(false);
                d.setRetainInstance(true);
                d.show(getActivity().getSupportFragmentManager(), "QrDownload");
            }
        }
    };

    private final OnClickListener mButtonAuthenticateListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            final String otc = mOtc.getText().toString();
            final String url = mUrl.getText().toString();
            updateUi();
            mActivity.finishSecuritySetup(otc, url, true);
        }
    };

    private final OnClickListener mButtonCancelListener = new OnClickListener() {
        @Override
        public void onClick(View v) {
            updateUi();
            LOG.fine("User has canceled OTC popup dialog");
            mActivity.finishSecuritySetup("", "", false);
        }
    };

    private final TextWatcher mTextWatcher = new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
        }

        @Override
        public void onTextChanged(CharSequence s, int start, int before, int count) {
        }

        @Override
        public void afterTextChanged(Editable s) {
            updateUi();
        }
    };

    private enum InputValidationResult {
        INPUT_NOK, INPUT_NOK_OTC_SYNTAX_INVALID, INPUT_OK;
    }

    /**
     * updateUI: helper method to update (activate/de-activate) your buttons according to some minimal validation
     * checks: <br/>
     * 1) OTC Code should not be empty <br/>
     * 2) URL should not be empty <br/>
     * 3) URL should be a valid URL <br/>
     * 4) OTC length should be 8 digits <br/>
     * 5) OTC modulo 97 value should be 1 (2 digit error verification).
     */
    private void updateUi() {
        final String otcText = mOtc.getText().toString();
        final String urlText = mUrl.getText().toString();

        InputValidationResult ivr = validateInputData(otcText, urlText);

        if (ivr == InputValidationResult.INPUT_OK) {
            mOtc.setTextColor(mOtcOriginalTextColors);
            mButtonAuthenticate.setEnabled(true);
        } else {
            if (ivr == InputValidationResult.INPUT_NOK_OTC_SYNTAX_INVALID) {
                Toast.makeText(getActivity(), getResources().getString(R.string.otcIncorrect), Toast.LENGTH_LONG)
                        .show();
                mOtc.setTextColor(Color.RED);
            } else {
                mOtc.setTextColor(mOtcOriginalTextColors);
            }
            mButtonAuthenticate.setEnabled(false);
        }
    }

    /**
     * validInputData helper methods to check when Authenticate Button can be enabled.
     * 
     * @param otcText
     * @param urlText
     * @return boolean true if otcText and urlText are valid as input data
     */
    private InputValidationResult validateInputData(String otcText, String urlText) {
        if (null == otcText || null == urlText || TextUtils.isEmpty(otcText) || TextUtils.isEmpty(urlText)) {
            return InputValidationResult.INPUT_NOK;
        }

        if (!URLUtil.isValidUrl(urlText)) {
            return InputValidationResult.INPUT_NOK;
        }

        return InputValidationResult.INPUT_OK;
    }

    /**
     * setQrResult a helper method to split the QR Code and fill in the appropriate text fields.
     * 
     * @param qrResult semicolon separated string containing <otc>;<url>
     */
    void setQrResult(String qrResult) {
        final String[] seperated = qrResult.split(";");
        final String otc = (null != seperated[0]) ? seperated[0] : "";
        final String url = (null != seperated[1]) ? seperated[1] : "";
        mOtc.setText(otc);
        mUrl.setText(url);
        updateUi();
    }
}