reworked account managment. now status display actually works

This commit is contained in:
Daniel Gultsch 2014-02-04 15:09:50 +01:00
parent 14a171b088
commit 0d80d88736
8 changed files with 287 additions and 160 deletions

View file

@ -35,16 +35,16 @@
android:layout_height="wrap_content"
android:text="Status: "
android:textStyle="bold"
android:textSize="14sp" />
android:textSize="16sp" />
<TextView
android:id="@+id/account_status"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="#669900"
android:text="Online"
android:text="unknown"
android:textStyle="bold"
android:textSize="14sp"/>
android:textSize="16sp"/>
</LinearLayout>

View file

@ -18,11 +18,20 @@ public class Account extends AbstractEntity{
public static final int OPTION_USETLS = 0;
public static final int STATUS_OFFLINE = 0;
public static final int STATUS_ONLINE = 1;
public static final int STATUS_UNAUTHORIZED = 2;
public static final int STATUS_NOINTERNET = 3;
public static final int STATUS_TLS_ERROR = 4;
public static final int STATUS_SERVER_NOT_FOUND = 5;
protected String username;
protected String server;
protected String password;
protected int options;
protected String rosterVersion;
protected String resource;
protected int status = 0;
protected boolean online = false;
@ -70,8 +79,16 @@ public class Account extends AbstractEntity{
this.password = password;
}
public boolean isOnline() {
return online;
public void setStatus(int status) {
this.status = status;
}
public int getStatus() {
return this.status;
}
public void setResource(String resource) {
this.resource = resource;
}
public String getJid() {

View file

@ -1,6 +1,5 @@
package de.gultsch.chat.services;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Hashtable;
import java.util.List;
@ -10,7 +9,7 @@ import de.gultsch.chat.entities.Contact;
import de.gultsch.chat.entities.Conversation;
import de.gultsch.chat.entities.Message;
import de.gultsch.chat.persistance.DatabaseBackend;
import de.gultsch.chat.ui.ConversationActivity;
import de.gultsch.chat.ui.OnAccountListChangedListener;
import de.gultsch.chat.ui.OnConversationListChangedListener;
import de.gultsch.chat.ui.OnRosterFetchedListener;
import de.gultsch.chat.utils.UIHelper;
@ -19,20 +18,15 @@ import de.gultsch.chat.xmpp.IqPacket;
import de.gultsch.chat.xmpp.MessagePacket;
import de.gultsch.chat.xmpp.OnIqPacketReceived;
import de.gultsch.chat.xmpp.OnMessagePacketReceived;
import de.gultsch.chat.xmpp.OnStatusChanged;
import de.gultsch.chat.xmpp.XmppConnection;
import android.R;
import android.R.dimen;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.os.Binder;
import android.os.IBinder;
import android.os.PowerManager;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.TaskStackBuilder;
import android.util.Log;
public class XmppConnectionService extends Service {
@ -48,6 +42,7 @@ public class XmppConnectionService extends Service {
private Hashtable<Account, XmppConnection> connections = new Hashtable<Account, XmppConnection>();
private OnConversationListChangedListener convChangedListener = null;
private OnAccountListChangedListener accountChangedListener = null;
private final IBinder mBinder = new XmppConnectionBinder();
private OnMessagePacketReceived messageListener = new OnMessagePacketReceived() {
@ -79,6 +74,16 @@ public class XmppConnectionService extends Service {
}
}
};
private OnStatusChanged statusListener = new OnStatusChanged() {
@Override
public void onStatusChanged(Account account) {
Log.d(LOGTAG,account.getJid()+" changed status to "+account.getStatus());
if (accountChangedListener != null) {
accountChangedListener.onAccountListChangedListener();
}
}
};
public class XmppConnectionBinder extends Binder {
public XmppConnectionService getService() {
@ -88,15 +93,10 @@ public class XmppConnectionService extends Service {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
for (Account account : accounts) {
if (!connections.containsKey(account)) {
XmppConnection connection = new XmppConnection(account, pm);
connection
.setOnMessagePacketReceivedListener(this.messageListener);
Thread thread = new Thread(connection);
thread.start();
this.connections.put(account, connection);
this.connections.put(account, this.createConnection(account));
}
}
return START_STICKY;
@ -108,46 +108,43 @@ public class XmppConnectionService extends Service {
this.accounts = databaseBackend.getAccounts();
}
public XmppConnection createConnection(Account account) {
PowerManager pm = (PowerManager) getSystemService(Context.POWER_SERVICE);
XmppConnection connection = new XmppConnection(account, pm);
connection
.setOnMessagePacketReceivedListener(this.messageListener);
connection.setOnStatusChangedListener(this.statusListener );
Thread thread = new Thread(connection);
thread.start();
return connection;
}
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
public void sendMessage(final Account account, final Message message) {
new Thread() {
@Override
public void run() {
Log.d(LOGTAG, "sending message for " + account.getJid()
+ " to: " + message.getCounterpart());
Log.d(LOGTAG, "sending message for " + account.getJid() + " to: "
+ message.getCounterpart());
databaseBackend.createMessage(message);
MessagePacket packet = new MessagePacket();
packet.setType(MessagePacket.TYPE_CHAT);
packet.setTo(message.getCounterpart());
packet.setFrom(account.getJid());
packet.setBody(message.getBody());
try {
connections.get(account).sendMessagePacket(packet);
message.setStatus(Message.STATUS_SEND);
databaseBackend.updateMessage(message);
} catch (IOException e) {
Log.d(LOGTAG,
"io exception during send. message is in database. will try again later");
}
}
}.start();
}
public void getRoster(final Account account,
final OnRosterFetchedListener listener) {
new Thread() {
@Override
public void run() {
IqPacket iqPacket = new IqPacket(IqPacket.TYPE_GET);
Element query = new Element("query");
query.setAttribute("xmlns", "jabber:iq:roster");
query.setAttribute("ver", "");
iqPacket.addChild(query);
try {
connections.get(account).sendIqPacket(iqPacket,
new OnIqPacketReceived() {
@ -162,8 +159,8 @@ public class XmppConnectionService extends Service {
if (name == null) {
name = jid.split("@")[0];
}
Contact contact = new Contact(account,
name, jid, null);
Contact contact = new Contact(account, name, jid,
null);
contacts.add(contact);
}
if (listener != null) {
@ -171,11 +168,6 @@ public class XmppConnectionService extends Service {
}
}
});
} catch (IOException e) {
Log.d(LOGTAG, "io error during roster fetch");
}
}
}.start();
}
public void addConversation(Conversation conversation) {
@ -249,14 +241,32 @@ public class XmppConnectionService extends Service {
public void createAccount(Account account) {
databaseBackend.createAccount(account);
this.accounts.add(account);
this.connections.put(account, this.createConnection(account));
if (accountChangedListener!=null) accountChangedListener.onAccountListChangedListener();
}
public void updateAccount(Account account) {
databaseBackend.updateAccount(account);
XmppConnection connection = this.connections.get(account);
if (connection != null) {
connection.disconnect();
this.connections.remove(account);
}
this.connections.put(account, this.createConnection(account));
if (accountChangedListener!=null) accountChangedListener.onAccountListChangedListener();
}
public void deleteAccount(Account account) {
Log.d(LOGTAG,"called delete account");
if (this.connections.containsKey(account)) {
Log.d(LOGTAG,"found connection. disconnecting");
this.connections.get(account).disconnect();
this.connections.remove(account);
this.accounts.remove(account);
}
databaseBackend.deleteAccount(account);
if (accountChangedListener!=null) accountChangedListener.onAccountListChangedListener();
}
public void setOnConversationListChangedListener(
@ -267,4 +277,12 @@ public class XmppConnectionService extends Service {
public void removeOnConversationListChangedListener() {
this.convChangedListener = null;
}
public void setOnAccountListChangedListener(OnAccountListChangedListener listener) {
this.accountChangedListener = listener;
}
public void removeOnAccountListChangedListener() {
this.accountChangedListener = null;
}
}

View file

@ -6,7 +6,6 @@ import java.util.List;
import de.gultsch.chat.R;
import de.gultsch.chat.entities.Account;
import de.gultsch.chat.ui.EditAccount.EditAccountListener;
import android.app.ActionBar;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
@ -25,10 +24,29 @@ import android.widget.TextView;
public class ManageAccountActivity extends XmppActivity {
protected List<Account> accountList = new ArrayList<Account>();
protected ListView accountListView;
protected ArrayAdapter<Account> accountListViewAdapter;
protected OnAccountListChangedListener accountChanged = new OnAccountListChangedListener() {
@Override
public void onAccountListChangedListener() {
Log.d("xmppService", "ui on account list changed listener");
accountList.clear();
accountList.addAll(xmppConnectionService.getAccounts());
runOnUiThread(new Runnable() {
@Override
public void run() {
if (accountList.size() == 1) {
startActivity(new Intent(getApplicationContext(),
NewConversationActivity.class));
}
accountListViewAdapter.notifyDataSetChanged();
}
});
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -38,14 +56,39 @@ public class ManageAccountActivity extends XmppActivity {
setContentView(R.layout.manage_accounts);
accountListView = (ListView) findViewById(R.id.account_list);
accountListViewAdapter = new ArrayAdapter<Account>(getApplicationContext(), R.layout.account_row, this.accountList) {
accountListViewAdapter = new ArrayAdapter<Account>(
getApplicationContext(), R.layout.account_row, this.accountList) {
@Override
public View getView(int position, View view, ViewGroup parent) {
Account account = getItem(position);
if (view == null) {
LayoutInflater inflater = (LayoutInflater) getSystemService(Context.LAYOUT_INFLATER_SERVICE);
view = (View) inflater.inflate(R.layout.account_row, null);
}
((TextView) view.findViewById(R.id.account_jid)).setText(getItem(position).getJid());
((TextView) view.findViewById(R.id.account_jid))
.setText(account.getJid());
TextView statusView = (TextView) view
.findViewById(R.id.account_status);
switch (account.getStatus()) {
case Account.STATUS_ONLINE:
statusView.setText("online");
statusView.setTextColor(0xFF83b600);
break;
case Account.STATUS_OFFLINE:
statusView.setText("offline");
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_UNAUTHORIZED:
statusView.setText("unauthorized");
statusView.setTextColor(0xFFe92727);
break;
case Account.STATUS_SERVER_NOT_FOUND:
statusView.setText("server not found");
statusView.setTextColor(0xFFe92727);
break;
default:
break;
}
return view;
}
@ -54,8 +97,8 @@ public class ManageAccountActivity extends XmppActivity {
accountListView.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> arg0, View view, int position,
long arg3) {
public void onItemClick(AdapterView<?> arg0, View view,
int position, long arg3) {
EditAccount dialog = new EditAccount();
dialog.setAccount(accountList.get(position));
dialog.setEditAccountListener(new EditAccountListener() {
@ -67,41 +110,27 @@ public class ManageAccountActivity extends XmppActivity {
@Override
public void onAccountDelete(Account account) {
Log.d("gultsch","deleting account:"+account.getJid());
xmppConnectionService.deleteAccount(account);
//dont bother finding the right account in the frontend list. just reload
accountList.clear();
accountList.addAll(xmppConnectionService.getAccounts());
accountListViewAdapter.notifyDataSetChanged();
}
});
dialog.show(getFragmentManager(),"edit_account");
dialog.show(getFragmentManager(), "edit_account");
}
});
}
@Override
public void onStart() {
super.onStart();
protected void onStop() {
super.onStop();
if (xmppConnectionServiceBound) {
this.accountList.clear();
this.accountList.addAll(xmppConnectionService
.getAccounts());
accountListViewAdapter.notifyDataSetChanged();
if (this.accountList.size() == 0) {
getActionBar().setDisplayHomeAsUpEnabled(false);
}
xmppConnectionService.removeOnAccountListChangedListener();
unbindService(mConnection);
xmppConnectionServiceBound = false;
}
}
@Override
void onBackendConnected() {
Log.d("gultsch","called on backend connected");
xmppConnectionService.setOnAccountListChangedListener(accountChanged);
this.accountList.clear();
this.accountList.addAll(xmppConnectionService.getAccounts());
accountListViewAdapter.notifyDataSetChanged();
@ -141,19 +170,14 @@ public class ManageAccountActivity extends XmppActivity {
@Override
public void onAccountEdited(Account account) {
xmppConnectionService.createAccount(account);
accountList.add(account);
accountListViewAdapter.notifyDataSetChanged();
activity.getActionBar().setDisplayHomeAsUpEnabled(true);
if (accountList.size() == 1) {
activity.startActivity(new Intent(activity,NewConversationActivity.class));
}
}
@Override
public void onAccountDelete(Account account) {
//this will never be called
// this will never be called
}
});
dialog.show(getFragmentManager(),"add_account");
dialog.show(getFragmentManager(), "add_account");
}
}

View file

@ -0,0 +1,5 @@
package de.gultsch.chat.ui;
public interface OnAccountListChangedListener {
public void onAccountListChangedListener();
}

View file

@ -3,12 +3,37 @@ package de.gultsch.chat.xml;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Collection;
import java.util.Iterator;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import android.util.Log;
public class TagWriter {
OutputStreamWriter writer;
private OutputStreamWriter outputStream;
private LinkedBlockingQueue<String> writeQueue = new LinkedBlockingQueue<String>();
private Thread writer = new Thread() {
public boolean shouldStop = false;
@Override
public void run() {
while(!shouldStop) {
try {
String output = writeQueue.take();
outputStream.write(output);
outputStream.flush();
} catch (IOException e) {
Log.d("xmppService", "error writing to stream");
} catch (InterruptedException e) {
}
}
}
};
public TagWriter() {
@ -16,31 +41,29 @@ public class TagWriter {
public TagWriter(OutputStream out) {
this.setOutputStream(out);
writer.start();
}
public void setOutputStream(OutputStream out) {
this.writer = new OutputStreamWriter(out);
this.outputStream = new OutputStreamWriter(out);
if (!writer.isAlive()) writer.start();
}
public TagWriter beginDocument() throws IOException {
writer.write("<?xml version='1.0'?>");
public TagWriter beginDocument() {
writeQueue.add("<?xml version='1.0'?>");
return this;
}
public TagWriter writeTag(Tag tag) throws IOException {
writer.write(tag.toString());
public TagWriter writeTag(Tag tag) {
writeQueue.add(tag.toString());
return this;
}
public void flush() throws IOException {
writer.flush();
public void writeString(String string) {
writeQueue.add(string);
}
public void writeString(String string) throws IOException {
writer.write(string);
}
public void writeElement(Element element) throws IOException {
writer.write(element.toString());
public void writeElement(Element element) {
writeQueue.add(element.toString());
}
}

View file

@ -0,0 +1,7 @@
package de.gultsch.chat.xmpp;
import de.gultsch.chat.entities.Account;
public interface OnStatusChanged {
public void onStatusChanged(Account account);
}

View file

@ -39,7 +39,7 @@ public class XmppConnection implements Runnable {
private boolean isTlsEncrypted = false;
private boolean isAuthenticated = false;
//private boolean shouldUseTLS = false;
private boolean shouldReConnect = true;
private boolean shouldConnect = true;
private boolean shouldBind = true;
private boolean shouldAuthenticate = true;
private Element streamFeatures;
@ -52,8 +52,7 @@ public class XmppConnection implements Runnable {
private OnPresencePacketReceived presenceListener = null;
private OnIqPacketReceived unregisteredIqListener = null;
private OnMessagePacketReceived messageListener = null;
private String resource = null;
private OnStatusChanged statusListener = null;
public XmppConnection(Account account, PowerManager pm) {
this.account = account;
@ -66,7 +65,6 @@ public class XmppConnection implements Runnable {
protected void connect() {
try {
socket = new Socket(account.getServer(), 5222);
Log.d(LOGTAG, "starting new socket");
OutputStream out = socket.getOutputStream();
tagWriter.setOutputStream(out);
InputStream in = socket.getInputStream();
@ -77,40 +75,54 @@ public class XmppConnection implements Runnable {
while ((nextTag = tagReader.readTag()) != null) {
if (nextTag.isStart("stream")) {
processStream(nextTag);
break;
} else {
Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName());
return;
}
}
if (socket.isConnected()) {
socket.close();
}
} catch (UnknownHostException e) {
Log.d(LOGTAG,account.getJid()+": error during connect. unknown host");
account.setStatus(Account.STATUS_SERVER_NOT_FOUND);
return;
} catch (IOException e) {
Log.d(LOGTAG, account.getJid()+": error during connect. io exception. falscher port?");
return;
if (shouldConnect) {
Log.d(LOGTAG,account.getJid()+": connection lost");
account.setStatus(Account.STATUS_OFFLINE);
if (statusListener!=null) {
statusListener.onStatusChanged(account);
}
}
} catch (XmlPullParserException e) {
Log.d(LOGTAG,"xml exception "+e.getMessage());
return;
}
}
@Override
public void run() {
while(shouldReConnect) {
shouldConnect = true;
while(shouldConnect) {
connect();
try {
if (shouldConnect) {
Thread.sleep(30000);
}
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Log.d(LOGTAG,"end run");
}
private void processStream(Tag currentTag) throws XmlPullParserException,
IOException {
Tag nextTag;
while (!(nextTag = tagReader.readTag()).isEnd("stream")) {
Tag nextTag = tagReader.readTag();
while ((nextTag != null) && (!nextTag.isEnd("stream"))) {
if (nextTag.isStart("error")) {
processStreamError(nextTag);
} else if (nextTag.isStart("features")) {
@ -124,6 +136,12 @@ public class XmppConnection implements Runnable {
tagReader.reset();
sendStartStream();
processStream(tagReader.readTag());
break;
} else if(nextTag.isStart("failure")) {
Element failure = tagReader.readElement(nextTag);
Log.d(LOGTAG,"read failure element"+failure.toString());
account.setStatus(Account.STATUS_UNAUTHORIZED);
tagWriter.writeTag(Tag.end("stream"));
} else if (nextTag.isStart("iq")) {
processIq(nextTag);
} else if (nextTag.isStart("message")) {
@ -134,6 +152,13 @@ public class XmppConnection implements Runnable {
Log.d(LOGTAG, "found unexpected tag: " + nextTag.getName()
+ " as child of " + currentTag.getName());
}
nextTag = tagReader.readTag();
}
if (account.getStatus() == Account.STATUS_ONLINE) {
account.setStatus(Account.STATUS_OFFLINE);
if (statusListener!=null) {
statusListener.onStatusChanged(account);
}
}
}
@ -190,11 +215,11 @@ public class XmppConnection implements Runnable {
}
}
private void sendStartTLS() throws XmlPullParserException, IOException {
private void sendStartTLS() {
Tag startTLS = Tag.empty("starttls");
startTLS.setAttribute("xmlns", "urn:ietf:params:xml:ns:xmpp-tls");
Log.d(LOGTAG,account.getJid()+": sending starttls");
tagWriter.writeTag(startTLS).flush();
tagWriter.writeTag(startTLS);
}
private void switchOverToTls(Tag currentTag) throws XmlPullParserException,
@ -213,6 +238,7 @@ public class XmppConnection implements Runnable {
isTlsEncrypted = true;
sendStartStream();
processStream(tagReader.readTag());
sslSocket.close();
} catch (IOException e) {
Log.d(LOGTAG, account.getJid()+": error on ssl '" + e.getMessage()+"'");
}
@ -227,7 +253,6 @@ public class XmppConnection implements Runnable {
auth.setContent(saslString);
Log.d(LOGTAG,account.getJid()+": sending sasl "+auth.toString());
tagWriter.writeElement(auth);
tagWriter.flush();
}
private void processStreamFeatures(Tag currentTag)
@ -249,12 +274,10 @@ public class XmppConnection implements Runnable {
startSession.addChild(session);
sendIqPacket(startSession, null);
tagWriter.writeElement(startSession);
tagWriter.flush();
}
Element presence = new Element("presence");
tagWriter.writeElement(presence);
tagWriter.flush();
}
}
@ -266,8 +289,12 @@ public class XmppConnection implements Runnable {
this.sendIqPacket(iq, new OnIqPacketReceived() {
@Override
public void onIqPacketReceived(Account account, IqPacket packet) {
resource = packet.findChild("bind").findChild("jid").getContent().split("/")[1];
Log.d(LOGTAG,account.getJid()+": new resource is "+resource);
String resource = packet.findChild("bind").findChild("jid").getContent().split("/")[1];
account.setResource(resource);
account.setStatus(Account.STATUS_ONLINE);
if (statusListener!=null) {
statusListener.onStatusChanged(account);
}
}
});
}
@ -276,7 +303,7 @@ public class XmppConnection implements Runnable {
Log.d(LOGTAG, "processStreamError");
}
private void sendStartStream() throws IOException {
private void sendStartStream() {
Tag stream = Tag.start("stream");
stream.setAttribute("from", account.getJid());
stream.setAttribute("to", account.getServer());
@ -284,32 +311,29 @@ public class XmppConnection implements Runnable {
stream.setAttribute("xml:lang", "en");
stream.setAttribute("xmlns", "jabber:client");
stream.setAttribute("xmlns:stream", "http://etherx.jabber.org/streams");
tagWriter.writeTag(stream).flush();
tagWriter.writeTag(stream);
}
private String nextRandomId() {
return new BigInteger(50, random).toString(32);
}
public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) throws IOException {
public void sendIqPacket(IqPacket packet, OnIqPacketReceived callback) {
String id = nextRandomId();
packet.setAttribute("id",id);
tagWriter.writeElement(packet);
if (callback != null) {
iqPacketCallbacks.put(id, callback);
}
tagWriter.flush();
Log.d(LOGTAG,account.getJid()+": sending: "+packet.toString());
}
public void sendMessagePacket(MessagePacket packet) throws IOException {
public void sendMessagePacket(MessagePacket packet){
tagWriter.writeElement(packet);
tagWriter.flush();
}
public void sendPresencePacket(PresencePacket packet) throws IOException {
public void sendPresencePacket(PresencePacket packet) {
tagWriter.writeElement(packet);
tagWriter.flush();
}
public void setOnMessagePacketReceivedListener(OnMessagePacketReceived listener) {
@ -323,4 +347,13 @@ public class XmppConnection implements Runnable {
public void setOnPresencePacketReceivedListener(OnPresencePacketReceived listener) {
this.presenceListener = listener;
}
public void setOnStatusChangedListener(OnStatusChanged listener) {
this.statusListener = listener;
}
public void disconnect() {
shouldConnect = false;
tagWriter.writeTag(Tag.end("stream"));
}
}