Skip to content
Snippets Groups Projects
BriarService.java 7.89 KiB
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;
akwizgran's avatar
akwizgran committed
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

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;
akwizgran's avatar
akwizgran committed
import org.briarproject.api.db.DatabaseComponent;
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.Intent;
import android.content.ServiceConnection;
import android.os.Binder;
import android.os.IBinder;
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;

	private static final Logger LOG =
			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;
akwizgran's avatar
akwizgran committed
	@Inject private volatile DatabaseComponent db;
	@Inject private volatile EventBus eventBus;
	private volatile boolean started = false;

	@Override
	public void onCreate() {
		if (created.getAndSet(true)) {
			LOG.info("Already created");
		if (databaseConfig.getEncryptionKey() == null) {
			LOG.info("No database key");
		// 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
		b.setOngoing(true);
		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();
				if (result == SUCCESS) {
					eventBus.addListener(BriarService.this);
					started = true;
				} else if (result == ALREADY_RUNNING) {
					LOG.info("Already running");
					stopSelf();
					if (LOG.isLoggable(WARNING))
						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
	public IBinder onBind(Intent intent) {
	@Override
	public void onDestroy() {
		super.onDestroy();
		LOG.info("Destroyed");
		stopForeground(true);
		notificationManager.clearNotifications();
		// Stop the services in a background thread
			@Override
			public void run() {
				if (started) {
					eventBus.removeListener(BriarService.this);
					lifecycleManager.stopServices();
				}
	@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);
				} catch (InterruptedException e) {
					LOG.info("Interruped while waiting for database");
	/** 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. */
		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;
		}
	}