org.jnrain.mobile.accounts.kbs.KBSRegisterActivity.java Source code

Java tutorial

Introduction

Here is the source code for org.jnrain.mobile.accounts.kbs.KBSRegisterActivity.java

Source

/*
 * Copyright 2013 JNRain
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may
 * not use this file except in compliance with the License. You may obtain a
 * copy of the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package org.jnrain.mobile.accounts.kbs;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.util.Timer;
import java.util.TimerTask;
import java.util.regex.Pattern;

import name.xen0n.cytosol.ui.util.DialogHelper;
import name.xen0n.cytosol.ui.util.FormatHelper;
import name.xen0n.cytosol.ui.widget.GuidedEditText;
import name.xen0n.cytosol.util.TelephonyHelper;

import org.apache.commons.io.output.ByteArrayOutputStream;
import org.jnrain.mobile.R;
import org.jnrain.mobile.ui.base.JNRainActivity;
import org.jnrain.mobile.ui.base.RegisterPoint;
import org.jnrain.mobile.ui.kbs.KBSUIConstants;

import roboguice.inject.InjectView;
import android.app.ProgressDialog;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Bitmap.CompressFormat;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.text.Editable;
import android.text.TextUtils;
import android.text.TextWatcher;
import android.text.method.LinkMovementMethod;
import android.util.SparseBooleanArray;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnFocusChangeListener;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import android.widget.CompoundButton.OnCheckedChangeListener;
import android.widget.ImageView;
import android.widget.TextView;

public class KBSRegisterActivity extends JNRainActivity<KBSRegisterResult> implements RegisterPoint {
    public static final String PARAM_NEW_UID = "org.jnrain.mobile.kbs.newuid";
    public static final String PARAM_NEW_PSW = "org.jnrain.mobile.kbs.newpsw";

    @InjectView(R.id.textRegisterDisclaimer)
    TextView textRegisterDisclaimer;

    @InjectView(R.id.editNewUID)
    GuidedEditText editNewUID;
    @InjectView(R.id.editNewEmail)
    GuidedEditText editNewEmail;
    @InjectView(R.id.editNewPassword)
    GuidedEditText editNewPassword;
    @InjectView(R.id.editRetypeNewPassword)
    GuidedEditText editRetypeNewPassword;
    @InjectView(R.id.editNewNickname)
    GuidedEditText editNewNickname;

    @InjectView(R.id.editStudID)
    GuidedEditText editStudID;
    @InjectView(R.id.editRealName)
    GuidedEditText editRealName;

    @InjectView(R.id.textEthnicMinorityIndicator)
    TextView textEthnicMinorityIndicator;
    @InjectView(R.id.checkIsEthnicMinority)
    CheckBox checkIsEthnicMinority;

    @InjectView(R.id.checkUseCurrentPhone)
    CheckBox checkUseCurrentPhone;
    @InjectView(R.id.editPhone)
    GuidedEditText editPhone;

    @InjectView(R.id.imageRegCaptcha)
    ImageView imageRegCaptcha;
    @InjectView(R.id.editCaptcha)
    GuidedEditText editCaptcha;

    @InjectView(R.id.btnSubmitRegister)
    Button btnSubmitRegister;

    // private static final String TAG = "KBSRegisterActivity";
    private static final int FIXED_VALIDATION_FIELD_IDS[] = { R.id.editNewUID, R.id.editNewEmail,
            R.id.editNewPassword, R.id.editRetypeNewPassword, R.id.editNewNickname, R.id.editStudID,
            R.id.editRealName,
            // R.id.editPhone,
            R.id.editCaptcha };

    private static final String EMAIL_CHECK_RE = "^[0-9A-Za-z._\\-]+(?:[+][0-9A-Za-z._\\-]+)?@[0-9A-Za-z._\\-]+\\.[A-Za-z]+$";
    private static final Pattern EMAIL_CHECKER = Pattern.compile(EMAIL_CHECK_RE);

    private static final String IDENT_CHECK_RE = "(?:\\d{10})|(?:[Ss]\\d{9})|(?:\\d{7})";
    private static final Pattern IDENT_CHECKER = Pattern.compile(IDENT_CHECK_RE);

    private ProgressDialog loadingDlg;
    private Handler mHandler;

    private String currentPhoneNumber;
    private boolean isCurrentPhoneNumberAvailable;

    private Timer delayedUIDChecker;
    private long lastUIDCheckTime;

    private SparseBooleanArray validatedMap;

    protected synchronized void initValidationMapping() {
        if (validatedMap == null) {
            validatedMap = new SparseBooleanArray();

            for (int id : FIXED_VALIDATION_FIELD_IDS) {
                validatedMap.put(id, false);
            }

            // dynamic fields
            validatedMap.put(R.id.editPhone, false);
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.kbs_register);

        mHandler = new Handler() {
            @Override
            public void handleMessage(Message msg) {
                loadingDlg = DialogHelper.showProgressDialog(KBSRegisterActivity.this,
                        R.string.check_updates_dlg_title, R.string.please_wait, false, false);
            }
        };

        lastUIDCheckTime = 0;

        initValidationMapping();

        // instance state
        if (savedInstanceState != null) {
            /*
             * editNewUID.onRestoreInstanceState(savedInstanceState
             * .getParcelable("newUID"));
             * editNewEmail.onRestoreInstanceState(savedInstanceState
             * .getParcelable("newEmail"));
             * editNewPassword.onRestoreInstanceState(savedInstanceState
             * .getParcelable("newPass"));
             * editRetypeNewPassword.onRestoreInstanceState
             * (savedInstanceState .getParcelable("repeatPass"));
             * editNewNickname.onRestoreInstanceState(savedInstanceState
             * .getParcelable("newNickname"));
             * editStudID.onRestoreInstanceState(savedInstanceState
             * .getParcelable("studID"));
             * editRealName.onRestoreInstanceState(savedInstanceState
             * .getParcelable("realname"));
             * editPhone.onRestoreInstanceState(savedInstanceState
             * .getParcelable("phone"));
             * editCaptcha.onRestoreInstanceState(savedInstanceState
             * .getParcelable("captcha"));
             */

            // captcha image
            byte[] captchaPNG = savedInstanceState.getByteArray("captchaImage");
            ByteArrayInputStream captchaStream = new ByteArrayInputStream(captchaPNG);
            try {
                Drawable captchaDrawable = BitmapDrawable.createFromStream(captchaStream, "src");
                imageRegCaptcha.setImageDrawable(captchaDrawable);
            } finally {
                try {
                    captchaStream.close();
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

        // event handlers
        // UID
        editNewUID.addTextChangedListener(new StrippedDownTextWatcher() {
            long lastCheckTime = 0;
            String lastUID;

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                // FIXME: use monotonic clock...
                long curtime = System.currentTimeMillis();
                if (curtime - lastCheckTime >= KBSUIConstants.REG_CHECK_UID_INTERVAL_MILLIS) {
                    String uid = s.toString();

                    // don't check at the first char
                    if (uid.length() > 1) {
                        checkUIDAvailability(uid, curtime);
                    }

                    lastCheckTime = curtime;
                    lastUID = uid;

                    // schedule a new delayed check
                    if (delayedUIDChecker != null) {
                        delayedUIDChecker.cancel();
                        delayedUIDChecker.purge();
                    }
                    delayedUIDChecker = new Timer();
                    delayedUIDChecker.schedule(new TimerTask() {
                        @Override
                        public void run() {
                            final String uid = getUID();

                            if (uid != lastUID) {
                                runOnUiThread(new Runnable() {
                                    @Override
                                    public void run() {
                                        checkUIDAvailability(uid, System.currentTimeMillis());
                                    }
                                });

                                lastUID = uid;
                            }
                        }
                    }, KBSUIConstants.REG_CHECK_UID_INTERVAL_MILLIS);
                }
            }
        });

        editNewUID.setOnFocusChangeListener(new OnFocusChangeListener() {
            @Override
            public void onFocusChange(View v, boolean hasFocus) {
                if (!hasFocus) {
                    // lost focus, force check uid availability
                    checkUIDAvailability(getUID(), System.currentTimeMillis());
                } else {
                    // inputting, temporarily clear that notice
                    updateUIDAvailability(false, 0);
                }
            }
        });

        // E-mail
        editNewEmail.addTextChangedListener(new StrippedDownTextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (TextUtils.isEmpty(s)) {
                    updateValidation(editNewEmail, false, true, R.string.reg_email_empty);
                    return;
                }

                if (!EMAIL_CHECKER.matcher(s).matches()) {
                    updateValidation(editNewEmail, false, true, R.string.reg_email_malformed);
                    return;
                }

                updateValidation(editNewEmail, true, true, R.string.ok_short);
            }
        });

        // Password
        editNewPassword.addTextChangedListener(new StrippedDownTextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (TextUtils.isEmpty(s)) {
                    updateValidation(editNewPassword, false, true, R.string.reg_psw_empty);
                    return;
                }

                if (s.length() < 6) {
                    updateValidation(editNewPassword, false, true, R.string.reg_psw_too_short);
                    return;
                }

                if (s.length() > 39) {
                    updateValidation(editNewPassword, false, true, R.string.reg_psw_too_long);
                    return;
                }

                if (getUID().equalsIgnoreCase(s.toString())) {
                    updateValidation(editNewPassword, false, true, 0);
                }

                updateValidation(editNewPassword, true, true, R.string.ok_short);

                updateRetypedPasswordCorrectness();
            }
        });

        // Retype password
        editRetypeNewPassword.addTextChangedListener(new StrippedDownTextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                updateRetypedPasswordCorrectness();
            }
        });

        // Nickname
        editNewNickname.addTextChangedListener(new StrippedDownTextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (TextUtils.isEmpty(s)) {
                    updateValidation(editNewNickname, false, true, R.string.reg_nick_empty);
                    return;
                }

                updateValidation(editNewNickname, true, true, R.string.ok_short);
            }
        });

        // Student ID
        editStudID.addTextChangedListener(new StrippedDownTextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (TextUtils.isEmpty(s)) {
                    updateValidation(editStudID, false, true, R.string.reg_stud_id_empty);
                    return;
                }

                if (!IDENT_CHECKER.matcher(s).matches()) {
                    updateValidation(editStudID, false, true, R.string.reg_stud_id_malformed);
                    return;
                }

                updateValidation(editStudID, true, true, R.string.ok_short);
            }
        });

        // Real name
        editRealName.addTextChangedListener(new StrippedDownTextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (TextUtils.isEmpty(s)) {
                    updateValidation(editRealName, false, true, R.string.reg_realname_empty);
                    return;
                }

                updateValidation(editRealName, true, true, R.string.ok_short);
            }
        });

        // Phone
        editPhone.addTextChangedListener(new StrippedDownTextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (TextUtils.isEmpty(s)) {
                    updateValidation(editPhone, false, true, R.string.reg_phone_empty);
                    return;
                }

                if (!TextUtils.isDigitsOnly(s)) {
                    updateValidation(editPhone, false, true, R.string.reg_phone_onlynumbers);
                    return;
                }

                updateValidation(editPhone, true, true, R.string.ok_short);
            }
        });

        // Captcha
        editCaptcha.addTextChangedListener(new StrippedDownTextWatcher() {
            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                if (TextUtils.isEmpty(s)) {
                    updateValidation(editCaptcha, false, true, R.string.reg_captcha_empty);
                    return;
                }

                updateValidation(editCaptcha, true, true, R.string.ok_short);
            }
        });

        // Use current phone
        checkUseCurrentPhone.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setUseCurrentPhone(isChecked);
            }
        });

        // Is ethnic minority
        checkIsEthnicMinority.setOnCheckedChangeListener(new OnCheckedChangeListener() {
            @Override
            public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
                setEthnicMinority(isChecked);
            }
        });

        // Submit form!
        btnSubmitRegister.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View arg0) {
                // progress dialog
                mHandler.sendMessage(new Message());

                // prevent repeated requests
                setSubmitButtonEnabled(false);

                // fire our biiiiiig request!
                // TODO: ??23333
                final String uid = getUID();
                final String psw = editNewPassword.getText().toString();
                makeSpiceRequest(
                        new KBSRegisterRequest(uid, psw, editNewNickname.getText().toString(), getRealName(),
                                editStudID.getText().toString(), editNewEmail.getText().toString(), getPhone(),
                                editCaptcha.getText().toString(), 2),
                        new KBSRegisterRequestListener(KBSRegisterActivity.this, uid, psw));
            }
        });

        // interface init
        // UID availability
        updateUIDAvailability(false, 0);

        // HTML-formatted register disclaimer
        FormatHelper.setHtmlText(this, textRegisterDisclaimer, R.string.register_disclaimer);
        textRegisterDisclaimer.setMovementMethod(LinkMovementMethod.getInstance());

        // is ethnic minority defaults to false
        checkIsEthnicMinority.setChecked(false);
        setEthnicMinority(false);

        // current phone number
        currentPhoneNumber = TelephonyHelper.getPhoneNumber(this);
        isCurrentPhoneNumberAvailable = currentPhoneNumber != null;

        if (isCurrentPhoneNumberAvailable) {
            // display the obtained number as hint
            FormatHelper.setHtmlText(this, checkUseCurrentPhone, R.string.field_use_current_phone,
                    currentPhoneNumber);
        } else {
            // phone number unavailable, disable the choice
            checkUseCurrentPhone.setEnabled(false);
            checkUseCurrentPhone.setVisibility(View.GONE);
        }

        // default to use current phone number if available
        checkUseCurrentPhone.setChecked(isCurrentPhoneNumberAvailable);
        setUseCurrentPhone(isCurrentPhoneNumberAvailable);

        if (savedInstanceState == null) {
            // issue preflight request
            // load captcha in success callback
            this.makeSpiceRequest(new KBSRegisterRequest(), new KBSRegisterRequestListener(this));
        }
    }

    @Override
    protected void onStop() {
        if (delayedUIDChecker != null) {
            delayedUIDChecker.cancel();
        }

        super.onStop();
    }

    @Override
    protected void onSaveInstanceState(Bundle outState) {
        super.onSaveInstanceState(outState);

        /*
         * outState.putParcelable("newUID",
         * editNewUID.onSaveInstanceState()); outState.putParcelable(
         * "newEmail", editNewEmail.onSaveInstanceState());
         * outState.putParcelable( "newPass",
         * editNewPassword.onSaveInstanceState()); outState.putParcelable(
         * "repeatPass", editRetypeNewPassword.onSaveInstanceState());
         * outState.putParcelable( "newNickname",
         * editNewNickname.onSaveInstanceState());
         * outState.putParcelable("studID",
         * editStudID.onSaveInstanceState()); outState.putParcelable(
         * "realname", editRealName.onSaveInstanceState());
         * outState.putParcelable("phone", editPhone.onSaveInstanceState());
         * outState.putParcelable("captcha",
         * editCaptcha.onSaveInstanceState());
         */

        // captcha image
        ByteArrayOutputStream captchaOutStream = new ByteArrayOutputStream();
        Bitmap captchaBitmap = ((BitmapDrawable) imageRegCaptcha.getDrawable()).getBitmap();
        captchaBitmap.compress(CompressFormat.PNG, 100, captchaOutStream);

        outState.putByteArray("captchaImage", captchaOutStream.toByteArray());
    }

    public ProgressDialog getLoadingDialog() {
        return loadingDlg;
    }

    @Override
    public void onRegisterSuccess(String uid, String psw) {
        final Intent resultIntent = new Intent();

        resultIntent.putExtra(PARAM_NEW_UID, uid);
        resultIntent.putExtra(PARAM_NEW_PSW, psw);

        setResult(RESULT_OK, resultIntent);
        finish();
    }

    public boolean isUseCurrentPhone() {
        return checkUseCurrentPhone.isChecked();
    }

    protected String getPhoneInput() {
        return editPhone.getText().toString().trim();
    }

    public String getPhone() {
        if (!isCurrentPhoneNumberAvailable) {
            return getPhoneInput();
        }

        return checkUseCurrentPhone.isChecked() ? currentPhoneNumber : getPhoneInput();
    }

    public synchronized void setUseCurrentPhone(boolean useCurrent) {
        editPhone.setEnabled(!useCurrent);
        editPhone.setVisibility(useCurrent ? View.GONE : View.VISIBLE);
    }

