Java tutorial
/* * Kontalk Android client * Copyright (C) 2017 Kontalk Devteam <devteam@kontalk.org> * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kontalk.ui; import java.net.SocketException; import java.util.HashMap; import java.util.Map; import com.afollestad.materialdialogs.DialogAction; import com.afollestad.materialdialogs.MaterialDialog; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.v4.content.ContextCompat; import android.view.MenuItem; import android.view.View; import android.view.WindowManager; import android.widget.Button; import android.widget.EditText; import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; import org.kontalk.Log; import org.kontalk.R; import org.kontalk.client.EndpointServer; import org.kontalk.client.NumberValidator; import org.kontalk.client.NumberValidator.NumberValidatorListener; import org.kontalk.crypto.PersonalKey; import org.kontalk.provider.UsersProvider; import org.kontalk.service.KeyPairGeneratorService; import org.kontalk.util.Preferences; import org.kontalk.util.SystemUtils; /** Manual validation code input. */ public class CodeValidation extends AccountAuthenticatorActionBarActivity implements NumberValidatorListener { private static final String TAG = NumberValidation.TAG; private EditText mCode; private Button mButton; private Button mFallbackButton; private Button mCallButton; private ProgressBar mProgress; private NumberValidator mValidator; private PersonalKey mKey; private String mName; private String mPhone; private String mPassphrase; boolean mForce; private EndpointServer.EndpointServerProvider mServerProvider; private byte[] mImportedPrivateKey; private byte[] mImportedPublicKey; Map<String, String> mTrustedKeys; private static final class RetainData { NumberValidator validator; Map<String, String> trustedKeys; RetainData() { } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.code_validation_screen); setupToolbar(true, false); mCode = (EditText) findViewById(R.id.validation_code); mButton = (Button) findViewById(R.id.send_button); mFallbackButton = (Button) findViewById(R.id.fallback_button); mCallButton = (Button) findViewById(R.id.code_validation_call); mProgress = (ProgressBar) findViewById(R.id.progressbar); // configuration change?? RetainData data = (RetainData) getLastCustomNonConfigurationInstance(); if (data != null) { mValidator = data.validator; if (mValidator != null) { startProgress(); mValidator.setListener(this); } mTrustedKeys = data.trustedKeys; } int requestCode = getIntent().getIntExtra("requestCode", -1); if (requestCode == NumberValidation.REQUEST_VALIDATION_CODE || getIntent().getStringExtra("sender") == null) { findViewById(R.id.code_validation_sender).setVisibility(View.GONE); findViewById(R.id.code_validation_intro2).setVisibility(View.GONE); ((TextView) findViewById(R.id.code_validation_intro)).setText(R.string.code_validation_intro_manual); } else { String challenge = getIntent().getStringExtra("challenge"); String sender = getIntent().getStringExtra("sender"); final TextView senderText = (TextView) findViewById(R.id.code_validation_sender); CharSequence textId1, textId2; if (NumberValidator.isMissedCall(sender) || NumberValidator.CHALLENGE_MISSED_CALL.equals(challenge)) { // reverse missed call textId1 = getText(R.string.code_validation_intro_missed_call); textId2 = getString(R.string.code_validation_intro2_missed_call, NumberValidator.getChallengeLength(sender)); mFallbackButton.setText(R.string.button_validation_fallback); mFallbackButton.setVisibility(View.VISIBLE); // show sender label and hide call button senderText.setText(sender); senderText.setVisibility(View.VISIBLE); mCallButton.setVisibility(View.GONE); mCode.setVisibility(View.VISIBLE); } else if (NumberValidator.CHALLENGE_CALLER_ID.equals(challenge)) { // user-initiated missed call textId1 = getText(R.string.code_validation_intro_callerid); textId2 = getText(R.string.code_validation_intro2_callerid); mFallbackButton.setText(R.string.button_validation_fallback_callerid); mFallbackButton.setVisibility(View.VISIBLE); // show call button and hide sender label mCallButton.setText(sender); mCallButton.setVisibility(View.VISIBLE); senderText.setVisibility(View.GONE); mCode.setVisibility(View.GONE); } else { // PIN code textId1 = getText(R.string.code_validation_intro); textId2 = getText(R.string.code_validation_intro2); mFallbackButton.setVisibility(View.GONE); // show sender label and hide call button senderText.setText(sender); senderText.setVisibility(View.VISIBLE); mCallButton.setVisibility(View.GONE); mCode.setVisibility(View.VISIBLE); } if (mCallButton.getVisibility() == View.VISIBLE) { mCallButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { SystemUtils.dial(CodeValidation.this, mCallButton.getText()); } }); } ((TextView) findViewById(R.id.code_validation_intro)).setText(textId1); ((TextView) findViewById(R.id.code_validation_intro2)).setText(textId2); } Intent i = getIntent(); mKey = i.getParcelableExtra(KeyPairGeneratorService.EXTRA_KEY); mName = i.getStringExtra("name"); mPhone = i.getStringExtra("phone"); mForce = i.getBooleanExtra("force", false); mPassphrase = i.getStringExtra("passphrase"); mImportedPrivateKey = i.getByteArrayExtra("importedPrivateKey"); mImportedPublicKey = i.getByteArrayExtra("importedPublicKey"); mTrustedKeys = (HashMap) i.getSerializableExtra("trustedKeys"); String server = i.getStringExtra("server"); if (server != null) mServerProvider = new EndpointServer.SingleServerProvider(server); else /* * FIXME HUGE problem here. If we already have a verification code, * how are we supposed to know from what server it came from?? * https://github.com/kontalk/androidclient/issues/118 */ mServerProvider = Preferences.getEndpointServerProvider(this); } /** Not used. */ @Override protected boolean isNormalUpNavigation() { return false; } @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case android.R.id.home: onBackPressed(); return true; default: return super.onOptionsItemSelected(item); } } @Override public void onBackPressed() { new MaterialDialog.Builder(this).title(R.string.title_confirm_cancel_registration) .content(R.string.confirm_cancel_registration).positiveText(android.R.string.ok) .positiveColorRes(R.color.button_danger).negativeText(android.R.string.cancel) .onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog materialDialog, @NonNull DialogAction dialogAction) { // we are going back voluntarily Preferences.clearRegistrationProgress(); CodeValidation.super.onBackPressed(); } }).show(); } /** No search here. */ @Override public boolean onSearchRequested() { return false; } /** Returning the validator thread. */ @Override public Object onRetainCustomNonConfigurationInstance() { RetainData data = new RetainData(); data.validator = mValidator; data.trustedKeys = mTrustedKeys; return data; } @Override protected void onStart() { super.onStart(); // start a users resync in the meantime UsersProvider.resync(this); } @Override protected void onStop() { super.onStop(); if (isFinishing()) abort(true); } void keepScreenOn(boolean active) { if (active) getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); else getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } private void error(int message) { new MaterialDialog.Builder(this).content(message).positiveText(android.R.string.ok).show(); } public void doFallback(View view) { new MaterialDialog.Builder(this).title(R.string.title_fallback).content(R.string.msg_fallback) .icon(ContextCompat.getDrawable(this, android.R.drawable.ic_dialog_info)) .positiveText(android.R.string.ok).onPositive(new MaterialDialog.SingleButtonCallback() { @Override public void onClick(@NonNull MaterialDialog dialog, @NonNull DialogAction which) { Intent i = new Intent(); i.putExtra("force", mForce); setResult(NumberValidation.RESULT_FALLBACK, i); finish(); } }).negativeText(android.R.string.cancel).show(); } public void validateCode(View view) { String code = null; String challenge = getIntent().getStringExtra("challenge"); if (!NumberValidator.CHALLENGE_CALLER_ID.equals(challenge)) { code = mCode.getText().toString().trim(); if (code.length() == 0) { error(R.string.msg_invalid_code); return; } } startProgress(); // send the code boolean imported = (mImportedPrivateKey != null && mImportedPublicKey != null); mServerProvider.reset(); mValidator = new NumberValidator(this, mServerProvider, mName, mPhone, imported ? null : mKey, mPassphrase); mValidator.setListener(this); if (imported) mValidator.importKey(mImportedPrivateKey, mImportedPublicKey); mValidator.manualInput(code); mValidator.start(); } private void enableControls(boolean enabled) { mButton.setEnabled(enabled); mFallbackButton.setEnabled(enabled); mCallButton.setEnabled(enabled); mCode.setEnabled(enabled); } private void startProgress() { mProgress.setVisibility(View.VISIBLE); enableControls(false); keepScreenOn(true); } void abort(boolean ending) { if (!ending) { mProgress.setVisibility(View.INVISIBLE); enableControls(true); } else { // ending - clear registration progress Preferences.clearRegistrationProgress(); } keepScreenOn(false); if (mValidator != null) { mValidator.shutdown(); mValidator = null; } } @Override public void onError(NumberValidator v, final Throwable e) { Log.e(TAG, "validation error.", e); runOnUiThread(new Runnable() { public void run() { int msgId; if (e instanceof SocketException) msgId = R.string.err_validation_network_error; else msgId = R.string.err_validation_error; Toast.makeText(CodeValidation.this, msgId, Toast.LENGTH_LONG).show(); abort(false); } }); } @Override public void onServerCheckFailed(NumberValidator v) { runOnUiThread(new Runnable() { public void run() { Toast.makeText(CodeValidation.this, R.string.err_validation_server_not_supported, Toast.LENGTH_LONG) .show(); abort(false); } }); } @Override public void onValidationRequested(NumberValidator v, String sender, String challenge) { // not used. } @Override public void onValidationFailed(NumberValidator v, int reason) { // not used. } @Override public void onAuthTokenReceived(final NumberValidator v, final byte[] privateKeyData, final byte[] publicKeyData) { Log.d(TAG, "got authentication token!"); runOnUiThread(new Runnable() { @Override public void run() { abort(true); Intent i = new Intent(); i.putExtra(NumberValidation.PARAM_SERVER_URI, v.getServer().toString()); i.putExtra(NumberValidation.PARAM_PUBLICKEY, publicKeyData); i.putExtra(NumberValidation.PARAM_PRIVATEKEY, privateKeyData); i.putExtra(NumberValidation.PARAM_TRUSTED_KEYS, (HashMap) mTrustedKeys); i.putExtra(NumberValidation.PARAM_CHALLENGE, v.getServerChallenge()); setResult(RESULT_OK, i); finish(); } }); } @Override public void onAuthTokenFailed(NumberValidator v, int reason) { Log.e(TAG, "authentication token request failed (" + reason + ")"); runOnUiThread(new Runnable() { @Override public void run() { keepScreenOn(false); int resId; String challenge = getIntent().getStringExtra("challenge"); if (NumberValidator.CHALLENGE_CALLER_ID.equals(challenge)) { // we are verifying through user-initiated missed call // notify the user that the verification didn't succeed resId = R.string.err_authentication_failed_callerid; } else { // we are verifying through PIN-based challenge // notify the user that the challenge code wasn't accepted resId = R.string.err_authentication_failed; } Toast.makeText(CodeValidation.this, resId, Toast.LENGTH_LONG).show(); abort(false); } }); } }