diff --git a/AndroidManifest.xml b/AndroidManifest.xml index b96ed87..dfca2e6 100644 --- a/AndroidManifest.xml +++ b/AndroidManifest.xml @@ -22,11 +22,14 @@ along with Yaaic. If not, see . - + android:versionName="0.2"> + + android:label="@string/app_name" + android:launchMode="singleTask"> diff --git a/src/org/yaaic/Yaaic.java b/src/org/yaaic/Yaaic.java index 990d920..f07bb94 100644 --- a/src/org/yaaic/Yaaic.java +++ b/src/org/yaaic/Yaaic.java @@ -23,8 +23,11 @@ package org.yaaic; import java.util.ArrayList; import java.util.HashMap; +import org.yaaic.db.Database; import org.yaaic.model.Server; +import android.content.Context; + /** * Global Master Class :) * @@ -35,6 +38,7 @@ public class Yaaic public static Yaaic instance; private HashMap servers; + private boolean serversLoaded = false; /** * Private constructor, you may want to use static getInstance() @@ -44,6 +48,23 @@ public class Yaaic servers = new HashMap(); } + /** + * Load servers from database + * + * @param context + */ + public void loadServers(Context context) + { + if (!serversLoaded) { + Database db = new Database(context); + servers = db.getServers(); + db.close(); + + //context.sendBroadcast(new Intent(Broadcast.SERVER_UPDATE)); + serversLoaded = true; + } + } + /** * Get global Yaaic instance * diff --git a/src/org/yaaic/activity/ConversationActivity.java b/src/org/yaaic/activity/ConversationActivity.java index 96bc67e..dcc97a1 100644 --- a/src/org/yaaic/activity/ConversationActivity.java +++ b/src/org/yaaic/activity/ConversationActivity.java @@ -99,7 +99,7 @@ public class ConversationActivity extends Activity implements ServiceConnection, ((TextView) findViewById(R.id.title)).setText(server.getTitle()); ((EditText) findViewById(R.id.input)).setOnKeyListener(this); - + deckAdapter = new DeckAdapter(); deck = (Gallery) findViewById(R.id.deck); deck.setOnItemSelectedListener(this); @@ -124,7 +124,10 @@ public class ConversationActivity extends Activity implements ServiceConnection, ((ImageView) findViewById(R.id.status)).setImageResource(server.getStatusIcon()); + // Start service Intent intent = new Intent(this, IRCService.class); + intent.setAction(IRCService.ACTION_FOREGROUND); + startService(intent); bindService(intent, this, 0); channelReceiver = new ConversationReceiver(server.getId(), this); @@ -146,6 +149,10 @@ public class ConversationActivity extends Activity implements ServiceConnection, while (conversation.hasBufferedMessages()) { if (conversation.getMessageListAdapter() != null) { conversation.getMessageListAdapter().addMessage(conversation.pollBufferedMessage()); + } else { + // There's no adapter... so let's remove the buffer + conversation.clearBuffer(); + Log.d("Yaaic", "No MessageListAdapter found - clear buffer"); } } } @@ -159,6 +166,15 @@ public class ConversationActivity extends Activity implements ServiceConnection, { super.onPause(); + binder.getService().checkServiceStatus(); + + /*if (!binder.getService().hasConnections()) { + Log.d("Yaaic", "Stopping service"); + //binder.getService().stopSelf(); + } else { + Log.d("Yaaic", "Unbinding service"); + }*/ + unbindService(this); unregisterReceiver(channelReceiver); unregisterReceiver(serverReceiver); @@ -210,10 +226,12 @@ public class ConversationActivity extends Activity implements ServiceConnection, switch (item.getItemId()) { case R.id.disconnect: binder.getService().getConnection(serverId).quitServer(); + server.setStatus(Status.DISCONNECTED); server.clearConversations(); setResult(RESULT_OK); finish(); break; + case R.id.join: startActivityForResult(new Intent(this, JoinActivity.class), 0); break; diff --git a/src/org/yaaic/activity/ServersActivity.java b/src/org/yaaic/activity/ServersActivity.java index 7b9ea0f..a4803df 100644 --- a/src/org/yaaic/activity/ServersActivity.java +++ b/src/org/yaaic/activity/ServersActivity.java @@ -91,6 +91,7 @@ public class ServersActivity extends ListActivity implements ServiceConnection, // Start and connect to service Intent intent = new Intent(this, IRCService.class); + intent.setAction(IRCService.ACTION_BACKGROUND); startService(intent); bindService(intent, this, 0); diff --git a/src/org/yaaic/irc/IRCBinder.java b/src/org/yaaic/irc/IRCBinder.java index d8feead..a4faf0f 100644 --- a/src/org/yaaic/irc/IRCBinder.java +++ b/src/org/yaaic/irc/IRCBinder.java @@ -24,7 +24,6 @@ import org.jibble.pircbot.IrcException; import org.jibble.pircbot.NickAlreadyInUseException; import org.yaaic.R; import org.yaaic.model.Broadcast; -import org.yaaic.model.Extra; import org.yaaic.model.Message; import org.yaaic.model.Server; import org.yaaic.model.ServerInfo; diff --git a/src/org/yaaic/irc/IRCConnection.java b/src/org/yaaic/irc/IRCConnection.java index 7ffc76c..dc9d37f 100644 --- a/src/org/yaaic/irc/IRCConnection.java +++ b/src/org/yaaic/irc/IRCConnection.java @@ -1093,6 +1093,10 @@ public class IRCConnection extends PircBot @Override public void quitServer() { - quitServer("Yaaic - Yet another Android IRC client - http://www.yaaic.org"); + new Thread() { + public void run() { + quitServer("Yaaic - Yet another Android IRC client - http://www.yaaic.org"); + } + }.start(); } } diff --git a/src/org/yaaic/irc/IRCService.java b/src/org/yaaic/irc/IRCService.java index fb54c58..c2324e6 100644 --- a/src/org/yaaic/irc/IRCService.java +++ b/src/org/yaaic/irc/IRCService.java @@ -20,12 +20,21 @@ along with Yaaic. If not, see . */ package org.yaaic.irc; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; import java.util.HashMap; +import org.yaaic.R; import org.yaaic.Yaaic; import org.yaaic.db.Database; import org.yaaic.model.Broadcast; +import org.yaaic.model.Server; +import org.yaaic.activity.ServersActivity; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.util.Log; @@ -41,7 +50,22 @@ public class IRCService extends Service private IRCBinder binder; private HashMap connections; + private boolean foreground = false; + @SuppressWarnings("unchecked") + private static final Class[] mStartForegroundSignature = new Class[] { int.class, Notification.class }; + @SuppressWarnings("unchecked") + private static final Class[] mStopForegroundSignature = new Class[] { boolean.class }; + + public static final String ACTION_FOREGROUND = "org.yaaic.service.foreground"; + public static final String ACTION_BACKGROUND = "org.yaaic.service.background"; + + private NotificationManager mNM; + private Method mStartForeground; + private Method mStopForeground; + private Object[] mStartForegroundArgs = new Object[2]; + private Object[] mStopForegroundArgs = new Object[1]; + /** * Create new service */ @@ -55,15 +79,24 @@ public class IRCService extends Service this.binder = new IRCBinder(this); } + /** + * On create + */ @Override public void onCreate() { - super.onCreate(); - - Log.d(TAG, "Loading servers from database"); + mNM = (NotificationManager) getSystemService(NOTIFICATION_SERVICE); + + try { + mStartForeground = getClass().getMethod("startForeground", mStartForegroundSignature); + mStopForeground = getClass().getMethod("stopForeground", mStopForegroundSignature); + } catch (NoSuchMethodException e) { + // Running on an older platform. + mStartForeground = mStopForeground = null; + } // Load servers from Database - Database db = new Database(this); + Database db = new Database(this); Yaaic.getInstance().setServers(db.getServers()); db.close(); @@ -71,12 +104,113 @@ public class IRCService extends Service sendBroadcast(new Intent(Broadcast.SERVER_UPDATE)); } + /** + * On start (will be called on pre-2.0 platform. On 2.0 or later onStartCommand() + * will be called) + */ @Override public void onStart(Intent intent, int startId) { - Log.d(TAG, "onStart()"); - super.onStart(intent, startId); + Log.d(TAG, "Service onStart()"); + + handleCommand(intent); } + + /** + * On start command (Android >= 2.0) + * + * @param intent + * @param flags + * @param startId + * @return + */ + public int onStartCommand(Intent intent, int flags, int startId) + { + Log.d(TAG, "Service onStartCommand()"); + + handleCommand(intent); + + // We want this service to continue running until it is explicitly + // stopped, so return sticky. + //return START_STICKY; + return 1; + } + + + /** + * Handle command + * + * @param intent + */ + private void handleCommand(Intent intent) + { + if (ACTION_FOREGROUND.equals(intent.getAction())) { + foreground = true; + + // Set the icon, scrolling text and timestamp + Notification notification = new Notification(R.drawable.icon, "Connected", System.currentTimeMillis()); + + // The PendingIntent to launch our activity if the user selects this notification + PendingIntent contentIntent = PendingIntent.getActivity(this, 0, new Intent(this, ServersActivity.class), 0); + + // Set the info for the views that show in the notification panel. + notification.setLatestEventInfo(this, getText(R.string.app_name), "Connected", contentIntent); + + startForegroundCompat(R.string.app_name, notification); + } else if (ACTION_BACKGROUND.equals(intent.getAction()) && !foreground) { + stopForegroundCompat(R.string.app_name); + } + } + + + + /** + * This is a wrapper around the new startForeground method, using the older + * APIs if it is not available. + */ + private void startForegroundCompat(int id, Notification notification) + { + // If we have the new startForeground API, then use it. + if (mStartForeground != null) { + mStartForegroundArgs[0] = Integer.valueOf(id); + mStartForegroundArgs[1] = notification; + try { + mStartForeground.invoke(this, mStartForegroundArgs); + } catch (InvocationTargetException e) { + // Should not happen. + } catch (IllegalAccessException e) { + // Should not happen. + } + } else { + // Fall back on the old API. + setForeground(true); + mNM.notify(id, notification); + } + } + + /** + * This is a wrapper around the new stopForeground method, using the older + * APIs if it is not available. + */ + private void stopForegroundCompat(int id) + { + // If we have the new stopForeground API, then use it. + if (mStopForeground != null) { + mStopForegroundArgs[0] = Boolean.TRUE; + try { + mStopForeground.invoke(this, mStopForegroundArgs); + } catch (InvocationTargetException e) { + // Should not happen. + } catch (IllegalAccessException e) { + // Should not happen. + } + } else { + // Fall back on the old API. Note to cancel BEFORE changing the + // foreground state, since we could be killed at that point. + mNM.cancel(id); + setForeground(false); + } + } /** * Get connection for given server @@ -96,6 +230,42 @@ public class IRCService extends Service return connection; } + /** + * Check status of service + */ + public void checkServiceStatus() + { + boolean shutDown = true; + ArrayList mServers = Yaaic.getInstance().getServersAsArrayList(); + int mSize = mServers.size(); + Server server; + + for (int i = 0; i < mSize; i++) { + server = mServers.get(i); + if (server.isDisconnected()) { + connections.remove(server.getId()); + } else { + shutDown = false; + } + } + + if (shutDown) { + foreground = false; + stopSelf(); + } + } + + /** + * On Destroy + */ + @Override + public void onDestroy() + { + // Make sure our notification is gone. + stopForegroundCompat(R.string.app_name); + } + + /** * On Activity binding to this service *