/* Yaaic - Yet Another Android IRC Client Copyright 2009-2013 Sebastian Kaspari This file is part of Yaaic. Yaaic 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. Yaaic 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 Yaaic. If not, see <http://www.gnu.org/licenses/>. */ package org.numixproject.hermes.activity; import java.io.UnsupportedEncodingException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.regex.Pattern; import org.numixproject.hermes.MainActivity; import org.numixproject.hermes.R; import org.numixproject.hermes.Hermes; import org.numixproject.hermes.db.Database; import org.numixproject.hermes.exception.ValidationException; import org.numixproject.hermes.model.Authentication; import org.numixproject.hermes.model.Extra; import org.numixproject.hermes.model.Identity; import org.numixproject.hermes.model.Server; import org.numixproject.hermes.model.Status; import android.app.AlertDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.graphics.Typeface; import android.net.Uri; import android.os.Bundle; import android.support.v7.app.ActionBar; import android.support.v7.app.ActionBarActivity; import android.text.Editable; import android.text.TextWatcher; import android.text.method.PasswordTransformationMethod; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; import android.view.View.OnClickListener; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.BaseAdapter; import android.widget.Button; import android.widget.CheckBox; import android.widget.EditText; import android.widget.ImageView; import android.widget.Spinner; import android.widget.TextView; import android.widget.Toast; import com.github.paolorotolo.expandableheightlistview.ExpandableHeightListView; /** * Add a new server to the list * * @author Sebastian Kaspari <sebastian@yaaic.org> */ public class AddServerActivity extends ActionBarActivity implements OnClickListener, AdapterView.OnItemClickListener { private static final int REQUEST_CODE_CHANNELS = 1; private static final int REQUEST_CODE_COMMANDS = 2; private static final int REQUEST_CODE_AUTHENTICATION = 4; private Server server; private Authentication authentication; private ArrayList<String> aliases; private ArrayList<String> channels; private ArrayList<String> commands; private commandsAdapter commandsAdapter; private CheckBox nickservCheckbox; private CheckBox saslCheckbox; private TextView saslUsernameLabel; private EditText saslUsernameEditText; private TextView saslPasswordLabel; private EditText saslPasswordEditText; private EditText commandInput; private EditText channelInput; private ArrayList<String> channels2; private ArrayAdapter<String> adapter3; /** * On create */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.serveradd); ActionBar actionBar = getSupportActionBar(); actionBar.setDisplayHomeAsUpEnabled(true); authentication = new Authentication(); aliases = new ArrayList<String>(); channels = new ArrayList<String>(); commands = new ArrayList<String>(); Spinner spinner = (Spinner) findViewById(R.id.charset); String[] charsets = getResources().getStringArray(R.array.charsets); ArrayAdapter<CharSequence> adapter = new ArrayAdapter<CharSequence>(this, android.R.layout.simple_spinner_item, charsets); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); spinner.setAdapter(adapter); EditText password = (EditText) findViewById(R.id.password); password.setTypeface(Typeface.DEFAULT); password.setTransformationMethod(new PasswordTransformationMethod()); Bundle extras = getIntent().getExtras(); if (extras != null && extras.containsKey(Extra.SERVER)) { setTitle(R.string.edit_server_label); // Request to edit an existing server Database db = new Database(this); this.server = db.getServerById(extras.getInt(Extra.SERVER)); aliases.addAll(server.getIdentity().getAliases()); this.channels = db.getChannelsByServerId(server.getId()); this.commands = db.getCommandsByServerId(server.getId()); this.authentication = server.getAuthentication(); db.close(); // Set server values ((EditText) findViewById(R.id.title)).setText(server.getTitle()); ((AutoCompleteTextView) findViewById(R.id.host)).setText(server.getHost()); ((EditText) findViewById(R.id.port)).setText(String.valueOf(server.getPort())); ((EditText) findViewById(R.id.password)).setText(server.getPassword()); ((EditText) findViewById(R.id.nickname)).setText(server.getIdentity().getNickname()); ((EditText) findViewById(R.id.ident)).setText(server.getIdentity().getIdent()); ((EditText) findViewById(R.id.realname)).setText(server.getIdentity().getRealName()); ((CheckBox) findViewById(R.id.useSSL)).setChecked(server.useSSL()); // Select charset if (server.getCharset() != null) { for (int i = 0; i < charsets.length; i++) { if (server.getCharset().equals(charsets[i])) { spinner.setSelection(i); break; } } } } // Disable suggestions for host name if (android.os.Build.VERSION.SDK_INT >= 5) { EditText serverHostname = (EditText) findViewById(R.id.host); serverHostname.setInputType(0x80000); } ArrayAdapter<String> hostAdapter = new ArrayAdapter<String>(this, R.layout.dropdown_item, HOSTS); AutoCompleteTextView hostTextView = (AutoCompleteTextView) findViewById(R.id.host); hostTextView.setAdapter(hostAdapter); hostTextView.setThreshold(0); Uri uri = getIntent().getData(); if (uri != null && uri.getScheme().equals("irc")) { // handling an irc:// uri ((EditText) findViewById(R.id.host)).setText(uri.getHost()); if (uri.getPort() != -1) { ((EditText) findViewById(R.id.port)).setText(String.valueOf(uri.getPort())); } if (uri.getPath() != null) { channels.add(uri.getPath().replace('/', '#')); } if (uri.getQuery() != null) { ((EditText) findViewById(R.id.password)).setText(String.valueOf(uri.getQuery())); } } // Autentication nickservCheckbox = (CheckBox) findViewById(R.id.nickserv_checkbox); saslCheckbox = (CheckBox) findViewById(R.id.sasl_checkbox); saslUsernameEditText = (EditText) findViewById(R.id.sasl_username); saslPasswordEditText = (EditText) findViewById(R.id.sasl_password); // Commands commandInput = (EditText) findViewById(R.id.command); commandsAdapter = new commandsAdapter(commands); final Button addCommand = (Button) findViewById(R.id.add_command); addCommand.setEnabled(false); commandInput.addTextChangedListener(new TextWatcher() { @Override public void onTextChanged(CharSequence s, int start, int before, int count) { // TODO Auto-generated method stub } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { // TODO Auto-generated method stub } @Override public void afterTextChanged(Editable s) { if (commandInput.getText().length() == 1){ addCommand.setEnabled(false); } else { addCommand.setEnabled(true); } } }); ExpandableHeightListView list2 = (ExpandableHeightListView) findViewById(R.id.commands_list); list2.setAdapter(commandsAdapter); list2.setOnItemClickListener(this); // Workaround for ListView height list2.setExpanded(true); ((Button) findViewById(R.id.add_command)).setOnClickListener(this); commandsAdapter.notifyDataSetChanged(); // Autojoin Rooms channelInput = (EditText) findViewById(R.id.channel); adapter3 = new ArrayAdapter<String>(this, R.layout.channelitem); // ExpandableHeightListView list3 = (ExpandableHeightListView) findViewById(R.id.channels); // list3.setAdapter(adapter3); // list3.setOnItemClickListener(this); // Workaround for ListView height // list3.setExpanded(true); // ((Button) findViewById(R.id.add_room)).setOnClickListener(this); for (String channel : channels) { adapter3.add(channel); } } private static final String[] HOSTS = new String[] { "irc.efnet.org", "irc.freenode.net", "irc.geekshed.net", "irc.gimp.org", "irc.ircnet.org", "irc.oftc.net", "irc.spotchat.org" }; @Override public boolean onOptionsItemSelected(MenuItem item) { switch (item.getItemId()) { case R.id.save: save(); return true; case android.R.id.home: // app icon in action bar clicked; go home Intent intent = new Intent(this, MainActivity.class); intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); startActivity(intent); return true; default: return super.onOptionsItemSelected(item); } } /** * On activity result */ @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (resultCode != RESULT_OK) { return; // ignore everything else } switch (requestCode) { case REQUEST_CODE_CHANNELS: channels = data.getExtras().getStringArrayList(Extra.CHANNELS); break; case REQUEST_CODE_COMMANDS: commands = data.getExtras().getStringArrayList(Extra.COMMANDS); break; } } /** * On click add server or cancel activity */ @Override public void onClick(View v) { switch (v.getId()) { // case R.id.add_room: // String channel = channelInput.getText().toString().trim(); // channels.add(channel); // adapter3.add(channel); // channelInput.setText("#"); // channelInput.setSelection(1); // break; case R.id.add_command: String command = commandInput.getText().toString().trim(); if (!command.startsWith("/")) { command = "/" + command; } commands.add(command); commandsAdapter.notifyDataSetChanged(); commandInput.setText("/"); break; case R.id.channels: Intent channelIntent = new Intent(this, AddChannelActivity.class); channelIntent.putExtra(Extra.CHANNELS, channels); startActivityForResult(channelIntent, REQUEST_CODE_CHANNELS); break; case R.id.commands: Intent commandsIntent = new Intent(this, AddCommandsActivity.class); commandsIntent.putExtra(Extra.COMMANDS, commands); startActivityForResult(commandsIntent, REQUEST_CODE_COMMANDS); break; case R.id.add: save(); break; case R.id.cancel: setResult(RESULT_CANCELED); finish(); break; } } @Override public boolean onCreateOptionsMenu(Menu menu) { // Inflate the menu items for use in the action bar MenuInflater inflater = getMenuInflater(); inflater.inflate(R.menu.addserver, menu); return super.onCreateOptionsMenu(menu); } /** * On item clicked */ @Override public void onItemClick(AdapterView<?> list, View item, int position, long id) { final String channel = adapter3.getItem(position); String[] items = { getResources().getString(R.string.action_remove) }; AlertDialog.Builder builder = new AlertDialog.Builder(this); builder.setTitle(channel); builder.setItems(items, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { switch (item) { case 0: // Remove adapter3.remove(channel); channels.remove(channel); break; } } }); AlertDialog alert = builder.create(); alert.show(); } /** * Try to save server. */ private void save() { try { validateServer(); validateIdentity(); if (server == null) { addServer(); } else { updateServer(); } setResult(RESULT_OK); finish(); } catch(ValidationException e) { Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show(); } if (nickservCheckbox.isChecked()) { authentication.setNickservPassword(saslPasswordEditText.toString()); } else if (saslCheckbox.isChecked()) { authentication.setSaslUsername(saslUsernameEditText.toString()); authentication.setSaslPassword(saslPasswordEditText.toString()); } } /** * Add server to database */ private void addServer() { Database db = new Database(this); Identity identity = getIdentityFromView(); long identityId = db.addIdentity( identity.getNickname(), identity.getIdent(), identity.getRealName(), identity.getAliases() ); Server server = getServerFromView(); if (nickservCheckbox.isChecked()) { authentication.setNickservPassword(saslPasswordEditText.toString()); } else if (saslCheckbox.isChecked()) { authentication.setSaslUsername(saslUsernameEditText.toString()); authentication.setSaslPassword(saslPasswordEditText.toString()); } server.setAuthentication(authentication); long serverId = db.addServer(server, (int) identityId); db.setChannels((int) serverId, channels); db.setCommands((int) serverId, commands); db.close(); server.setId((int) serverId); server.setIdentity(identity); server.setAutoJoinChannels(channels); server.setConnectCommands(commands); Hermes.getInstance().addServer(server); } /** * Update server */ private void updateServer() { Database db = new Database(this); int serverId = this.server.getId(); int identityId = db.getIdentityIdByServerId(serverId); Server server = getServerFromView(); if (nickservCheckbox.isChecked()) { authentication.setNickservPassword(saslPasswordEditText.toString()); } else if (saslCheckbox.isChecked()) { authentication.setSaslUsername(saslUsernameEditText.toString()); authentication.setSaslPassword(saslPasswordEditText.toString()); } server.setAuthentication(authentication); db.updateServer(serverId, server, identityId); Identity identity = getIdentityFromView(); db.updateIdentity( identityId, identity.getNickname(), identity.getIdent(), identity.getRealName(), identity.getAliases() ); db.setChannels(serverId, channels); db.setCommands(serverId, commands); db.close(); server.setId(this.server.getId()); server.setIdentity(identity); server.setAutoJoinChannels(channels); server.setConnectCommands(commands); Hermes.getInstance().updateServer(server); } /** * Populate a server object from the data in the view * * @return The server object */ private Server getServerFromView() { String title = ((EditText) findViewById(R.id.title)).getText().toString().trim(); String host = ((EditText) findViewById(R.id.host)).getText().toString().trim(); int port = Integer.parseInt(((EditText) findViewById(R.id.port)).getText().toString().trim()); String password = ((EditText) findViewById(R.id.password)).getText().toString().trim(); String charset = ((Spinner) findViewById(R.id.charset)).getSelectedItem().toString(); Boolean useSSL = ((CheckBox) findViewById(R.id.useSSL)).isChecked(); // not in use yet //boolean autoConnect = ((CheckBox) findViewById(R.id.autoconnect)).isChecked(); Server server = new Server(); server.setHost(host); server.setPort(port); server.setPassword(password); server.setTitle(title); server.setCharset(charset); server.setUseSSL(useSSL); server.setStatus(Status.DISCONNECTED); return server; } /** * Populate an identity object from the data in the view * * @return The identity object */ private Identity getIdentityFromView() { String nickname = ((EditText) findViewById(R.id.nickname)).getText().toString(); String ident = ((EditText) findViewById(R.id.ident)).getText().toString().trim(); String realname = ((EditText) findViewById(R.id.realname)).getText().toString().trim(); String[] floatStrings = nickname.split(","); String[] nick = new String[floatStrings.length]; for (int i=0; i<nick.length; i++) { nick[i] = String.valueOf(floatStrings[i]); } Identity identity = new Identity(); identity.setNickname(nick[0]); identity.setIdent(ident); identity.setRealName(realname); // convert String[] to ArrayList<String> List<String> aliasesList = Arrays.asList(nick); identity.setAliases(aliasesList); return identity; } /** * Validate the input for a server * * @throws ValidationException */ private void validateServer() throws ValidationException { String title = ((EditText) findViewById(R.id.title)).getText().toString(); String host = ((EditText) findViewById(R.id.host)).getText().toString(); String port = ((EditText) findViewById(R.id.port)).getText().toString(); String charset = ((Spinner) findViewById(R.id.charset)).getSelectedItem().toString(); if (title.trim().equals("")) { throw new ValidationException(getResources().getString(R.string.validation_blank_title)); } if (host.trim().equals("")) { // XXX: We should use some better host validation throw new ValidationException(getResources().getString(R.string.validation_blank_host)); } try { Integer.parseInt(port); } catch (NumberFormatException e) { throw new ValidationException(getResources().getString(R.string.validation_invalid_port)); } try { "".getBytes(charset); } catch (UnsupportedEncodingException e) { throw new ValidationException(getResources().getString(R.string.validation_unsupported_charset)); } Database db = new Database(this); if (db.isTitleUsed(title) && (server == null || !server.getTitle().equals(title))) { db.close(); throw new ValidationException(getResources().getString(R.string.validation_title_used)); } db.close(); } /** * Validate the input for a identity * * @throws ValidationException */ private void validateIdentity() throws ValidationException { String nickname = ((EditText) findViewById(R.id.nickname)).getText().toString(); String ident = ((EditText) findViewById(R.id.ident)).getText().toString(); String realname = ((EditText) findViewById(R.id.realname)).getText().toString(); if (nickname.trim().equals("")) { throw new ValidationException(getResources().getString(R.string.validation_blank_nickname)); } if (ident.trim().equals("")) { throw new ValidationException(getResources().getString(R.string.validation_blank_ident)); } if (realname.trim().equals("")) { throw new ValidationException(getResources().getString(R.string.validation_blank_realname)); } // RFC 1459: <nick> ::= <letter> { <letter> | <number> | <special> } // <special> ::= '-' | '[' | ']' | '\' | '`' | '^' | '{' | '}' // Chars that are not in RFC 1459 but are supported too: // | and _ //Pattern nickPattern = Pattern.compile("^[a-zA-Z_][a-zA-Z0-9^\\-`\\[\\]{}|_\\\\]*$"); //if (!nickPattern.matcher(nickname).matches()) { // throw new ValidationException(getResources().getString(R.string.validation_invalid_nickname)); //} // We currently only allow chars, numbers and some special chars for ident Pattern identPattern = Pattern.compile("^[a-zA-Z0-9\\[\\]\\-_/]+$"); if (!identPattern.matcher(ident).matches()) { throw new ValidationException(getResources().getString(R.string.validation_invalid_ident)); } } // Adapter for Recent List class commandsAdapter extends BaseAdapter { ArrayList<String> Command; commandsAdapter() { Command = null; } public commandsAdapter(ArrayList<String> text) { Command = text; } public int getCount() { // TODO Auto-generated method stub return Command.size(); } public Object getItem(int arg0) { // TODO Auto-generated method stub return null; } public long getItemId(int position) { // TODO Auto-generated method stub return position; } public View getView(final int position, View convertView, ViewGroup parent) { LayoutInflater inflater = (LayoutInflater) parent.getContext().getSystemService(Context.LAYOUT_INFLATER_SERVICE); View row; row = inflater.inflate(R.layout.commanditem, parent, false); TextView commandText = (TextView) row.findViewById(R.id.host); ImageView remove = (ImageView) row.findViewById(R.id.command_remove); commandText.setText(Command.get(position)); remove.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { commands.remove(Command.get(position)); commandsAdapter.this.notifyDataSetChanged(); } }); return (row); } } }