diff options
Diffstat (limited to 'app/src/main/java')
3 files changed, 443 insertions, 0 deletions
diff --git a/app/src/main/java/org/connectbot/EditHostActivity.java b/app/src/main/java/org/connectbot/EditHostActivity.java new file mode 100644 index 0000000..6c7da11 --- /dev/null +++ b/app/src/main/java/org/connectbot/EditHostActivity.java @@ -0,0 +1,43 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2015 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import android.support.v7.app.AppCompatActivity; +import android.os.Bundle; + +import org.connectbot.bean.HostBean; + +public class EditHostActivity extends AppCompatActivity implements HostEditorFragment.Listener { + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_edit_host); + + if (savedInstanceState == null) { + HostEditorFragment editor = HostEditorFragment.newInstance(null); + getSupportFragmentManager().beginTransaction() + .add(R.id.fragment_container, editor).commit(); + } + } + + @Override + public void onHostUpdated(HostBean host) { + + } +} diff --git a/app/src/main/java/org/connectbot/HostEditorFragment.java b/app/src/main/java/org/connectbot/HostEditorFragment.java new file mode 100644 index 0000000..ea9d65f --- /dev/null +++ b/app/src/main/java/org/connectbot/HostEditorFragment.java @@ -0,0 +1,339 @@ +/* + * ConnectBot: simple, powerful, open-source SSH client for Android + * Copyright 2015 Kenny Root, Jeffrey Sharkey + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.connectbot; + +import android.content.ContentValues; +import android.content.Context; +import android.net.Uri; +import android.os.Bundle; +import android.os.Parcelable; +import android.support.design.widget.TextInputLayout; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.EditText; +import android.widget.ImageButton; +import android.widget.Spinner; + +import org.connectbot.bean.HostBean; +import org.connectbot.transport.SSH; +import org.connectbot.transport.Telnet; +import org.connectbot.transport.TransportFactory; +import org.connectbot.util.HostDatabase; + +public class HostEditorFragment extends Fragment { + + private static final String ARG_EXISTING_HOST = "existingHost"; + private static final String ARG_IS_EXPANDED = "isExpanded"; + private static final String ARG_QUICKCONNECT_STRING = "quickConnectString"; + + // The host being edited. + private HostBean mHost; + + // Whether the host is being created for the first time (as opposed to an existing one being + // edited). + private boolean mIsCreating; + + // The listener for changes to this host. + private Listener mListener; + + // Whether the URI parts subsection is expanded. + private boolean mIsUriEditorExpanded = false; + + // Whether a URI text edit is in progress. When the quick-connect field is being edited, changes + // automatically propagate to the URI part fields; likewise, when the URI part fields are + // edited, changes are propagated to the quick-connect field. This boolean safeguards against + // infinite loops which can be caused by one field changing the other field, which changes the + // first field, etc. + private boolean mUriFieldEditInProgress = false; + + private Spinner mTransportSpinner; + private TextInputLayout mQuickConnectContainer; + private EditText mQuickConnectField; + private ImageButton mExpandCollapseButton; + private View mUriPartsContainer; + private View mUsernameContainer; + private EditText mUsernameField; + private View mHostnameContainer; + private EditText mHostnameField; + private View mPortContainer; + private EditText mPortField; + + public static HostEditorFragment newInstance(HostBean existingHost) { + HostEditorFragment fragment = new HostEditorFragment(); + Bundle args = new Bundle(); + if (existingHost != null) { + args.putParcelable(ARG_EXISTING_HOST, existingHost.getValues()); + } + fragment.setArguments(args); + return fragment; + } + + public HostEditorFragment() { + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle bundle = savedInstanceState == null ? getArguments() : savedInstanceState; + + Parcelable existingHostParcelable = bundle.getParcelable(ARG_EXISTING_HOST); + mIsCreating = existingHostParcelable == null; + if (existingHostParcelable != null) { + mHost = HostBean.fromContentValues((ContentValues) existingHostParcelable); + } else { + mHost = new HostBean(); + } + + mIsUriEditorExpanded = bundle.getBoolean(ARG_IS_EXPANDED); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_host_editor, container, false); + + mTransportSpinner = (Spinner) view.findViewById(R.id.transport_selector); + ArrayAdapter<String> transportSelection = new ArrayAdapter<>( + getActivity(), + android.R.layout.simple_spinner_item, + TransportFactory.getTransportNames()); + transportSelection.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mTransportSpinner.setAdapter(transportSelection); + mTransportSpinner.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { + String protocol = (String) mTransportSpinner.getSelectedItem(); + if (protocol == null) { + // During initialization, protocol can be null before the list of dropdown items + // has been generated. Return early in that case. + return; + } + + mHost.setProtocol(protocol); + mHost.setPort(TransportFactory.getTransport(protocol).getDefaultPort()); + + mQuickConnectContainer.setHint( + TransportFactory.getFormatHint(protocol, getActivity())); + + // Different protocols have different field types, so show only the fields needed. + if (SSH.getProtocolName().equals(protocol)) { + mUsernameContainer.setVisibility(View.VISIBLE); + mHostnameContainer.setVisibility(View.VISIBLE); + mPortContainer.setVisibility(View.VISIBLE); + mExpandCollapseButton.setVisibility(View.VISIBLE); + } else if (Telnet.getProtocolName().equals(protocol)) { + mUsernameContainer.setVisibility(View.GONE); + mHostnameContainer.setVisibility(View.VISIBLE); + mPortContainer.setVisibility(View.VISIBLE); + mExpandCollapseButton.setVisibility(View.VISIBLE); + } else { + // Local protocol has only one field, so no need to show the URI parts + // container. + setUriPartsContainerExpanded(false); + mExpandCollapseButton.setVisibility(View.INVISIBLE); + } + } + + @Override + public void onNothingSelected(AdapterView<?> parent) { + } + }); + + mQuickConnectContainer = + (TextInputLayout) view.findViewById(R.id.quickconnect_field_container); + + mQuickConnectField = (EditText) view.findViewById(R.id.quickconnect_field); + String oldQuickConnect = savedInstanceState == null ? + null : savedInstanceState.getString(ARG_QUICKCONNECT_STRING); + mQuickConnectField.setText(oldQuickConnect == null ? mHost.toString() : oldQuickConnect); + mQuickConnectField.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) { + if (mTransportSpinner.getSelectedItem() == null) { + // During initialization, protocol can be null before the list of dropdown items + // has been generated. Return early in that case. + return; + } + + if (!mUriFieldEditInProgress) { + applyQuickConnectString( + s.toString(), (String) mTransportSpinner.getSelectedItem()); + + mUriFieldEditInProgress = true; + mUsernameField.setText(mHost.getUsername()); + mHostnameField.setText(mHost.getHostname()); + mPortField.setText(Integer.toString(mHost.getPort())); + mUriFieldEditInProgress = false; + } + } + }); + + mExpandCollapseButton = (ImageButton) view.findViewById(R.id.expand_collapse_button); + mExpandCollapseButton.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + setUriPartsContainerExpanded(!mIsUriEditorExpanded); + } + }); + + mUriPartsContainer = view.findViewById(R.id.uri_parts_container); + + mUsernameContainer = view.findViewById(R.id.username_field_container); + mUsernameField = (EditText) view.findViewById(R.id.username_edit_text); + mUsernameField.setText(mHost.getUsername()); + mUsernameField.addTextChangedListener(new UriDataUpdater(HostDatabase.FIELD_HOST_USERNAME)); + + mHostnameContainer = view.findViewById(R.id.hostname_field_container); + mHostnameField = (EditText) view.findViewById(R.id.hostname_edit_text); + mHostnameField.setText(mHost.getHostname()); + mHostnameField.addTextChangedListener(new UriDataUpdater(HostDatabase.FIELD_HOST_HOSTNAME)); + + mPortContainer = view.findViewById(R.id.port_field_container); + mPortField = (EditText) view.findViewById(R.id.port_edit_text); + mPortField.setText(Integer.toString(mHost.getPort())); + mPortField.addTextChangedListener(new UriDataUpdater(HostDatabase.FIELD_HOST_PORT)); + + setUriPartsContainerExpanded(mIsUriEditorExpanded); + + return view; + } + + @Override + public void onAttach(Context context) { + super.onAttach(context); + try { + mListener = (Listener) context; + } catch (ClassCastException e) { + throw new ClassCastException(context.toString() + " must implement Listener"); + } + } + + @Override + public void onDetach() { + super.onDetach(); + mListener = null; + } + + @Override + public void onSaveInstanceState(Bundle savedInstanceState) { + super.onSaveInstanceState(savedInstanceState); + + savedInstanceState.putParcelable(ARG_EXISTING_HOST, mHost.getValues()); + savedInstanceState.putBoolean(ARG_IS_EXPANDED, mIsUriEditorExpanded); + savedInstanceState.putString( + ARG_QUICKCONNECT_STRING, mQuickConnectField.getText().toString()); + } + + private void setUriPartsContainerExpanded(boolean expanded) { + mIsUriEditorExpanded = expanded; + + if (mIsUriEditorExpanded) { + mExpandCollapseButton.setImageResource(R.drawable.ic_expand_less); + mUriPartsContainer.setVisibility(View.VISIBLE); + } else { + mExpandCollapseButton.setImageResource(R.drawable.ic_expand_more); + mUriPartsContainer.setVisibility(View.GONE); + } + } + + /** + * Applies the quick-connect URI entered in the field by copying its URI parts to mHost's + * fields. + * @param quickConnectString The URI entered in the quick-connect field. + * @param protocol The protocol for this connection. + */ + private void applyQuickConnectString(String quickConnectString, String protocol) { + if (quickConnectString == null || protocol == null) + return; + + Uri uri = TransportFactory.getUri(protocol, quickConnectString); + if (uri == null) { + // If the URI was invalid, null out the associated fields. + mHost.setProtocol(protocol); + mHost.setUsername(null); + mHost.setHostname(null); + mHost.setNickname(null); + mHost.setPort(TransportFactory.getTransport(protocol).getDefaultPort()); + return; + } + + HostBean host = TransportFactory.getTransport(protocol).createHost(uri); + mHost.setProtocol(host.getProtocol()); + mHost.setUsername(host.getUsername()); + mHost.setHostname(host.getHostname()); + mHost.setNickname(host.getNickname()); + mHost.setPort(host.getPort()); + } + + public interface Listener { + public void onHostUpdated(HostBean host); + } + + private class UriDataUpdater implements TextWatcher { + + private final String mFieldType; + + public UriDataUpdater(String fieldType) { + mFieldType = fieldType; + } + + @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 text = s.toString(); + + if (HostDatabase.FIELD_HOST_USERNAME.equals(mFieldType)) { + mHost.setUsername(text); + } else if (HostDatabase.FIELD_HOST_HOSTNAME.equals(mFieldType)) { + mHost.setHostname(text); + } else if (HostDatabase.FIELD_HOST_PORT.equals(mFieldType)) { + try { + mHost.setPort(Integer.parseInt(text)); + } catch (NumberFormatException e) { + return; + } + } else { + throw new RuntimeException("Invalid field type."); + } + + if (!mUriFieldEditInProgress) { + mUriFieldEditInProgress = true; + mQuickConnectField.setText(mHost.toString()); + mUriFieldEditInProgress = false; + } + } + } +} diff --git a/app/src/main/java/org/connectbot/bean/HostBean.java b/app/src/main/java/org/connectbot/bean/HostBean.java index 93d7716..1fbb9be 100644 --- a/app/src/main/java/org/connectbot/bean/HostBean.java +++ b/app/src/main/java/org/connectbot/bean/HostBean.java @@ -17,6 +17,10 @@ package org.connectbot.bean; +import org.connectbot.transport.Local; +import org.connectbot.transport.SSH; +import org.connectbot.transport.Telnet; +import org.connectbot.transport.TransportFactory; import org.connectbot.util.HostDatabase; import android.content.ContentValues; @@ -226,6 +230,29 @@ public class HostBean extends AbstractBean { return values; } + public static HostBean fromContentValues(ContentValues values) { + HostBean host = new HostBean(); + host.setNickname(values.getAsString(HostDatabase.FIELD_HOST_NICKNAME)); + host.setProtocol(values.getAsString(HostDatabase.FIELD_HOST_PROTOCOL)); + host.setUsername(values.getAsString(HostDatabase.FIELD_HOST_USERNAME)); + host.setHostname(values.getAsString(HostDatabase.FIELD_HOST_HOSTNAME)); + host.setPort(values.getAsInteger(HostDatabase.FIELD_HOST_PORT)); + host.setLastConnect(values.getAsLong(HostDatabase.FIELD_HOST_LASTCONNECT)); + host.setColor(values.getAsString(HostDatabase.FIELD_HOST_COLOR)); + host.setUseKeys(Boolean.valueOf(values.getAsString(HostDatabase.FIELD_HOST_USEKEYS))); + host.setUseAuthAgent(values.getAsString(HostDatabase.FIELD_HOST_USEAUTHAGENT)); + host.setPostLogin(values.getAsString(HostDatabase.FIELD_HOST_POSTLOGIN)); + host.setPubkeyId(values.getAsLong(HostDatabase.FIELD_HOST_PUBKEYID)); + host.setWantSession(Boolean.valueOf(values.getAsString(HostDatabase.FIELD_HOST_WANTSESSION))); + host.setDelKey(values.getAsString(HostDatabase.FIELD_HOST_DELKEY)); + host.setFontSize(values.getAsInteger(HostDatabase.FIELD_HOST_FONTSIZE)); + host.setCompression(Boolean.valueOf(values.getAsString(HostDatabase.FIELD_HOST_COMPRESSION))); + host.setEncoding(values.getAsString(HostDatabase.FIELD_HOST_ENCODING)); + host.setStayConnected(values.getAsBoolean(HostDatabase.FIELD_HOST_STAYCONNECTED)); + host.setQuickDisconnect(values.getAsBoolean(HostDatabase.FIELD_HOST_QUICKDISCONNECT)); + return host; + } + @Override public boolean equals(Object o) { if (o == null || !(o instanceof HostBean)) @@ -302,4 +329,38 @@ public class HostBean extends AbstractBean { return Uri.parse(sb.toString()); } + /** + * Generates a "pretty" string to be used in the quick-connect host edit view. + */ + @Override + public String toString() { + if (protocol == null) + return ""; + + int defaultPort = TransportFactory.getTransport(protocol).getDefaultPort(); + + if (SSH.getProtocolName().equals(protocol)) { + if (username == null || hostname == null || + username.equals("") || hostname.equals("")) + return ""; + + if (port == defaultPort) + return username + "@" + hostname; + else + return username + "@" + hostname + ":" + port; + } else if (Telnet.getProtocolName().equals(protocol)) { + if (hostname == null || hostname.equals("")) + return ""; + else if (port == defaultPort) + return hostname; + else + return hostname + ":" + port; + } else if (Local.getProtocolName().equals(protocol)) { + return nickname; + } + + // Fail gracefully. + return ""; + } + } |