package io.homeassistant.android.view; import android.app.AlertDialog; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.content.SharedPreferences; import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.support.annotation.Nullable; import android.support.annotation.StringRes; import android.support.design.widget.TextInputEditText; import android.support.design.widget.TextInputLayout; import android.text.Editable; import android.text.TextUtils; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.inputmethod.InputMethodManager; import android.widget.Button; import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.Toast; import io.homeassistant.android.Common; import io.homeassistant.android.HassActivity; import io.homeassistant.android.R; import io.homeassistant.android.Utils; import okhttp3.HttpUrl; import static io.homeassistant.android.CommunicationHandler.FAILURE_REASON_GENERIC; import static io.homeassistant.android.CommunicationHandler.FAILURE_REASON_SSL_MISMATCH; import static io.homeassistant.android.CommunicationHandler.FAILURE_REASON_WRONG_PASSWORD; public class LoginView extends LinearLayout { private TextInputLayout urlInputLayout; private TextInputEditText urlInput; private TextInputLayout passwordInputLayout; private TextInputEditText passwordInput; private Button connectButton; private ProgressBar progress; private ConnectivityManager connectivityManager; private BroadcastReceiver networkReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { updateConnectButton(); } }; public LoginView(Context context) { super(context); } public LoginView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public LoginView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); } @Override protected void onFinishInflate() { super.onFinishInflate(); urlInputLayout = (TextInputLayout) findViewById(R.id.url_input_layout); urlInput = (TextInputEditText) findViewById(R.id.url_input); passwordInputLayout = (TextInputLayout) findViewById(R.id.password_input_layout); passwordInput = (TextInputEditText) findViewById(R.id.password_input); connectButton = (Button) findViewById(R.id.connect_button); progress = (ProgressBar) findViewById(android.R.id.progress); urlInputLayout.setErrorEnabled(true); if (!Utils.getUrl(getContext()).isEmpty()) { urlInput.setText(Utils.getUrl(getContext())); } urlInput.addTextChangedListener(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) { String currentInput = s.toString(); if (!currentInput.matches("http(s)?://[\\s\\S]*")) { currentInput = "http://".concat(currentInput); } urlInputLayout.setError(HttpUrl.parse(currentInput) == null && !s.toString().isEmpty() ? getResources().getString(R.string.invalid_url_scheme) : null); updateConnectButton(); } }); passwordInputLayout.setPasswordVisibilityToggleEnabled(true); passwordInput.addTextChangedListener(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) { updateConnectButton(); } }); // Hint that existing password is used when there is one available (and it isn't a fake one for open instances) String savedPassword = Utils.getPassword(getContext()); if (!savedPassword.isEmpty() && !savedPassword.equals(Common.NO_PASSWORD)) passwordInputLayout.setHint(getResources().getString(R.string.hint_password_existing)); connectButton.setOnClickListener(v -> { SharedPreferences.Editor prefs = Utils.getPrefs(getContext()).edit(); // Tidying up the URL format String url = urlInput.getText().toString(); if (!url.matches("http(s)?://[\\s\\S]*")) { url = "http://".concat(url); } if (url.charAt(url.length() - 1) == '/') { url = url.substring(0, url.length() - 1); } prefs.putString(Common.PREF_HASS_URL_KEY, url); String password = passwordInput.getText().toString(); if (!password.isEmpty()) { prefs.putString(Common.PREF_HASS_PASSWORD_KEY, password); } else if (Utils.getPassword(getContext()).isEmpty()) { // Set fake password to support open instances if none was provided prefs.putString(Common.PREF_HASS_PASSWORD_KEY, Common.NO_PASSWORD); } prefs.apply(); InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); imm.hideSoftInputFromWindow(getWindowToken(), 0); connectButton.setVisibility(INVISIBLE); progress.setVisibility(VISIBLE); ((HassActivity) getContext()).attemptLogin(); }); connectivityManager = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE); updateConnectButton(); } @Override protected void onAttachedToWindow() { super.onAttachedToWindow(); getContext().registerReceiver(networkReceiver, new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)); } @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); getContext().unregisterReceiver(networkReceiver); } private boolean isConnected() { NetworkInfo info = connectivityManager.getActiveNetworkInfo(); return info != null && info.isConnected(); } /** * Update the state of the connect button: require a valid URL and network access to allow clicking */ private void updateConnectButton() { connectButton.setEnabled(isConnected() && !urlInput.getText().toString().isEmpty() && TextUtils.isEmpty(urlInputLayout.getError())); connectButton.setText(isConnected() ? R.string.button_connect : R.string.button_connect_no_network); } public void showLoginError(int reason) { progress.setVisibility(INVISIBLE); connectButton.setVisibility(VISIBLE); @StringRes int message; switch (reason) { default: case FAILURE_REASON_GENERIC: message = R.string.login_error_generic; break; case FAILURE_REASON_WRONG_PASSWORD: message = R.string.login_error_wrong_password; break; case FAILURE_REASON_SSL_MISMATCH: new AlertDialog.Builder(getContext()) .setTitle(R.string.dialog_login_error_ssl_mismatch_title) .setMessage(R.string.dialog_login_error_ssl_mismatch_message) .setPositiveButton(android.R.string.cancel, null) .setNeutralButton(R.string.dialog_login_error_ssl_mismatch_ignore, (dialog, which) -> { Utils.addAllowedHostMismatch(getContext(), HttpUrl.parse(Utils.getUrl(getContext())).host()); connectButton.callOnClick(); }) .setCancelable(false) .create().show(); return; } Toast.makeText(getContext(), message, Toast.LENGTH_LONG).show(); } }