diff --git a/CMakeLists.txt b/CMakeLists.txt
index 35d3e830f..c5bcaea3b 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -174,9 +174,11 @@ cmake_dependent_option(XRT_BUILD_DRIVER_NS "Enable North Star driver" ON "XRT_HA
 # This one defaults to off, even if we find the deps.
 cmake_dependent_option(XRT_BUILD_DRIVER_SURVIVE "Enable libsurvive driver" OFF "SURVIVE_FOUND" OFF)
 
+cmake_dependent_option(XRT_BUILD_DRIVER_ANDROID "Enable Android sensors driver" ON "ANDROID" OFF)
+
 # You can set this from a superproject to add a driver
 # All drivers must be listed in here to be included in the generated header!
-list(APPEND AVAILABLE_DRIVERS ARDUINO DUMMY HDK HYDRA NS OHMD PSMV PSVR RS V4L2 VIVE DAYDREAM REMOTE SURVIVE)
+list(APPEND AVAILABLE_DRIVERS "ANDROID" ARDUINO DUMMY HDK HYDRA NS OHMD PSMV PSVR RS V4L2 VIVE DAYDREAM REMOTE SURVIVE)
 
 
 # Package name needs to be known by the native code itself.
@@ -280,6 +282,7 @@ message(STATUS "#    FEATURE_OPENXR_LAYER_CYLINDER:        ${XRT_FEATURE_OPENXR_
 message(STATUS "#    FEATURE_OPENXR_LAYER_EQUIRECT:        ${XRT_FEATURE_OPENXR_LAYER_EQUIRECT}")
 message(STATUS "#    FEATURE_OPENXR_LAYER_EQUIRECT_LEGACY: ${XRT_FEATURE_OPENXR_LAYER_EQUIRECT_LEGACY}")
 message(STATUS "#")
+message(STATUS "#    DRIVER_ANDROID:  ${XRT_BUILD_DRIVER_ANDROID}")
 message(STATUS "#    DRIVER_ARDUINO:  ${XRT_BUILD_DRIVER_ARDUINO}")
 message(STATUS "#    DRIVER_DAYDREAM: ${XRT_BUILD_DRIVER_DAYDREAM}")
 message(STATUS "#    DRIVER_DUMMY:    ${XRT_BUILD_DRIVER_DUMMY}")
diff --git a/doc/changes/drivers/mr.581.md b/doc/changes/drivers/mr.581.md
new file mode 100644
index 000000000..f3bca74de
--- /dev/null
+++ b/doc/changes/drivers/mr.581.md
@@ -0,0 +1 @@
+Add initial "Cardboard" phone-holder driver for Android.
diff --git a/src/xrt/drivers/CMakeLists.txt b/src/xrt/drivers/CMakeLists.txt
index f7520c8b0..5df409509 100644
--- a/src/xrt/drivers/CMakeLists.txt
+++ b/src/xrt/drivers/CMakeLists.txt
@@ -194,6 +194,19 @@ if (XRT_BUILD_DRIVER_SURVIVE)
 	list(APPEND ENABLED_HEADSET_DRIVERS survive)
 endif()
 
+if(XRT_BUILD_DRIVER_ANDROID)
+	set(ANDROID_SOURCE_FILES
+		android/android_prober.c
+		android/android_prober.h
+		android/android_sensors.c
+		android/android_sensors.h
+		)
+
+	add_library(drv_android STATIC ${ANDROID_SOURCE_FILES})
+	target_link_libraries(drv_android PRIVATE xrt-interfaces aux_util aux_os ${ANDROID_LIBRARY})
+	list(APPEND ENABLED_DRIVERS android)
+endif()
+
 if(ENABLED_HEADSET_DRIVERS)
 	set(ENABLED_DRIVERS ${ENABLED_HEADSET_DRIVERS} ${ENABLED_DRIVERS})
 	list(SORT ENABLED_DRIVERS)
diff --git a/src/xrt/drivers/android/android_prober.c b/src/xrt/drivers/android/android_prober.c
new file mode 100644
index 000000000..9bd628c94
--- /dev/null
+++ b/src/xrt/drivers/android/android_prober.c
@@ -0,0 +1,91 @@
+// Copyright 2020, Collabora, Ltd.
+// SPDX-License-Identifier: BSL-1.0
+/*!
+ * @file
+ * @brief  Interface to Android sensors prober code.
+ * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
+ * @ingroup drv_android
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <wchar.h>
+
+#include "xrt/xrt_prober.h"
+
+#include "util/u_misc.h"
+#include "util/u_debug.h"
+#include "util/u_logging.h"
+
+#include "android_prober.h"
+#include "android_sensors.h"
+
+
+
+/*
+ *
+ * Defines & structs.
+ *
+ */
+
+/*!
+ * Android prober struct.
+ *
+ * @ingroup drv_android
+ * @implements xrt_auto_prober
+ */
+struct android_prober
+{
+	struct xrt_auto_prober base;
+};
+
+
+/*
+ *
+ * Static functions.
+ *
+ */
+
+//! @private @memberof android_prober
+static inline struct android_prober *
+android_prober(struct xrt_auto_prober *p)
+{
+	return (struct android_prober *)p;
+}
+
+//! @public @memberof android_prober
+static void
+android_prober_destroy(struct xrt_auto_prober *p)
+{
+	struct android_prober *pandroid = android_prober(p);
+	free(pandroid);
+}
+
+//! @public @memberof android_prober
+static struct xrt_device *
+android_prober_autoprobe(struct xrt_auto_prober *xap,
+                         cJSON *attached_data,
+                         bool no_hmds,
+                         struct xrt_prober *xp)
+{
+	struct android_device *dd = android_device_create();
+	return &dd->base;
+}
+
+
+/*
+ *
+ * Exported functions.
+ *
+ */
+
+struct xrt_auto_prober *
+android_create_auto_prober()
+{
+	struct android_prober *p = U_TYPED_CALLOC(struct android_prober);
+	p->base.name = "Android";
+	p->base.destroy = android_prober_destroy;
+	p->base.lelo_dallas_autoprobe = android_prober_autoprobe;
+
+	return &p->base;
+}
diff --git a/src/xrt/drivers/android/android_prober.h b/src/xrt/drivers/android/android_prober.h
new file mode 100644
index 000000000..5b8e60429
--- /dev/null
+++ b/src/xrt/drivers/android/android_prober.h
@@ -0,0 +1,41 @@
+// Copyright 2020, Collabora, Ltd.
+// SPDX-License-Identifier: BSL-1.0
+/*!
+ * @file
+ * @brief  Interface to Android sensors driver.
+ * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
+ * @ingroup drv_android
+ */
+
+#pragma once
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * @defgroup drv_android Android sensors driver
+ * @ingroup drv
+ *
+ * @brief Generic driver for phone sensors.
+ */
+
+/*!
+ * Probing function for Android sensors.
+ *
+ * @ingroup drv_android
+ */
+struct xrt_auto_prober *
+android_create_auto_prober();
+
+
+/*!
+ * @dir drivers/android
+ *
+ * @brief @ref drv_android files.
+ */
+
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/xrt/drivers/android/android_sensors.c b/src/xrt/drivers/android/android_sensors.c
new file mode 100644
index 000000000..20be3ca74
--- /dev/null
+++ b/src/xrt/drivers/android/android_sensors.c
@@ -0,0 +1,260 @@
+// Copyright 2013, Fredrik Hultin.
+// Copyright 2013, Jakob Bornecrantz.
+// Copyright 2015, Joey Ferwerda.
+// Copyright 2020, Collabora, Ltd.
+// SPDX-License-Identifier: BSL-1.0
+/*!
+ * @file
+ * @brief  Android sensors driver code.
+ * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
+ * @ingroup drv_android
+ */
+
+#include "android_sensors.h"
+
+#include "util/u_debug.h"
+#include "util/u_device.h"
+#include "util/u_var.h"
+
+// 60 events per second (in us).
+#define POLL_RATE_USEC (1000L / 60) * 1000
+
+
+DEBUG_GET_ONCE_LOG_OPTION(android_log, "ANDROID_SENSORS_LOG", U_LOGGING_WARN)
+
+static inline struct android_device *
+android_device(struct xrt_device *xdev)
+{
+	return (struct android_device *)xdev;
+}
+
+// Callback for the Android sensor event queue
+static int
+android_sensor_callback(int fd, int events, void *data)
+{
+	struct android_device *d = (struct android_device *)data;
+
+	if (d->accelerometer == NULL || d->gyroscope == NULL)
+		return 1;
+
+	ASensorEvent event;
+	struct xrt_vec3 gyro;
+	struct xrt_vec3 accel;
+	while (ASensorEventQueue_getEvents(d->event_queue, &event, 1) > 0) {
+
+		switch (event.type) {
+		case ASENSOR_TYPE_ACCELEROMETER: {
+			accel.x = event.acceleration.y;
+			accel.y = -event.acceleration.x;
+			accel.z = event.acceleration.z;
+
+			ANDROID_TRACE(d, "accel %ld %.2f %.2f %.2f",
+			              event.timestamp, accel.x, accel.y,
+			              accel.z);
+			break;
+		}
+		case ASENSOR_TYPE_GYROSCOPE: {
+			gyro.x = -event.data[1];
+			gyro.y = event.data[0];
+			gyro.z = event.data[2];
+
+			ANDROID_TRACE(d, "gyro %ld %.2f %.2f %.2f",
+			              event.timestamp, gyro.x, gyro.y, gyro.z);
+
+			// TODO: Make filter handle accelerometer
+			struct xrt_vec3 null_accel;
+
+			// Lock last and the fusion.
+			os_mutex_lock(&d->lock);
+
+			m_imu_3dof_update(&d->fusion, event.timestamp,
+			                  &null_accel, &gyro);
+
+			// Now done.
+			os_mutex_unlock(&d->lock);
+		}
+		default:
+			ANDROID_TRACE(d, "Unhandled event type %d", event.type);
+		}
+	}
+
+	return 1;
+}
+
+static void *
+android_run_thread(void *ptr)
+{
+	struct android_device *d = (struct android_device *)ptr;
+
+	d->sensor_manager = ASensorManager_getInstance();
+	d->accelerometer = ASensorManager_getDefaultSensor(
+	    d->sensor_manager, ASENSOR_TYPE_ACCELEROMETER);
+	d->gyroscope = ASensorManager_getDefaultSensor(d->sensor_manager,
+	                                               ASENSOR_TYPE_GYROSCOPE);
+
+	ALooper *looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
+
+	d->event_queue = ASensorManager_createEventQueue(
+	    d->sensor_manager, looper, ALOOPER_POLL_CALLBACK,
+	    android_sensor_callback, (void *)d);
+
+	// Start sensors in case this was not done already.
+	if (d->accelerometer != NULL) {
+		ASensorEventQueue_enableSensor(d->event_queue,
+		                               d->accelerometer);
+		ASensorEventQueue_setEventRate(d->event_queue, d->accelerometer,
+		                               POLL_RATE_USEC);
+	}
+	if (d->gyroscope != NULL) {
+		ASensorEventQueue_enableSensor(d->event_queue, d->gyroscope);
+		ASensorEventQueue_setEventRate(d->event_queue, d->gyroscope,
+		                               POLL_RATE_USEC);
+	}
+
+	int ret = 0;
+	while (ret != ALOOPER_POLL_ERROR) {
+		ret = ALooper_pollAll(0, NULL, NULL, NULL);
+	}
+
+	return NULL;
+}
+
+
+/*
+ *
+ * Device functions.
+ *
+ */
+
+static void
+android_device_destroy(struct xrt_device *xdev)
+{
+	struct android_device *android = android_device(xdev);
+
+	// Destroy the thread object.
+	os_thread_helper_destroy(&android->oth);
+
+	// Now that the thread is not running we can destroy the lock.
+	os_mutex_destroy(&android->lock);
+
+	// Destroy the fusion.
+	m_imu_3dof_close(&android->fusion);
+
+	// Remove the variable tracking.
+	u_var_remove_root(android);
+
+	free(android);
+}
+
+static void
+android_device_update_inputs(struct xrt_device *xdev)
+{
+	// Empty
+}
+
+static void
+android_device_get_tracked_pose(struct xrt_device *xdev,
+                                enum xrt_input_name name,
+                                uint64_t at_timestamp_ns,
+                                struct xrt_space_relation *out_relation)
+{
+	(void)at_timestamp_ns;
+
+	struct android_device *d = android_device(xdev);
+	out_relation->pose.orientation = d->fusion.rot;
+
+	//! @todo assuming that orientation is actually currently tracked.
+	out_relation->relation_flags = (enum xrt_space_relation_flags)(
+	    XRT_SPACE_RELATION_ORIENTATION_VALID_BIT |
+	    XRT_SPACE_RELATION_ORIENTATION_TRACKED_BIT);
+}
+
+static void
+android_device_get_view_pose(struct xrt_device *xdev,
+                             struct xrt_vec3 *eye_relation,
+                             uint32_t view_index,
+                             struct xrt_pose *out_pose)
+{
+	struct xrt_pose pose = {{0.0f, 0.0f, 0.0f, 1.0f}, {0.0f, 0.0f, 0.0f}};
+	bool adjust = view_index == 0;
+
+	pose.position.x = eye_relation->x / 2.0f;
+	pose.position.y = eye_relation->y / 2.0f;
+	pose.position.z = eye_relation->z / 2.0f;
+
+	// Adjust for left/right while also making sure there aren't any -0.f.
+	if (pose.position.x > 0.0f && adjust) {
+		pose.position.x = -pose.position.x;
+	}
+	if (pose.position.y > 0.0f && adjust) {
+		pose.position.y = -pose.position.y;
+	}
+	if (pose.position.z > 0.0f && adjust) {
+		pose.position.z = -pose.position.z;
+	}
+
+	*out_pose = pose;
+}
+
+/*
+ *
+ * Prober functions.
+ *
+ */
+
+struct android_device *
+android_device_create()
+{
+	enum u_device_alloc_flags flags = (enum u_device_alloc_flags)(
+	    U_DEVICE_ALLOC_HMD | U_DEVICE_ALLOC_TRACKING_NONE);
+	struct android_device *d =
+	    U_DEVICE_ALLOCATE(struct android_device, flags, 1, 0);
+
+	d->base.name = XRT_DEVICE_ANDROID;
+	d->base.destroy = android_device_destroy;
+	d->base.update_inputs = android_device_update_inputs;
+	d->base.get_tracked_pose = android_device_get_tracked_pose;
+	d->base.get_view_pose = android_device_get_view_pose;
+	d->base.inputs[0].name = XRT_INPUT_GENERIC_HEAD_POSE;
+	d->base.device_type = XRT_DEVICE_TYPE_HMD;
+
+	d->ll = debug_get_log_option_android_log();
+
+	m_imu_3dof_init(&d->fusion, M_IMU_3DOF_USE_GRAVITY_DUR_20MS);
+
+	// Everything done, finally start the thread.
+	int ret = os_thread_helper_start(&d->oth, android_run_thread, d);
+	if (ret != 0) {
+		ANDROID_ERROR(d, "Failed to start thread!");
+		android_device_destroy(&d->base);
+		return NULL;
+	}
+
+	// Setup info.
+	struct u_device_simple_info info;
+	info.display.w_pixels = 1280;
+	info.display.h_pixels = 720;
+	info.display.w_meters = 0.13f;
+	info.display.h_meters = 0.07f;
+	info.lens_horizontal_separation_meters = 0.13f / 2.0f;
+	info.lens_vertical_position_meters = 0.07f / 2.0f;
+	info.views[0].fov = 85.0f * (M_PI / 180.0f);
+	info.views[1].fov = 85.0f * (M_PI / 180.0f);
+
+	if (!u_device_setup_split_side_by_side(&d->base, &info)) {
+		ANDROID_ERROR(d, "Failed to setup basic device info");
+		android_device_destroy(&d->base);
+		return NULL;
+	}
+
+	u_var_add_root(d, "Android phone", true);
+	u_var_add_ro_vec3_f32(d, &d->fusion.last.accel, "last.accel");
+	u_var_add_ro_vec3_f32(d, &d->fusion.last.gyro, "last.gyro");
+
+	d->base.orientation_tracking_supported = true;
+	d->base.position_tracking_supported = false;
+
+	ANDROID_DEBUG(d, "Created device!");
+
+	return d;
+}
diff --git a/src/xrt/drivers/android/android_sensors.h b/src/xrt/drivers/android/android_sensors.h
new file mode 100644
index 000000000..cd46ff487
--- /dev/null
+++ b/src/xrt/drivers/android/android_sensors.h
@@ -0,0 +1,70 @@
+// Copyright 2020, Collabora, Ltd.
+// SPDX-License-Identifier: BSL-1.0
+/*!
+ * @file
+ * @brief  Android sensors driver header.
+ * @author Lubosz Sarnecki <lubosz.sarnecki@collabora.com>
+ * @ingroup drv_android
+ */
+
+#pragma once
+
+#include <android/sensor.h>
+
+#include "math/m_api.h"
+#include "math/m_imu_pre.h"
+#include "math/m_imu_3dof.h"
+
+#include "xrt/xrt_device.h"
+
+#include "os/os_threading.h"
+
+#include "util/u_logging.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/*!
+ * @implements xrt_device
+ */
+struct android_device
+{
+	struct xrt_device base;
+	struct os_thread_helper oth;
+
+	ASensorManager *sensor_manager;
+	const ASensor *accelerometer;
+	const ASensor *gyroscope;
+	ASensorEventQueue *event_queue;
+
+	struct
+	{
+		//! Lock for last and fusion.
+		struct os_mutex lock;
+		struct m_imu_3dof fusion;
+	};
+
+	enum u_logging_level ll;
+};
+
+
+struct android_device *
+android_device_create();
+
+
+/*
+ *
+ * Printing functions.
+ *
+ */
+
+#define ANDROID_TRACE(d, ...) U_LOG_XDEV_IFL_T(&d->base, d->ll, __VA_ARGS__)
+#define ANDROID_DEBUG(d, ...) U_LOG_XDEV_IFL_D(&d->base, d->ll, __VA_ARGS__)
+#define ANDROID_INFO(d, ...) U_LOG_XDEV_IFL_I(&d->base, d->ll, __VA_ARGS__)
+#define ANDROID_WARN(d, ...) U_LOG_XDEV_IFL_W(&d->base, d->ll, __VA_ARGS__)
+#define ANDROID_ERROR(d, ...) U_LOG_XDEV_IFL_E(&d->base, d->ll, __VA_ARGS__)
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/xrt/include/xrt/xrt_defines.h b/src/xrt/include/xrt/xrt_defines.h
index 7c6653c1b..7b86f0f91 100644
--- a/src/xrt/include/xrt/xrt_defines.h
+++ b/src/xrt/include/xrt/xrt_defines.h
@@ -419,6 +419,7 @@ enum xrt_device_name
 	XRT_DEVICE_VIVE_WAND,
 	XRT_DEVICE_VIVE_TRACKER_GEN1,
 	XRT_DEVICE_VIVE_TRACKER_GEN2,
+	XRT_DEVICE_ANDROID
 };
 
 /*!
diff --git a/src/xrt/targets/common/CMakeLists.txt b/src/xrt/targets/common/CMakeLists.txt
index 662ab5738..db56c7905 100644
--- a/src/xrt/targets/common/CMakeLists.txt
+++ b/src/xrt/targets/common/CMakeLists.txt
@@ -76,6 +76,10 @@ if(XRT_BUILD_DRIVER_SURVIVE)
 	target_link_libraries(target_lists PRIVATE drv_survive)
 endif()
 
+if(XRT_BUILD_DRIVER_ANDROID)
+	target_link_libraries(target_lists PRIVATE drv_android)
+endif()
+
 ####
 # Instance
 #
diff --git a/src/xrt/targets/common/target_lists.c b/src/xrt/targets/common/target_lists.c
index 717f68ac3..56feda646 100644
--- a/src/xrt/targets/common/target_lists.c
+++ b/src/xrt/targets/common/target_lists.c
@@ -54,6 +54,10 @@
 #include "daydream/daydream_interface.h"
 #endif
 
+#ifdef XRT_BUILD_DRIVER_ANDROID
+#include "android/android_prober.h"
+#endif
+
 /*!
  * Each entry should be a vendor ID (VID), product ID (PID), a "found" function,
  * and a string literal name.
@@ -134,6 +138,10 @@ xrt_auto_prober_creator target_auto_list[] = {
     ns_create_auto_prober,
 #endif
 
+#ifdef XRT_BUILD_DRIVER_ANDROID
+    android_create_auto_prober,
+#endif
+
 #ifdef XRT_BUILD_DRIVER_DUMMY
     // Dummy headset driver last.
     dummy_create_auto_prober,