diff --git a/rxjava-contrib/rxjava-android/build.gradle b/rxjava-contrib/rxjava-android/build.gradle
new file mode 100644
index 0000000000..96bbb27267
--- /dev/null
+++ b/rxjava-contrib/rxjava-android/build.gradle
@@ -0,0 +1,59 @@
+apply plugin: 'java'
+apply plugin: 'eclipse'
+apply plugin: 'idea'
+apply plugin: 'osgi'
+
+sourceCompatibility = JavaVersion.VERSION_1_6
+targetCompatibility = JavaVersion.VERSION_1_6
+
+dependencies {
+ compile project(':rxjava-core')
+ provided 'junit:junit-dep:4.10'
+ provided 'org.mockito:mockito-core:1.8.5'
+ provided 'org.robolectric:robolectric:2.1.1'
+ provided 'com.google.android:android:4.0.1.2'
+}
+
+eclipse {
+ classpath {
+ // include 'provided' dependencies on the classpath
+ plusConfigurations += configurations.provided
+
+ downloadSources = true
+ downloadJavadoc = true
+ }
+}
+
+idea {
+ module {
+ // include 'provided' dependencies on the classpath
+ scopes.PROVIDED.plus += configurations.provided
+ }
+}
+
+javadoc {
+ options {
+ doclet = "org.benjchristensen.doclet.DocletExclude"
+ docletpath = [rootProject.file('./gradle/doclet-exclude.jar')]
+ stylesheetFile = rootProject.file('./gradle/javadocStyleSheet.css')
+ windowTitle = "RxJava Android Javadoc ${project.version}"
+ }
+ options.addStringOption('top').value = '
RxJava Android
'
+}
+
+jar {
+ manifest {
+ name = 'rxjava-android'
+ instruction 'Bundle-Vendor', 'Netflix'
+ instruction 'Bundle-DocURL', 'https://github.com/Netflix/RxJava'
+ instruction 'Import-Package', '!org.junit,!junit.framework,!org.mockito.*,*'
+ }
+}
+
+test {
+ testLogging {
+ exceptionFormat "full"
+ events "started"
+ displayGranularity 2
+ }
+}
diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/AndroidSchedulers.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/AndroidSchedulers.java
new file mode 100644
index 0000000000..36a8154d16
--- /dev/null
+++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/AndroidSchedulers.java
@@ -0,0 +1,36 @@
+package rx.android.concurrency;
+
+import android.os.Handler;
+import android.os.Looper;
+import rx.Scheduler;
+
+/**
+ * Schedulers that have Android specific functionality
+ */
+public class AndroidSchedulers {
+
+ private static final Scheduler MAIN_THREAD_SCHEDULER =
+ new HandlerThreadScheduler(new Handler(Looper.getMainLooper()));
+
+ private AndroidSchedulers(){
+
+ }
+
+ /**
+ * {@link Scheduler} which uses the provided {@link Handler} to execute an action
+ * @param handler The handler that will be used when executing the action
+ * @return A handler based scheduler
+ */
+ public static Scheduler handlerThread(final Handler handler) {
+ return new HandlerThreadScheduler(handler);
+ }
+
+ /**
+ * {@link Scheduler} which will execute an action on the main Android UI thread.
+ *
+ * @return A Main {@link Looper} based scheduler
+ */
+ public static Scheduler mainThread() {
+ return MAIN_THREAD_SCHEDULER;
+ }
+}
diff --git a/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/HandlerThreadScheduler.java b/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/HandlerThreadScheduler.java
new file mode 100644
index 0000000000..2a2b165bab
--- /dev/null
+++ b/rxjava-contrib/rxjava-android/src/main/java/rx/android/concurrency/HandlerThreadScheduler.java
@@ -0,0 +1,113 @@
+package rx.android.concurrency;
+
+import android.os.Handler;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.Config;
+import rx.Scheduler;
+import rx.Subscription;
+import rx.operators.AtomicObservableSubscription;
+import rx.util.functions.Func2;
+
+import java.util.concurrent.TimeUnit;
+
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
+/**
+ * Schedules actions to run on an Android Handler thread.
+ */
+public class HandlerThreadScheduler extends Scheduler {
+
+ private final Handler handler;
+
+ /**
+ * Constructs a {@link HandlerThreadScheduler} using the given {@link Handler}
+ * @param handler {@link Handler} to use when scheduling actions
+ */
+ public HandlerThreadScheduler(Handler handler) {
+ this.handler = handler;
+ }
+
+ /**
+ * Calls {@link HandlerThreadScheduler#schedule(Object, rx.util.functions.Func2, long, java.util.concurrent.TimeUnit)}
+ * with a delay of zero milliseconds.
+ *
+ * See {@link #schedule(Object, rx.util.functions.Func2, long, java.util.concurrent.TimeUnit)}
+ */
+ @Override
+ public Subscription schedule(final T state, final Func2 action) {
+ return schedule(state, action, 0L, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Calls {@link Handler#postDelayed(Runnable, long)} with a runnable that executes the given action.
+ * @param state
+ * State to pass into the action.
+ * @param action
+ * Action to schedule.
+ * @param delayTime
+ * Time the action is to be delayed before executing.
+ * @param unit
+ * Time unit of the delay time.
+ * @return A Subscription from which one can unsubscribe from.
+ */
+ @Override
+ public Subscription schedule(final T state, final Func2 action, long delayTime, TimeUnit unit) {
+ final AtomicObservableSubscription subscription = new AtomicObservableSubscription();
+ final Scheduler _scheduler = this;
+ handler.postDelayed(new Runnable() {
+ @Override
+ public void run() {
+ subscription.wrap(action.call(_scheduler, state));
+ }
+ }, unit.toMillis(delayTime));
+ return subscription;
+ }
+
+ @RunWith(RobolectricTestRunner.class)
+ @Config(manifest=Config.NONE)
+ public static final class UnitTest {
+
+ @Test
+ public void shouldScheduleImmediateActionOnHandlerThread() {
+ final Handler handler = mock(Handler.class);
+ final Object state = new Object();
+ final Func2 action = mock(Func2.class);
+
+ Scheduler scheduler = new HandlerThreadScheduler(handler);
+ scheduler.schedule(state, action);
+
+ // verify that we post to the given Handler
+ ArgumentCaptor runnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(handler).postDelayed(runnable.capture(), eq(0L));
+
+ // verify that the given handler delegates to our action
+ runnable.getValue().run();
+ verify(action).call(scheduler, state);
+ }
+
+ @Test
+ public void shouldScheduleDelayedActionOnHandlerThread() {
+ final Handler handler = mock(Handler.class);
+ final Object state = new Object();
+ final Func2 action = mock(Func2.class);
+
+ Scheduler scheduler = new HandlerThreadScheduler(handler);
+ scheduler.schedule(state, action, 1L, TimeUnit.SECONDS);
+
+ // verify that we post to the given Handler
+ ArgumentCaptor runnable = ArgumentCaptor.forClass(Runnable.class);
+ verify(handler).postDelayed(runnable.capture(), eq(1000L));
+
+ // verify that the given handler delegates to our action
+ runnable.getValue().run();
+ verify(action).call(scheduler, state);
+ }
+ }
+}
+
+
diff --git a/settings.gradle b/settings.gradle
index f07f904404..5d33717f7a 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,4 +4,5 @@ include 'rxjava-core', \
'language-adaptors:rxjava-jruby', \
'language-adaptors:rxjava-clojure', \
'language-adaptors:rxjava-scala', \
-'rxjava-contrib:rxjava-swing'
+'rxjava-contrib:rxjava-swing', \
+'rxjava-contrib:rxjava-android'