/*
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 indrora.atomic.activity;
import indrora.atomic.Atomic;
import indrora.atomic.db.Database;
import indrora.atomic.dialog.AddAliasView;
import indrora.atomic.dialog.AddChannelView;
import indrora.atomic.exception.ValidationException;
import indrora.atomic.model.Authentication;
import indrora.atomic.model.Extra;
import indrora.atomic.model.Identity;
import indrora.atomic.model.Server;
import indrora.atomic.model.Settings;
import indrora.atomic.model.Status;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.regex.Pattern;
import indrora.atomic.R;
import indrora.atomic.dialog.AuthenticationView;
import indrora.atomic.dialog.CommandListView;
import android.app.ActionBar;
import android.app.Activity;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.Spinner;
import android.widget.Toast;
/**
* Add a new server to the list
*
* @author Sebastian Kaspari <sebastian@yaaic.org>
*/
public class AddServerActivity extends Activity implements OnClickListener {
private static final int REQUEST_CODE_CHANNELS = 1;
private static final int REQUEST_CODE_COMMANDS = 2;
private static final int REQUEST_CODE_ALIASES = 3;
private static final int REQUEST_CODE_AUTHENTICATION = 4;
public static final String ACTION_NEW_SERVER = "new_server";
public static final String ACTION_EDIT_SERVER = "edit_server";
public static final String ACTION_DUPE_SERVER = "dupe_server";
private String _action = ACTION_NEW_SERVER;
private Server server;
private Authentication authentication;
private ArrayList<String> aliases;
private ArrayList<String> channels;
private ArrayList<String> commands;
private Settings s;
/**
* On create
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
s = new Settings(this);
setContentView(R.layout.serveradd);
ActionBar actionBar = getActionBar();
authentication = new Authentication();
aliases = new ArrayList<String>();
channels = new ArrayList<String>();
commands = new ArrayList<String>();
((Button)findViewById(R.id.add)).setOnClickListener(this);
((Button)findViewById(R.id.cancel)).setOnClickListener(this);
((Button)findViewById(R.id.aliases)).setOnClickListener(this);
((Button)findViewById(R.id.channels)).setOnClickListener(this);
((Button)findViewById(R.id.commands)).setOnClickListener(this);
((Button)findViewById(R.id.authentication)).setOnClickListener(this);
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);
if( getIntent().getAction() != null ) {
_action = getIntent().getAction();
}
((EditText)findViewById(R.id.username)).setText(s.getDefaultUsername());
((EditText)findViewById(R.id.nickname)).setText(s.getDefaultNick());
((EditText)findViewById(R.id.realname)).setText(s.getDefaultRealname());
Bundle extras = getIntent().getExtras();
if( extras != null && extras.containsKey(Extra.SERVER) ) {
if( _action.equals(ACTION_EDIT_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
if( _action.equals(ACTION_EDIT_SERVER) ) {
((EditText)findViewById(R.id.title)).setText(server.getTitle());
}
((EditText)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.username)).setText(server.getIdentity().getIdent());
((EditText)findViewById(R.id.realname)).setText(server.getIdentity().getRealName());
((CheckBox)findViewById(R.id.useSSL)).setChecked(server.useSSL());
((CheckBox)findViewById(R.id.autoconnect)).setChecked(server.getAutoconnect());
// Select charset
if( server.getCharset() != null ) {
for( int i = 0; i < charsets.length; i++ ) {
if( server.getCharset().equals(charsets[i]) ) {
spinner.setSelection(i);
break;
}
}
}
// Make the requested server null, since we don't care anymore.
if( _action.equals(ACTION_DUPE_SERVER) ) {
this.server = null;
}
}
// Disable suggestions for host name
if( android.os.Build.VERSION.SDK_INT >= 5 ) {
EditText serverHostname = (EditText)findViewById(R.id.host);
serverHostname.setInputType(0x80000);
}
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()));
}
}
}
/**
* On options menu requested
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.addserver, menu);
return true;
}
/**
* On menu item selected
*/
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
switch ( item.getItemId() ) {
case R.id.save:
save();
return true;
case android.R.id.home:
finish();
break;
}
return super.onMenuItemSelected(featureId, 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_ALIASES:
aliases.clear();
aliases.addAll(data.getExtras().getStringArrayList(Extra.ALIASES));
break;
case REQUEST_CODE_CHANNELS:
channels = data.getExtras().getStringArrayList(Extra.CHANNELS);
break;
case REQUEST_CODE_COMMANDS:
commands = data.getExtras().getStringArrayList(Extra.COMMANDS);
break;
}
}*/
private void showAuthDialog() {
AlertDialog.Builder b = new AlertDialog.Builder(this);
final AuthenticationView v = new AuthenticationView(this, authentication);
b.setView(v);
b.setCancelable(true);
b.setTitle(R.string.authentication);
b.setPositiveButton(R.string.action_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
// woo
authentication.setNickservPassword(v.getNickservPassword());
authentication.setSaslUsername(v.getSaslUsername());
authentication.setSaslPassword(v.getSaslPassword());
}
});
b.setNegativeButton(R.string.action_cancel, null);
b.show();
}
private void showCommmandList() {
AlertDialog.Builder b = new AlertDialog.Builder(this);
final CommandListView cv = new CommandListView(this, commands);
b.setView(cv);
b.setTitle(R.string.commands);
b.setPositiveButton(R.string.action_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
commands = cv.getCommands();
}
});
b.setNegativeButton(R.string.action_cancel, null);
b.show();
}
private void showAliasList() {
AlertDialog.Builder b = new AlertDialog.Builder(this);
final AddAliasView av = new AddAliasView(this, aliases);
b.setView(av);
b.setTitle(R.string.aliases);
b.setPositiveButton(R.string.action_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
aliases = av.getAliases();
}
});
b.setNegativeButton(R.string.action_cancel, null);
b.show();
}
private void showChannelList() {
AlertDialog.Builder b = new AlertDialog.Builder(this);
final AddChannelView channelview = new AddChannelView(this, channels);
b.setView(channelview);
b.setTitle(R.string.channels);
b.setPositiveButton(R.string.action_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
channels = channelview.getChannels();
}
});
b.setNegativeButton(R.string.action_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
return;
}
});
b.show();
}
/**
* On click add server or cancel activity
*/
@Override
public void onClick(View v) {
switch ( v.getId() ) {
case R.id.aliases:
showAliasList();
break;
case R.id.authentication:
showAuthDialog();
break;
case R.id.channels:
showChannelList();
break;
case R.id.commands:
showCommmandList();
break;
case R.id.add:
save();
break;
case R.id.cancel:
setResult(RESULT_CANCELED);
finish();
break;
}
}
/**
* Try to save server.
*/
private void save() {
try {
validateServer();
validateIdentity();
if( server == null || _action.equals(ACTION_DUPE_SERVER) ) {
addServer();
} else {
updateServer();
}
setResult(RESULT_OK);
finish();
} catch ( ValidationException e ) {
Toast.makeText(this, e.getMessage(), Toast.LENGTH_SHORT).show();
}
}
/**
* 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();
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);
Atomic.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();
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);
Atomic.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();
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.setAutoconnect(autoConnect);
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().trim();
String username = ((EditText)findViewById(R.id.username)).getText().toString().trim();
String realname = ((EditText)findViewById(R.id.realname)).getText().toString().trim();
Identity identity = new Identity();
identity.setNickname(nickname);
identity.setUsername(username);
identity.setRealName(realname);
identity.setAliases(aliases);
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 username = ((EditText)findViewById(R.id.username)).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( username.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
// We should accept @ as well so that users who like ZNC's client tags
// http://wiki.znc.in/FAQ#Why_do_I_get_an_.22Incorrect_Password.22_every_time_I_connect_even_though_my_pass_is_correct.3F
Pattern identPattern = Pattern.compile("^[a-zA-Z0-9\\[\\]\\-@_/]+$");
if( !identPattern.matcher(username).matches() ) {
throw new ValidationException(getResources().getString(R.string.validation_invalid_ident));
}
}
}