public String getRealName() {
    String name = editRealName.getText().toString().trim();

    // TODO: consider foreign students who have a "" suffix
    // appended to their last names WRITTEN IN UPPERCASE
    // Note this is Jiangnan University-specific.
    return checkIsEthnicMinority.isChecked() ? ('' + name) : name;
}

    public synchronized void setEthnicMinority(boolean isEthnicMinority) {
        textEthnicMinorityIndicator.setVisibility(isEthnicMinority ? View.VISIBLE : View.GONE);
    }

    @Override
    public void updateCaptcha(Drawable captcha) {
        imageRegCaptcha.setImageDrawable(captcha);
        imageRegCaptcha.invalidate();
    }

    @Override
    public void setSubmitButtonEnabled(boolean enabled) {
        btnSubmitRegister.setEnabled(enabled);
    }

    @Override
    public String getUID() {
        return editNewUID.getText().toString().trim();
    }

    @Override
    public void checkUIDAvailability(String uid, long timestamp) {
        if (uid.length() == 0) {
            // directly "return"
            updateUIDAvailability(false, R.string.reg_uid_empty);
            return;
        }

        makeSpiceRequest(new KBSCheckIDRequest(uid), new KBSCheckIDRequestListener(this, timestamp));
    }

    @Override
    public void notifyUIDAvailability(int status, long timestamp) {
        switch (status) {
        case 0:
            // Success
            updateUIDAvailability(true, R.string.ok_short, timestamp);
            break;

        case 1:
            // UID empty
            updateUIDAvailability(false, R.string.reg_uid_empty, timestamp);
            break;

        case 2:
            // Already exists
            updateUIDAvailability(false, R.string.reg_uid_exists, timestamp);
            break;

        case 3:
            // Too short
            updateUIDAvailability(false, R.string.reg_uid_too_short, timestamp);
            break;

        case 4:
            // Too long
            updateUIDAvailability(false, R.string.reg_uid_too_long, timestamp);
            break;

        case 5:
            // Forbidden characters
            updateUIDAvailability(false, R.string.reg_uid_malformed, timestamp);
            break;

        case 6:
            // Banned
            updateUIDAvailability(false, R.string.reg_uid_banned, timestamp);
            break;

        default:
            break;
        }
    }

    protected synchronized void updateUIDAvailability(boolean ok, int statusResId, long timestamp) {
        if (timestamp < lastUIDCheckTime) {
            // results of an earlier check arrived late, discard it
            return;
        }

        updateUIDAvailability(ok, statusResId);
    }

    protected synchronized void updateUIDAvailability(boolean ok, int statusResId) {
        if (statusResId != 0) {
            updateValidation(editNewUID, ok, true, statusResId);
        } else {
            updateValidation(editNewUID, ok, false, "");
        }
    }

    protected synchronized void updateRetypedPasswordCorrectness() {
        CharSequence retypedPassword = editRetypeNewPassword.getText();
        // don't show mismatch message if retyped password field is empty

        if (TextUtils.isEmpty(retypedPassword)) {
            updateValidation(editRetypeNewPassword, false, false, "");
            return;
        }

        if (!editNewPassword.getText().toString().equals(retypedPassword.toString())) {
            updateValidation(editRetypeNewPassword, false, true, R.string.reg_psw_mismatch);
            return;
        }

        updateValidation(editRetypeNewPassword, true, true, R.string.ok_short);
    }

    protected void setValidationColor(GuidedEditText editText, boolean ok) {
        Resources res = this.getResources();
        editText.setGuideTextColor(
                ok ? res.getColorStateList(R.color.jnrain_green_dark) : res.getColorStateList(R.color.error_red));
    }

    protected void updateValidation(GuidedEditText editText, boolean ok, boolean visible, CharSequence text) {
        setValidationColor(editText, ok);
        editText.setGuideVisible(visible);
        editText.setGuideText(text);

        validatedMap.put(editText.getId(), ok);
        doValidation();
    }

    protected void updateValidation(GuidedEditText editText, boolean ok, boolean visible, int resId) {
        setValidationColor(editText, ok);
        editText.setGuideVisible(visible);
        editText.setGuideText(resId);

        validatedMap.put(editText.getId(), ok);
        doValidation();
    }

    protected void doValidation() {
        boolean passed = true;

        // fixed fields
        for (int itemId : FIXED_VALIDATION_FIELD_IDS) {
            if (!validatedMap.get(itemId)) {
                passed = false;
                break;
            }
        }

        // dynamic fields
        if (!isUseCurrentPhone() && !validatedMap.get(R.id.editPhone)) {
            passed = false;
        }

        setSubmitButtonEnabled(passed);
    }

    private abstract class StrippedDownTextWatcher implements TextWatcher {
        @Override
        public void afterTextChanged(Editable s) {
            // stripped down to nothing
        }

        @Override
        public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            // ditto
        }
    }
}