Java tutorial
/* * 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 } } }