Newer
Older
package org.briarproject.android;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.ALREADY_RUNNING;
import static org.briarproject.api.lifecycle.LifecycleManager.StartResult.SUCCESS;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import javax.inject.Inject;
import org.briarproject.R;
import org.briarproject.api.ContactId;
import org.briarproject.api.android.AndroidExecutor;
import org.briarproject.api.android.AndroidNotificationManager;
import org.briarproject.api.db.DatabaseConfig;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.MessageAddedEvent;
import org.briarproject.api.lifecycle.LifecycleManager;
import org.briarproject.api.lifecycle.LifecycleManager.StartResult;
import org.briarproject.api.messaging.GroupId;
import roboguice.service.RoboService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ComponentName;
import android.content.ServiceConnection;
import android.support.v4.app.NotificationCompat;
public class BriarService extends RoboService implements EventListener {
private static final int ONGOING_NOTIFICATION_ID = 1;
private static final int FAILURE_NOTIFICATION_ID = 2;
Logger.getLogger(BriarService.class.getName());
private final AtomicBoolean created = new AtomicBoolean(false);
private final Binder binder = new BriarBinder();
@Inject private DatabaseConfig databaseConfig;
@Inject private AndroidNotificationManager notificationManager;
// Fields that are accessed from background threads must be volatile
@Inject private volatile LifecycleManager lifecycleManager;
@Inject private volatile AndroidExecutor androidExecutor;
@Inject @DatabaseExecutor private volatile Executor dbExecutor;
@Inject private volatile EventBus eventBus;
private volatile boolean started = false;
@Override
public void onCreate() {
super.onCreate();
LOG.info("Created");
LOG.info("Already created");
stopSelf();
return;
}
if (databaseConfig.getEncryptionKey() == null) {
LOG.info("No database key");
stopSelf();
return;
}
// Show an ongoing notification that the service is running
NotificationCompat.Builder b = new NotificationCompat.Builder(this);
b.setSmallIcon(R.drawable.ongoing_notification_icon);
b.setContentTitle(getText(R.string.ongoing_notification_title));
b.setContentText(getText(R.string.ongoing_notification_text));
b.setWhen(0); // Don't show the time
Intent i = new Intent(this, DashboardActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP |
FLAG_ACTIVITY_SINGLE_TOP);
b.setContentIntent(PendingIntent.getActivity(this, 0, i, 0));
startForeground(ONGOING_NOTIFICATION_ID, b.build());
// Start the services in a background thread
new Thread() {
@Override
public void run() {
StartResult result = lifecycleManager.startServices();
eventBus.addListener(BriarService.this);
} else if (result == ALREADY_RUNNING) {
LOG.info("Already running");
stopSelf();
LOG.warning("Startup failed: " + result);
showStartupFailureNotification(result);
private void showStartupFailureNotification(StartResult result) {
NotificationCompat.Builder b = new NotificationCompat.Builder(this);
b.setSmallIcon(android.R.drawable.stat_notify_error);
b.setContentTitle(getText(R.string.startup_failed_notification_title));
b.setContentText(getText(R.string.startup_failed_notification_text));
Intent i = new Intent(this, StartupFailureActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK);
i.putExtra("briar.START_RESULT", result);
i.putExtra("briar.FAILURE_NOTIFICATION_ID", FAILURE_NOTIFICATION_ID);
b.setContentIntent(PendingIntent.getActivity(this, 0, i, PendingIntent.FLAG_UPDATE_CURRENT));
Object o = getSystemService(NOTIFICATION_SERVICE);
NotificationManager nm = (NotificationManager) o;
nm.notify(FAILURE_NOTIFICATION_ID, b.build());
// Bring the dashboard to the front to clear all other activities
i = new Intent(this, DashboardActivity.class);
i.setFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TOP);
i.putExtra("briar.STARTUP_FAILED", true);
startActivity(i);
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
return START_NOT_STICKY; // Don't restart automatically if killed
@Override
@Override
public void onDestroy() {
super.onDestroy();
LOG.info("Destroyed");
stopForeground(true);
notificationManager.clearNotifications();
// Stop the services in a background thread
@Override
public void run() {
eventBus.removeListener(BriarService.this);
lifecycleManager.stopServices();
}
androidExecutor.shutdown();
@Override
public void onLowMemory() {
super.onLowMemory();
LOG.warning("Memory is low");
// FIXME: Work out what to do about it
}
public void eventOccurred(Event e) {
if (e instanceof MessageAddedEvent) {
MessageAddedEvent m = (MessageAddedEvent) e;
GroupId g = m.getGroup().getId();
ContactId c = m.getContactId();
if (c != null) showMessageNotification(g, c);
}
}
private void showMessageNotification(final GroupId g, final ContactId c) {
dbExecutor.execute(new Runnable() {
public void run() {
try {
lifecycleManager.waitForDatabase();
if (g.equals(db.getInboxGroupId(c)))
notificationManager.showPrivateMessageNotification(c);
else notificationManager.showGroupPostNotification(g);
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
LOG.info("Interruped while waiting for database");
Thread.currentThread().interrupt();
}
}
});
}
/** Waits for the database to be opened before returning. */
public void waitForDatabase() throws InterruptedException {
lifecycleManager.waitForDatabase();
/** Waits for all services to start before returning. */
public void waitForStartup() throws InterruptedException {
lifecycleManager.waitForStartup();
/** Waits for all services to stop before returning. */
public void waitForShutdown() throws InterruptedException {
lifecycleManager.waitForShutdown();
/** Starts the shutdown process. */
public void shutdown() {
stopSelf(); // This will call onDestroy()
public class BriarBinder extends Binder {
/** Returns the bound service. */
public BriarService getService() {
return BriarService.this;
}
}
public static class BriarServiceConnection implements ServiceConnection {
private final CountDownLatch binderLatch = new CountDownLatch(1);
private volatile IBinder binder = null;
public void onServiceConnected(ComponentName name, IBinder binder) {
this.binder = binder;
binderLatch.countDown();
}
public void onServiceDisconnected(ComponentName name) {}
/** Waits for the service to connect and returns its binder. */
public IBinder waitForBinder() throws InterruptedException {
binderLatch.await();
return binder;
}
}