@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());
  }
  @Override
  protected void onStop() {
    if (delayedUIDChecker != null) {
      delayedUIDChecker.cancel();
    }

    super.onStop();
  }
  @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));
    }
  }