package org.commcare.activities; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.os.Bundle; import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import org.commcare.CommCareApplication; import org.commcare.dalvik.R; import org.commcare.android.database.app.models.UserKeyRecord; import org.commcare.google.services.analytics.GoogleAnalyticsFields; import org.commcare.google.services.analytics.GoogleAnalyticsUtils; import org.commcare.views.ManagedUi; import org.commcare.views.UiElement; import org.commcare.views.dialogs.StandardAlertDialog; import org.javarosa.core.services.locale.Localization; /** * Activity that allows a user to set or reset their auth PIN * * @author Aliza Stone (astone@dimagi.com) */ @ManagedUi(R.layout.create_pin_view) public class CreatePinActivity extends SessionAwareCommCareActivity<CreatePinActivity> { private static final int MENU_REMEMBER_PW_AND_LOGOUT = Menu.FIRST; @UiElement(value = R.id.pin_entry) private EditText enterPinBox; @UiElement(value = R.id.pin_prompt_text) private TextView promptText; @UiElement(value = R.id.pin_cancel_button) private Button cancelButton; @UiElement(value = R.id.pin_confirm_button) private Button continueButton; @UiElement(value = R.id.extra_msg, locale = "pin.primed.mode.message") private TextView primedModeMessage; public static final String CHOSE_REMEMBER_PASSWORD = "chose-remember-password"; private static final String WAS_IN_CONFIRM_MODE = "was-in-confirm-mode"; private static final String FIRST_ROUND_PIN = "first-round-pin"; private String unhashedUserPassword; private UserKeyRecord userRecord; // Indicates whether the user is entering their PIN for the first time, or is confirming it private boolean inConfirmMode; private String firstRoundPin; @Override protected void onCreateSessionSafe(Bundle savedInstanceState) { super.onCreateSessionSafe(savedInstanceState); userRecord = CommCareApplication.instance().getRecordForCurrentUser(); unhashedUserPassword = CommCareApplication.instance().getSession().getLoggedInUser().getCachedPwd(); LoginMode loginMode = (LoginMode)getIntent().getSerializableExtra(LoginActivity.LOGIN_MODE); if (loginMode == LoginMode.PRIMED) { // Make user unable to cancel this activity if they were brought here by primed login cancelButton.setEnabled(false); // Show an explanatory message, since the user will have been brought here automatically // after logging in primedModeMessage.setVisibility(View.VISIBLE); // Clear the primed password userRecord.clearPrimedPassword(); CommCareApplication.instance().getCurrentApp().getStorage(UserKeyRecord.class).write(userRecord); } setListeners(); if (savedInstanceState != null && savedInstanceState.getBoolean(WAS_IN_CONFIRM_MODE)) { firstRoundPin = savedInstanceState.getString(FIRST_ROUND_PIN); setConfirmMode(); } else { setInitialEntryMode(); } } private void setListeners() { enterPinBox.addTextChangedListener(getPinTextWatcher(continueButton)); enterPinBox.setOnEditorActionListener(new TextView.OnEditorActionListener() { @Override public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { // processes the done/next keyboard action if (actionId == EditorInfo.IME_ACTION_DONE || actionId == EditorInfo.IME_ACTION_NEXT) { continueButton.performClick(); return true; } return false; } }); cancelButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { setResult(RESULT_CANCELED); finish(); } }); continueButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { if (!inConfirmMode) { processInitialPinEntry(); } else { processConfirmPinEntry(); } } }); } private void processInitialPinEntry() { firstRoundPin = enterPinBox.getText().toString(); setConfirmMode(); } private void processConfirmPinEntry() { String enteredPin = enterPinBox.getText().toString(); if (enteredPin.equals(firstRoundPin)) { assignPin(enteredPin); setResult(RESULT_OK); finish(); } else { Toast.makeText(this, Localization.get("pins.dont.match"), Toast.LENGTH_SHORT).show(); setInitialEntryMode(); } } private void setInitialEntryMode() { enterPinBox.setText(""); enterPinBox.requestFocus(); enterPinBox.setImeOptions(EditorInfo.IME_ACTION_NEXT); continueButton.setText(Localization.get("pin.continue.button")); if (userRecord.hasPinSet()) { promptText.setText(Localization.get("pin.directive.reset")); } else { promptText.setText(Localization.get("pin.directive.new")); } inConfirmMode = false; } private void setConfirmMode() { enterPinBox.setText(""); enterPinBox.requestFocus(); // open up the keyboard if it was dismissed InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); imm.showSoftInput(enterPinBox, 0); setTextEntryKeyboardAction(enterPinBox, EditorInfo.IME_ACTION_DONE); continueButton.setText(Localization.get("pin.confirm.button")); promptText.setText(Localization.get("pin.directive.confirm")); inConfirmMode = true; } private static void setTextEntryKeyboardAction(EditText textEntry, int action) { // bug/feature that requires setting the input type to null then changing the action type int inputType = textEntry.getInputType(); textEntry.setInputType(InputType.TYPE_NULL); textEntry.setImeOptions(action); textEntry.setInputType(inputType); } private void assignPin(String pin) { userRecord.assignPinToRecord(pin, unhashedUserPassword); CommCareApplication.instance().getCurrentApp().getStorage(UserKeyRecord.class).write(userRecord); GoogleAnalyticsUtils.reportFeatureUsage(GoogleAnalyticsFields.ACTION_SET_USER_PIN); } @Override public boolean onCreateOptionsMenu(Menu menu) { super.onCreateOptionsMenu(menu); menu.add(0, MENU_REMEMBER_PW_AND_LOGOUT, 0, Localization.get("remember.password.for.next.login")); return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == MENU_REMEMBER_PW_AND_LOGOUT) { launchRememberPasswordConfirmDialog(); } return super.onOptionsItemSelected(item); } private void launchRememberPasswordConfirmDialog() { StandardAlertDialog d = new StandardAlertDialog(this, Localization.get("remember.password.confirm.title"), Localization.get("remember.password.confirm.message")); d.setPositiveButton(Localization.get("dialog.ok"), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dismissAlertDialog(); userRecord.setPrimedPassword(unhashedUserPassword); CommCareApplication.instance().getCurrentApp().getStorage(UserKeyRecord.class).write(userRecord); Intent i = new Intent(); i.putExtra(CHOSE_REMEMBER_PASSWORD, true); setResult(RESULT_OK, i); finish(); } }); d.setNegativeButton(Localization.get("option.cancel"), new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dismissAlertDialog(); } }); showAlertDialog(d); } public static TextWatcher getPinTextWatcher(final Button confirmButton) { return 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) { if (s.length() == 4) { confirmButton.setEnabled(true); } else { confirmButton.setEnabled(false); } } }; } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); if (inConfirmMode) { outState.putBoolean(WAS_IN_CONFIRM_MODE, true); outState.putString(FIRST_ROUND_PIN, firstRoundPin); } } }