/*
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.App;
import indrora.atomic.Atomic;
import indrora.atomic.FirstRunActivity;
import indrora.atomic.adapter.ServerListAdapter;
import indrora.atomic.db.Database;
import indrora.atomic.irc.IRCBinder;
import indrora.atomic.irc.IRCService;
import indrora.atomic.listener.ServerListener;
import indrora.atomic.model.Broadcast;
import indrora.atomic.model.Extra;
import indrora.atomic.model.Server;
import indrora.atomic.model.Status;
import indrora.atomic.receiver.ServerReceiver;
import indrora.atomic.utils.LatchingValue;
import java.util.ArrayList;
import indrora.atomic.R;
import android.support.v7.app.AppCompatActivity;
import android.app.AlertDialog;
import android.app.Notification;
import android.app.Service;
import android.content.ComponentName;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.ServiceConnection;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.support.v7.app.AppCompatActivity;
import android.text.format.Time;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.widget.AdapterView;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AdapterView.OnItemLongClickListener;
import android.widget.ListView;
import android.widget.Toast;
/**
* List of servers
*
* @author Sebastian Kaspari <sebastian@yaaic.org>
*/
public class ServersActivity extends AppCompatActivity implements ServiceConnection, ServerListener, OnItemClickListener, OnItemLongClickListener {
private IRCBinder binder;
private ServerReceiver receiver;
private ServerListAdapter adapter;
private ListView list;
private static int instanceCount = 0;
static LatchingValue<Boolean> doAutoconnect = new LatchingValue<Boolean>(true, false);
/**
* On create
*/
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* With activity:launchMode = standard, we get duplicated activities
* depending on the task the app was started in. In order to avoid
* stacking up of this duplicated activities we keep a count of this
* root activity and let it finish if it already exists
*
* Launching the app via the notification icon creates a new task,
* and there doesn't seem to be a way around this so this is needed
*/
if( instanceCount > 0 ) {
finish();
}
instanceCount++;
setContentView(R.layout.servers);
setSupportActionBar((android.support.v7.widget.Toolbar)findViewById(R.id.toolbar));
adapter = new ServerListAdapter();
list = (ListView)findViewById(android.R.id.list);
list.setAdapter(adapter);
list.setOnItemClickListener(this);
list.setOnItemLongClickListener(this);
}
/**
* On Destroy
*/
@Override
public void onDestroy() {
super.onDestroy();
instanceCount--;
}
/**
* On resume
*/
@Override
public void onResume() {
super.onResume();
// Start and connect to service
Intent intent = new Intent(this, IRCService.class);
intent.setAction(IRCService.ACTION_BACKGROUND);
startService(intent);
int flags = 0;
if( android.os.Build.VERSION.SDK_INT >= 14 ) {
flags |= Context.BIND_ABOVE_CLIENT;
}
bindService(intent, this, flags);
receiver = new ServerReceiver(this);
registerReceiver(receiver, new IntentFilter(Broadcast.SERVER_UPDATE));
adapter.loadServers();
}
/**
* On pause
*/
@Override
public void onPause() {
super.onPause();
if( binder != null && binder.getService() != null ) {
binder.getService().checkServiceStatus();
}
unbindService(this);
unregisterReceiver(receiver);
}
/**
* Service connected to Activity
*/
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binder = (IRCBinder)service;
// This is kinda cute
// We'll always keep the service in the foreground, just to remind people that it's there.
Intent intent = new Intent(this, IRCService.class);
intent.setAction(IRCService.ACTION_FOREGROUND);
startService(intent);
// Autoconnect is done via a Latching Value. There's no real reason to have it
// a latchingValue but it lets us later on reset the Autoconnect fields.
autoconnect();
}
/**
* Do the autoconnect stuff
*/
private void autoconnect() {
// If we don't have any servers to go with.
if( Atomic.getInstance().getServersAsArrayList().size() < 1 )
return;
// Or we've done this already
if( !doAutoconnect.getValue() )
return;
// We don't need to get this far.
// Are we connected to the greater wide not-us?
NetworkInfo ninf = ((ConnectivityManager)(this.getSystemService(Service.CONNECTIVITY_SERVICE))).getActiveNetworkInfo();
// If there's no way out, or we aren't actually connected,
if( ninf == null || ninf.getState() != NetworkInfo.State.CONNECTED ) {
// We don't need to bother, but we should say something.
Toast.makeText(this, "Autoconnect skipped due to network outage", Toast.LENGTH_LONG).show();
return;
}
// Some slime...
Log.d("ServerList", "Doing autoconnect");
for( int idx = 0; idx < adapter.getCount(); idx++ ) {
Server s = adapter.getItem(idx);
if( s.getAutoconnect() && s.getStatus() == Status.DISCONNECTED ) {
ConnectServer(s);
}
}
}
/**
* Service disconnected from Activity
*/
@Override
public void onServiceDisconnected(ComponentName name) {
binder = null;
}
/**
* On server selected
*/
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
Server server = adapter.getItem(position);
if( server == null ) {
// "Add server" was selected
startActivityForResult(new Intent(this, AddServerActivity.class), 0);
return;
}
Intent intent = new Intent(this, ConversationActivity.class);
if( server.getStatus() == Status.DISCONNECTED && !server.mayReconnect() ) {
server.setStatus(Status.PRE_CONNECTING);
intent.putExtra("connect", true);
}
intent.putExtra("serverId", server.getId());
startActivity(intent);
}
private void ConnectServer(Server s) {
if( s.getStatus() == Status.DISCONNECTED ) {
binder.connect(s);
s.setStatus(Status.CONNECTING);
adapter.notifyDataSetChanged();
}
}
private void DisconnectServer(Server server) {
if( server.getStatus() == Status.DISCONNECTED ) {
return;
}
server.clearConversations();
server.setStatus(Status.DISCONNECTED);
server.setMayReconnect(false);
binder.getService().removeReconnection(server.getId());
binder.getService().getConnection(server.getId()).disconnect();
}
/**
* On long click
*/
@Override
public boolean onItemLongClick(AdapterView<?> l, View v, int position, long id) {
final Server server = adapter.getItem(position);
if( server == null ) {
// "Add server" view selected
return true;
}
// This lets us change if we're going to CONNECT or DISCONNECT from a server from the long-press menu.
int mangleString = R.string.connect;
if( server.getStatus() != Status.DISCONNECTED ) {
mangleString = R.string.disconnect;
}
final int fMangleString = mangleString;
final CharSequence[] items = {
getString(fMangleString),
getString(R.string.edit),
getString(R.string.duplicate_server),
getString(R.string.delete)
};
AlertDialog.Builder builder = new AlertDialog.Builder(this);
builder.setTitle(server.getTitle());
builder.setItems(items, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int item) {
switch ( item ) {
case 0: // Connect/Disconnect
if( fMangleString == R.string.connect ) {
ConnectServer(server);
} else if( fMangleString == R.string.disconnect ) {
DisconnectServer(server);
}
break;
case 1: // Edit
editServer(server.getId());
break;
case 2:
duplicateServer(server.getId());
break;
case 3: // Delete
binder.getService().getConnection(server.getId()).quitServer();
deleteServer(server.getId());
break;
}
}
});
AlertDialog alert = builder.create();
alert.show();
return true;
}
/**
* Start activity to edit server with given id
*
* @param serverId The id of the server
*/
private void editServer(int serverId) {
Server server = Atomic.getInstance().getServerById(serverId);
if( server.getStatus() != Status.DISCONNECTED ) {
Toast.makeText(this, getResources().getString(R.string.disconnect_before_editing), Toast.LENGTH_SHORT).show();
} else {
Intent intent = new Intent(this, AddServerActivity.class);
intent.setAction(AddServerActivity.ACTION_EDIT_SERVER);
intent.putExtra(Extra.SERVER, serverId);
startActivityForResult(intent, 0);
}
}
private void duplicateServer(int serverId) {
Intent intent = new Intent(this, AddServerActivity.class);
intent.setAction(AddServerActivity.ACTION_DUPE_SERVER);
intent.putExtra(Extra.SERVER, serverId);
startActivityForResult(intent, 0);
}
/**
* Options Menu (Menu Button pressed)
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
// inflate from xml
MenuInflater inflater = new MenuInflater(this);
inflater.inflate(R.menu.servers, menu);
return true;
}
/**
* On menu item selected
*/
@Override
public boolean onOptionsItemSelected( MenuItem item) {
switch ( item.getItemId() ) {
case R.id.add:
startActivityForResult(new Intent(this, AddServerActivity.class), 0);
break;
case R.id.about:
startActivity(new Intent(this, AboutActivity.class));
break;
case R.id.settings:
startActivity(new Intent(this, SettingsActivity.class));
break;
case R.id.disconnect_all:
ArrayList<Server> mServers = Atomic.getInstance().getServersAsArrayList();
binder.getService().clearReconnectList();
for( Server server : mServers ) {
DisconnectServer(server);
}
// ugly
// binder.getService().stopForegroundCompat(R.string.app_name);
}
return true;
}
/**
* On activity result
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if( resultCode == RESULT_OK ) {
// Refresh list from database
adapter.loadServers();
}
}
/**
* Delete server
*
* @param serverId
*/
public void deleteServer(int serverId) {
Database db = new Database(this);
db.removeServerById(serverId);
db.close();
// make sure we don't accidentally reconnect it
binder.getService().removeReconnection(serverId);
Atomic.getInstance().removeServerById(serverId);
adapter.loadServers();
}
/**
* On server status update
*/
@Override
public void onStatusUpdate() {
adapter.loadServers();
}
long lastBackPress = 0;
@Override
public void onBackPressed() {
if( lastBackPress + 2000 > System.currentTimeMillis() ) {
ArrayList<Server> mServers = Atomic.getInstance().getServersAsArrayList();
for( Server server : mServers ) {
if( binder.getService().hasConnection(server.getId()) ) {
DisconnectServer(server);
}
}
binder.getService().stopForegroundCompat(R.string.app_name);
System.exit(0);
} else {
Toast.makeText(this, R.string.back_twice_exit, Toast.LENGTH_LONG).show();
lastBackPress = System.currentTimeMillis();
}
}
}