package eu.hgross.blaubot.android.views.edit; import android.app.AlertDialog; import android.app.Dialog; import android.content.Context; import android.content.DialogInterface; import android.os.Handler; import android.os.Looper; import android.text.Editable; import android.text.TextWatcher; import android.util.AttributeSet; import android.view.View; import android.widget.EditText; import android.widget.FrameLayout; import eu.hgross.blaubot.android.R; import eu.hgross.blaubot.android.views.ViewUtils; import eu.hgross.blaubot.core.Blaubot; import eu.hgross.blaubot.core.BlaubotFactory; import eu.hgross.blaubot.core.BlaubotServerConnector; import eu.hgross.blaubot.core.IBlaubotDevice; /** * Provides a user interface to create a websocket server connector. * Can be reused inside a dialog to create websocket server conncetors when needed. * USAGE: * WebsocketServerConnectorEditView.createAsDialog(myContext, myBlaubot).show(); * * @author Henning Gross {@literal (mail.to@henning-gross.de)} */ public class WebsocketServerConnectorEditView extends FrameLayout { private static final String LOG_TAG = "WebsocketServerConnectorEditView"; private Handler mUiHandler; private View mMainView; private EditText mHostnameEditText; private EditText mPathEditText; private EditText mPortEditText; private EditText mServerUniqueDeviceId; public WebsocketServerConnectorEditView(Context context, AttributeSet attrs) { super(context, attrs); initView(context, attrs); } public WebsocketServerConnectorEditView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initView(context, attrs); } private void initView(Context context, AttributeSet attrs) { this.mMainView = inflate(getContext(), R.layout.blaubot_websocket_serverconnector_editview, null); this.mHostnameEditText = (EditText) mMainView.findViewById(R.id.hostnameOrIpEditText); this.mPortEditText = (EditText) mMainView.findViewById(R.id.port); this.mPathEditText = (EditText) mMainView.findViewById(R.id.path); this.mServerUniqueDeviceId = (EditText) mMainView.findViewById(R.id.serverUniqueDeviceId); mUiHandler = new Handler(Looper.getMainLooper()); addView(this.mMainView); this.mHostnameEditText.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) { mHostnameEditText.setError(isHostOrIpValid() ? null : "Not a valid hostname or IP-Address."); } @Override public void afterTextChanged(Editable s) { } }); this.mPortEditText.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) { mPortEditText.setError(isPortValid() ? null : "Port is not valid. Should be in [1025, 65535]"); } @Override public void afterTextChanged(Editable s) { } }); this.mPathEditText.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) { mPathEditText.setError(isPathSegmentValid() ? null : "Not a valid path."); } @Override public void afterTextChanged(Editable s) { } }); this.mServerUniqueDeviceId.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) { mServerUniqueDeviceId.setError(isServerUniqueDeviceIdValid() ? null : "Not a valid unique device id."); } @Override public void afterTextChanged(Editable s) { } }); } private boolean isHostOrIpValid() { final String val = mHostnameEditText.getText().toString(); final boolean valid = ViewUtils.isValidHostname(val) || ViewUtils.isValidIpAddress(val); return valid; } private boolean isPortValid() { final String val = mPortEditText.getText().toString(); try { int number = Integer.valueOf(val); return number > 1024 || number < 65535; } catch (NumberFormatException e) { return false; } } private boolean isPathSegmentValid() { final String val = mPathEditText.getText().toString(); return !val.isEmpty() && val.startsWith("/") && ViewUtils.validateURLPathSegment(val); } private boolean isServerUniqueDeviceIdValid() { final String serverUniqueDeviceId = mServerUniqueDeviceId.getText().toString(); // TODO: there are plans in my mind to limit the length of unique device ids ... has to be reflected here return !serverUniqueDeviceId.isEmpty(); } private boolean isInputValid() { return isHostOrIpValid() && isPortValid() && isPathSegmentValid() && isServerUniqueDeviceIdValid(); } /** * Tries to construct the server connector from the view's inputs. * * @param ownDevice the own device of the connecting blaubot instance * @return the server connector * @throws IllegalArgumentException if the user input was not valid * @throws ClassNotFoundException if the blaubot-websocket jar is not in the classpath */ public BlaubotServerConnector createConnector(IBlaubotDevice ownDevice) throws IllegalArgumentException, ClassNotFoundException { if (!isInputValid()) { throw new IllegalArgumentException(); } int port = Integer.valueOf(mPortEditText.getText().toString()); String path = mPathEditText.getText().toString(); String hostOrIp = mHostnameEditText.getText().toString(); String serverUniqueDeviceId = mServerUniqueDeviceId.getText().toString(); return BlaubotFactory.createWebSocketServerConnector(hostOrIp, port, path, ownDevice, serverUniqueDeviceId); } /** * Creates this view as a dialog that allows the user to create a server connector which is then * attached to the given blaubot instance. * * @param context the context * @param blaubot the blaubot instance to create the connector for * @param presetHostname the hostname with which the form is initially filled * @param presetPort the port with which the form is initially filled * @param presetPath the path with which the form is initially filled * @param presetServerUniqueDeviceId the server's unique device id with which the form is initially filled * @return the dialog. Call dialog.show() to use it. */ public static Dialog createAsDialog(Context context, final Blaubot blaubot, String presetHostname, int presetPort, String presetPath, String presetServerUniqueDeviceId) { final WebsocketServerConnectorEditView view = new WebsocketServerConnectorEditView(context, null); // set the preset fields view.mHostnameEditText.setText(presetHostname); view.mServerUniqueDeviceId.setText(presetServerUniqueDeviceId); view.mPortEditText.setText(presetPort+""); view.mPathEditText.setText(presetPath); // build the dialog AlertDialog.Builder builder = new AlertDialog.Builder(context); builder.setView(view) .setPositiveButton("Save", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { final BlaubotServerConnector connector; try { connector = view.createConnector(blaubot.getOwnDevice()); blaubot.setServerConnector(connector); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .setTitle("Create server connector"); return builder.create(); } }