Verbatim copy of gralloc, hwcomposer, framebuffer, vnc_server and wpa_supllicatn_lib

Taken from: persistent-https://googleplex-android.git.corp.google.com/device/google/gce
Commit: 7d529657f7aae5fae8ee1781f6157f7b17da7863

Test: None
Change-Id: Ie3502c92228ca6aff9fe53e7f1e9a9e4cd4a0e80
diff --git a/guest/frontend/vnc_server/Android.mk b/guest/frontend/vnc_server/Android.mk
new file mode 100644
index 0000000..587986a
--- /dev/null
+++ b/guest/frontend/vnc_server/Android.mk
@@ -0,0 +1,89 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+LOCAL_PATH:= $(call my-dir)
+
+include $(CLEAR_VARS)
+
+
+ifneq (,$(filter $(PLATFORM_SDK_VERSION),16 17 18 19 21 22 23)) # J K L M
+  # prior to (not including) nyc, libjpeg was used instead of libjpeg turbo
+  # the libjpeg turbo in external/ is a backport with the shared library name
+  # changed to libjpeg_turbo to avoid conflict with the system's libjpeg
+  LIBJPEG_TURBO_NAME := libjpeg_turbo
+else
+  # nyc and later use libjpeg turbo under its usual name
+  LIBJPEG_TURBO_NAME := libjpeg
+endif
+
+LOCAL_C_INCLUDES := \
+    device/google/gce/sensors \
+    device/google/gce/include \
+    external/libjpeg-turbo \
+    external/jsoncpp/include
+
+include device/google/gce/libs/base/libbase.mk
+LOCAL_C_INCLUDES += $(GCE_LIBBASE_INCLUDE_DIR)
+
+LOCAL_MODULE := vnc_server
+LOCAL_VENDOR_MODULE := true
+LOCAL_MODULE_TAGS := optional
+LOCAL_SRC_FILES := \
+	blackboard.cpp \
+	frame_buffer_watcher.cpp \
+	jpeg_compressor.cpp \
+	main.cpp \
+	simulated_hw_composer.cpp \
+	tcp_socket.cpp \
+	VirtualInputDevice.cpp \
+	virtual_inputs.cpp \
+	vnc_client_connection.cpp \
+	vnc_server.cpp \
+
+LOCAL_CFLAGS := \
+	$(GCE_VERSION_CFLAGS) \
+	-std=gnu++11 \
+	-Wall -Werror \
+	-Wno-error-unused -Wno-error=unused-parameter \
+	-Wno-attributes \
+	-DGCE_32_BIT_GRAPHICS
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 18; echo $$?))
+LOCAL_CFLAGS += -Wno-error=implicit-exception-spec-mismatch
+endif
+
+LOCAL_SHARED_LIBRARIES := $(LIBJPEG_TURBO_NAME)
+LOCAL_STATIC_LIBRARIES := libcutils liblog
+LOCAL_CLANG := true
+
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -le 22; echo $$?)) #lmp-mr1 and down
+  LOCAL_STATIC_LIBRARIES += \
+    libgceframebuffer_cxx \
+    libgcemetadata_cxx \
+    libjsoncpp_cxx \
+    libgcecutils_cxx \
+
+  include external/libcxx/libcxx.mk
+else
+  LOCAL_STATIC_LIBRARIES += \
+    libgcemetadata \
+    libjsoncpp \
+
+  LOCAL_SHARED_LIBRARIES += \
+    libgceframebuffer \
+    libgcecutils \
+
+endif
+
+include $(BUILD_EXECUTABLE)
diff --git a/guest/frontend/vnc_server/VirtualInputDevice.cpp b/guest/frontend/vnc_server/VirtualInputDevice.cpp
new file mode 100644
index 0000000..badeb07
--- /dev/null
+++ b/guest/frontend/vnc_server/VirtualInputDevice.cpp
@@ -0,0 +1,428 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <linux/input.h>
+#include <linux/uinput.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <vector>
+
+#include "keysyms.h"
+
+#define LOG_TAG "RemoterVirtualInput"
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include "VirtualInputDevice.h"
+
+#define ARRAY_SIZE(a)           \
+  ((sizeof(a) / sizeof(*(a))) / \
+   static_cast<size_t>(!(sizeof(a) % sizeof(*(a)))))
+
+//////////////////////////
+// VirtualButton Support
+//////////////////////////
+
+namespace avd {
+uint32_t VirtualButton::Senabled_events_[] = {EV_KEY};
+
+VirtualButton::VirtualButton(const char* name, uint32_t input_keycode)
+    : VirtualInputDevice(name, BUS_USB, 0x6006, 0x6007, 1),
+      input_keycode_(input_keycode) {
+  if (!VirtualInputDevice::Init(Senabled_events_, ARRAY_SIZE(Senabled_events_),
+                                &input_keycode_, 1, NULL, 0, NULL, 0)) {
+    LOG_FATAL("VirtualInputDevice Init() failed");
+  }
+}
+
+void VirtualButton::HandleButtonPressEvent(bool button_down) {
+  EmitEvent(EV_KEY, input_keycode_, button_down);
+  EmitEvent(EV_SYN, 0, 0);
+}
+
+//////////////////////////
+// VirtualKeyboard Support
+//////////////////////////
+
+uint32_t VirtualKeyboard::Senabled_events_[] = {EV_KEY};
+
+struct KeyEventToInput {
+  uint32_t xk;
+  uint32_t input_code;
+};
+
+static const KeyEventToInput key_table[] = {
+    {xk::AltLeft, KEY_LEFTALT},
+    {xk::ControlLeft, KEY_LEFTCTRL},
+    {xk::ShiftLeft, KEY_LEFTSHIFT},
+    {xk::AltRight, KEY_RIGHTALT},
+    {xk::ControlRight, KEY_RIGHTCTRL},
+    {xk::ShiftRight, KEY_RIGHTSHIFT},
+    {xk::MetaLeft, KEY_LEFTMETA},
+    {xk::MetaRight, KEY_RIGHTMETA},
+    {xk::MultiKey, KEY_COMPOSE},
+
+    {xk::CapsLock, KEY_CAPSLOCK},
+    {xk::NumLock, KEY_NUMLOCK},
+    {xk::ScrollLock, KEY_SCROLLLOCK},
+
+    {xk::BackSpace, KEY_BACKSPACE},
+    {xk::Tab, KEY_TAB},
+    {xk::Return, KEY_ENTER},
+    {xk::Escape, KEY_ESC},
+
+    {' ', KEY_SPACE},
+    {'!', KEY_1},
+    {'"', KEY_APOSTROPHE},
+    {'#', KEY_3},
+    {'$', KEY_4},
+    {'%', KEY_5},
+    {'^', KEY_6},
+    {'&', KEY_7},
+    {'\'', KEY_APOSTROPHE},
+    {'(', KEY_9},
+    {')', KEY_0},
+    {'*', KEY_8},
+    {'+', KEY_EQUAL},
+    {',', KEY_COMMA},
+    {'-', KEY_MINUS},
+    {'.', KEY_DOT},
+    {'/', KEY_SLASH},
+    {'0', KEY_0},
+    {'1', KEY_1},
+    {'2', KEY_2},
+    {'3', KEY_3},
+    {'4', KEY_4},
+    {'5', KEY_5},
+    {'6', KEY_6},
+    {'7', KEY_7},
+    {'8', KEY_8},
+    {'9', KEY_9},
+    {':', KEY_SEMICOLON},
+    {';', KEY_SEMICOLON},
+    {'<', KEY_COMMA},
+    {'=', KEY_EQUAL},
+    {'>', KEY_DOT},
+    {'?', KEY_SLASH},
+    {'@', KEY_2},
+    {'A', KEY_A},
+    {'B', KEY_B},
+    {'C', KEY_C},
+    {'D', KEY_D},
+    {'E', KEY_E},
+    {'F', KEY_F},
+    {'G', KEY_G},
+    {'H', KEY_H},
+    {'I', KEY_I},
+    {'J', KEY_J},
+    {'K', KEY_K},
+    {'L', KEY_L},
+    {'M', KEY_M},
+    {'N', KEY_N},
+    {'O', KEY_O},
+    {'P', KEY_P},
+    {'Q', KEY_Q},
+    {'R', KEY_R},
+    {'S', KEY_S},
+    {'T', KEY_T},
+    {'U', KEY_U},
+    {'V', KEY_V},
+    {'W', KEY_W},
+    {'X', KEY_X},
+    {'Y', KEY_Y},
+    {'Z', KEY_Z},
+    {'[', KEY_LEFTBRACE},
+    {'\\', KEY_BACKSLASH},
+    {']', KEY_RIGHTBRACE},
+    {'-', KEY_MINUS},
+    {'_', KEY_MINUS},
+    {'`', KEY_GRAVE},
+    {'a', KEY_A},
+    {'b', KEY_B},
+    {'c', KEY_C},
+    {'d', KEY_D},
+    {'e', KEY_E},
+    {'f', KEY_F},
+    {'g', KEY_G},
+    {'h', KEY_H},
+    {'i', KEY_I},
+    {'j', KEY_J},
+    {'k', KEY_K},
+    {'l', KEY_L},
+    {'m', KEY_M},
+    {'n', KEY_N},
+    {'o', KEY_O},
+    {'p', KEY_P},
+    {'q', KEY_Q},
+    {'r', KEY_R},
+    {'s', KEY_S},
+    {'t', KEY_T},
+    {'u', KEY_U},
+    {'v', KEY_V},
+    {'w', KEY_W},
+    {'x', KEY_X},
+    {'y', KEY_Y},
+    {'z', KEY_Z},
+    {'{', KEY_LEFTBRACE},
+    {'\\', KEY_BACKSLASH},
+    {'|', KEY_BACKSLASH},
+    {'}', KEY_RIGHTBRACE},
+    {'~', KEY_GRAVE},
+
+    {xk::F1, KEY_F1},
+    {xk::F2, KEY_F2},
+    {xk::F3, KEY_F3},
+    {xk::F4, KEY_F4},
+    {xk::F5, KEY_F5},
+    {xk::F6, KEY_F6},
+    {xk::F7, KEY_F7},
+    {xk::F8, KEY_F8},
+    {xk::F9, KEY_F9},
+    {xk::F10, KEY_F10},
+    {xk::F11, KEY_F11},
+    {xk::F12, KEY_F12},
+    {xk::F13, KEY_F13},
+    {xk::F14, KEY_F14},
+    {xk::F15, KEY_F15},
+    {xk::F16, KEY_F16},
+    {xk::F17, KEY_F17},
+    {xk::F18, KEY_F18},
+    {xk::F19, KEY_F19},
+    {xk::F20, KEY_F20},
+    {xk::F21, KEY_F21},
+    {xk::F22, KEY_F22},
+    {xk::F23, KEY_F23},
+    {xk::F24, KEY_F24},
+
+    {xk::Keypad0, KEY_KP0},
+    {xk::Keypad1, KEY_KP1},
+    {xk::Keypad2, KEY_KP2},
+    {xk::Keypad3, KEY_KP3},
+    {xk::Keypad4, KEY_KP4},
+    {xk::Keypad5, KEY_KP5},
+    {xk::Keypad6, KEY_KP6},
+    {xk::Keypad7, KEY_KP7},
+    {xk::Keypad8, KEY_KP8},
+    {xk::Keypad9, KEY_KP9},
+    {xk::KeypadMultiply, KEY_KPASTERISK},
+    {xk::KeypadSubtract, KEY_KPMINUS},
+    {xk::KeypadAdd, KEY_KPPLUS},
+    {xk::KeypadDecimal, KEY_KPDOT},
+    {xk::KeypadEnter, KEY_KPENTER},
+    {xk::KeypadDivide, KEY_KPSLASH},
+    {xk::KeypadEqual, KEY_KPEQUAL},
+    {xk::PlusMinus, KEY_KPPLUSMINUS},
+
+    {xk::SysReq, KEY_SYSRQ},
+    {xk::LineFeed, KEY_LINEFEED},
+    {xk::Home, KEY_HOME},
+    {xk::Up, KEY_UP},
+    {xk::PageUp, KEY_PAGEUP},
+    {xk::Left, KEY_LEFT},
+    {xk::Right, KEY_RIGHT},
+    {xk::End, KEY_END},
+    {xk::Down, KEY_DOWN},
+    {xk::PageDown, KEY_PAGEDOWN},
+    {xk::Insert, KEY_INSERT},
+    {xk::Delete, KEY_DELETE},
+    {xk::Pause, KEY_PAUSE},
+    {xk::KeypadSeparator, KEY_KPCOMMA},
+    {xk::Yen, KEY_YEN},
+    {xk::Cancel, KEY_STOP},
+    {xk::Redo, KEY_AGAIN},
+    {xk::Undo, KEY_UNDO},
+    {xk::Find, KEY_FIND},
+    {xk::Print, KEY_PRINT},
+    {xk::VolumeDown, KEY_VOLUMEDOWN},
+    {xk::Mute, KEY_MUTE},
+    {xk::VolumeUp, KEY_VOLUMEUP},
+    {xk::Menu, KEY_MENU},
+    {xk::VNCMenu, KEY_MENU},
+};
+
+VirtualKeyboard::VirtualKeyboard(const char* name)
+    : VirtualInputDevice(name, BUS_USB, 0x6006, 0x6008, 1) {
+  std::vector<uint32_t> keycodes(ARRAY_SIZE(key_table));
+  for (size_t i = 0; i < keycodes.size(); ++i) {
+    keymapping_[key_table[i].xk] = key_table[i].input_code;
+    keycodes[i] = key_table[i].input_code;
+  }
+
+  if (!VirtualInputDevice::Init(Senabled_events_, ARRAY_SIZE(Senabled_events_),
+                                &keycodes[0], keycodes.size(), NULL, 0, NULL,
+                                0)) {
+    LOG_FATAL("VirtualInputDevice Init() failed");
+  }
+}
+
+void VirtualKeyboard::GenerateKeyPressEvent(int keycode, bool button_down) {
+  if (keymapping_.count(keycode)) {
+    EmitEvent(EV_KEY, keymapping_[keycode], button_down);
+    EmitEvent(EV_SYN, 0, 0);
+  }
+  ALOGI("Unknown keycode %d", keycode);
+}
+
+//////////////////////////
+// VirtualTouchPad Support
+//////////////////////////
+
+uint32_t VirtualTouchPad::Senabled_events_[] = {EV_ABS, EV_KEY, EV_SYN};
+uint32_t VirtualTouchPad::Senabled_keys_[] = {BTN_TOUCH};
+uint32_t VirtualTouchPad::Senabled_abs_[] = {ABS_X, ABS_Y};
+uint32_t VirtualTouchPad::Senabled_props_[] = {INPUT_PROP_DIRECT};
+
+VirtualTouchPad::VirtualTouchPad(const char* name, int x_res, int y_res)
+    : VirtualInputDevice(name, BUS_USB, 0x6006, 0x6006, 1),
+      x_res_(x_res),
+      y_res_(y_res) {
+  // Customization of uinput_user_dev() must happen before calling our base
+  // Init().
+  uinput_user_dev()->absmin[ABS_X] = 0;
+  uinput_user_dev()->absmax[ABS_X] = x_res_;
+  uinput_user_dev()->absmin[ABS_Y] = 0;
+  uinput_user_dev()->absmax[ABS_Y] = y_res_;
+
+  if (!VirtualInputDevice::Init(Senabled_events_, ARRAY_SIZE(Senabled_events_),
+                                Senabled_keys_, ARRAY_SIZE(Senabled_keys_),
+                                Senabled_abs_, ARRAY_SIZE(Senabled_abs_),
+                                Senabled_props_, ARRAY_SIZE(Senabled_props_))) {
+    LOG_FATAL("VirtualInputDevice Init() failed");
+  }
+}
+
+void VirtualTouchPad::HandlePointerEvent(bool touch_down, int x, int y) {
+  EmitEvent(EV_ABS, ABS_X, x);
+  EmitEvent(EV_ABS, ABS_Y, y);
+  EmitEvent(EV_KEY, BTN_TOUCH, touch_down);
+  EmitEvent(EV_SYN, 0, 0);
+}
+
+//////////////////////////////////
+// Base VirtualInputDevice Support
+//////////////////////////////////
+VirtualInputDevice::VirtualInputDevice(const char* name, uint16_t bus_type,
+                                       uint16_t vendor, uint16_t product,
+                                       uint16_t version)
+    : fd_(-1) {
+  memset(&uinput_user_dev_, 0, sizeof(uinput_user_dev_));
+  strncpy(uinput_user_dev_.name, name, sizeof(uinput_user_dev_.name));
+  uinput_user_dev_.id.bustype = bus_type;
+  uinput_user_dev_.id.vendor = vendor;
+  uinput_user_dev_.id.product = product;
+  uinput_user_dev_.id.version = version;
+}
+
+VirtualInputDevice::~VirtualInputDevice() {
+  if (fd_ != -1) {
+    close(fd_);
+    fd_ = -1;
+  }
+}
+
+bool VirtualInputDevice::Init(uint32_t* events, int num_events, uint32_t* keys,
+                              int num_keys, uint32_t* abs, int num_abs,
+                              uint32_t* props, int num_props) {
+  if ((fd_ = open("/dev/uinput", O_WRONLY | O_NONBLOCK)) < 0) {
+    SLOGE("Failed to open /dev/uinput (%s)", strerror(errno));
+    return false;
+  }
+  if (events && !EnableEventBits(events, num_events)) {
+    SLOGE("Failed to set event bits (%s)", strerror(errno));
+    return false;
+  }
+  if (keys && !EnableKeyBits(keys, num_keys)) {
+    SLOGE("Failed to set key bits (%s)", strerror(errno));
+    return false;
+  }
+  if (abs && !EnableAbsBits(abs, num_abs)) {
+    SLOGE("Failed to set abs bits (%s)", strerror(errno));
+    return false;
+  }
+  if (props && !EnablePropBits(props, num_props)) {
+    SLOGE("Failed to set prop bits (%s)", strerror(errno));
+    return false;
+  }
+  if (!FinalizeDeviceCreation()) {
+    SLOGE("Failed to finalize device creation (%s)", strerror(errno));
+    return false;
+  }
+  return true;
+}
+
+bool VirtualInputDevice::EmitEvent(uint16_t type, uint16_t code,
+                                   uint32_t value) {
+  struct input_event ev;
+  ev.type = type;
+  ev.code = code;
+  ev.value = value;
+  if (write(fd_, &ev, sizeof(ev)) < 0) {
+    SLOGE("write() failed (%s)", strerror(errno));
+    return false;
+  }
+  return true;
+}
+
+bool VirtualInputDevice::DoIoctls(int request, uint32_t* list,
+                                  int num_elements) {
+  for (int i = 0; i < num_elements; i++) {
+    int rc = ioctl(fd_, request, *list++);
+    if (rc < 0) {
+      SLOGE("ioctl failed (%s)", strerror(errno));
+      return false;
+    }
+  }
+  return true;
+}
+
+bool VirtualInputDevice::EnableEventBits(uint32_t* events, int num_elements) {
+  return DoIoctls(UI_SET_EVBIT, events, num_elements);
+}
+
+bool VirtualInputDevice::EnableKeyBits(uint32_t* keys, int num_elements) {
+  return DoIoctls(UI_SET_KEYBIT, keys, num_elements);
+}
+
+bool VirtualInputDevice::EnableAbsBits(uint32_t* abs, int num_elements) {
+  return DoIoctls(UI_SET_ABSBIT, abs, num_elements);
+}
+
+bool VirtualInputDevice::EnablePropBits(uint32_t* props, int num_elements) {
+// JB and ICE do not have the latest uinput headers.
+#ifndef UI_SET_PROPBIT
+#define UI_SET_PROPBIT _IOW(UINPUT_IOCTL_BASE, 110, int)
+#endif  // #ifndef UI_SET_PROPBIT
+  return DoIoctls(UI_SET_PROPBIT, props, num_elements);
+}
+
+bool VirtualInputDevice::FinalizeDeviceCreation() {
+  if (write(fd_, &uinput_user_dev_, sizeof(uinput_user_dev_)) < 0) {
+    SLOGE("Unable to set input device info (%s)", strerror(errno));
+    return false;
+  }
+  if (ioctl(fd_, UI_DEV_CREATE) < 0) {
+    SLOGE("Unable to create input device (%s)", strerror(errno));
+    return false;
+  }
+  return true;
+}
+
+}  // namespace avd
diff --git a/guest/frontend/vnc_server/VirtualInputDevice.h b/guest/frontend/vnc_server/VirtualInputDevice.h
new file mode 100644
index 0000000..02605a2
--- /dev/null
+++ b/guest/frontend/vnc_server/VirtualInputDevice.h
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef VIRTUAL_INPUT_DEVICE_H_
+#define VIRTUAL_INPUT_DEVICE_H_
+
+#include <linux/uinput.h>
+#include <map>
+
+namespace avd {
+// Base virtual input device class which contains a bunch of boiler-plate code.
+class VirtualInputDevice {
+public:
+  VirtualInputDevice(const char* name, uint16_t bus_type, uint16_t vendor,
+                     uint16_t product, uint16_t version);
+  virtual ~VirtualInputDevice();
+
+protected:
+  bool Init(uint32_t* events, int num_events, uint32_t* keys, int num_keys,
+            uint32_t* abs, int num_abs, uint32_t* props, int num_props);
+
+  bool EmitEvent(uint16_t type, uint16_t code, uint32_t value);
+
+  struct uinput_user_dev* uinput_user_dev() { return &uinput_user_dev_; }
+
+private:
+  bool EnableEventBits(uint32_t* events, int num_elements);
+  bool EnableKeyBits(uint32_t* keys, int num_elements);
+  bool EnableAbsBits(uint32_t* abs, int num_elements);
+  bool EnablePropBits(uint32_t* props, int num_elements);
+  bool DoIoctls(int request, uint32_t* list, int num_elements);
+  bool FinalizeDeviceCreation();
+
+  int fd_;
+  struct uinput_user_dev uinput_user_dev_;
+};
+
+// Virtual touch-pad.
+class VirtualTouchPad : public VirtualInputDevice {
+public:
+  VirtualTouchPad(const char* name, int x_res, int y_res);
+  virtual ~VirtualTouchPad() {}
+
+  void HandlePointerEvent(bool touch_down, int x, int y);
+
+private:
+  static uint32_t Senabled_events_[];
+  static uint32_t Senabled_keys_[];
+  static uint32_t Senabled_abs_[];
+  static uint32_t Senabled_props_[];
+
+  int x_res_;
+  int y_res_;
+};
+
+// Virtual button.
+class VirtualButton : public VirtualInputDevice {
+public:
+  VirtualButton(const char* name, uint32_t input_keycode);
+  virtual ~VirtualButton() {}
+
+  void HandleButtonPressEvent(bool button_down);
+
+private:
+  static uint32_t Senabled_events_[];
+  uint32_t input_keycode_;
+};
+
+// Virtual keyboard.
+class VirtualKeyboard : public VirtualInputDevice {
+public:
+  VirtualKeyboard(const char* name);
+  virtual ~VirtualKeyboard() {}
+
+  void GenerateKeyPressEvent(int code, bool down);
+
+private:
+  static uint32_t Senabled_events_[];
+  std::map<uint32_t, uint32_t> keymapping_;
+};
+
+}  // namespace avd
+#endif
+
diff --git a/guest/frontend/vnc_server/blackboard.cpp b/guest/frontend/vnc_server/blackboard.cpp
new file mode 100644
index 0000000..ce4db8d
--- /dev/null
+++ b/guest/frontend/vnc_server/blackboard.cpp
@@ -0,0 +1,143 @@
+#include "blackboard.h"
+#include "frame_buffer_watcher.h"
+#include <utility>
+#include <algorithm>
+
+#define LOG_TAG "GceVNCServer"
+#include <cutils/log.h>
+
+using avd::vnc::BlackBoard;
+using avd::vnc::Stripe;
+
+avd::vnc::SeqNumberVec avd::vnc::MakeSeqNumberVec() {
+  return SeqNumberVec(FrameBufferWatcher::StripesPerFrame());
+}
+
+void BlackBoard::NewStripeReady(int index, StripeSeqNumber seq_num) {
+  std::lock_guard<std::mutex> guard(m_);
+  D("new stripe arrived from frame watcher");
+  auto& current_seq_num = most_recent_stripe_seq_nums_[index];
+  current_seq_num = std::max(current_seq_num, seq_num);
+  for (auto& client : clients_) {
+    if (client.second.ready_to_receive) {
+      client.second.new_frame_cv.notify_one();
+    }
+  }
+}
+
+void BlackBoard::Register(const VncClientConnection* conn) {
+  {
+    std::lock_guard<std::mutex> guard(m_);
+    ALOG_ASSERT(!clients_.count(conn));
+    clients_[conn];  // constructs new state in place
+  }
+  new_client_cv_.notify_one();
+}
+
+void BlackBoard::Unregister(const VncClientConnection* conn) {
+  std::lock_guard<std::mutex> guard(m_);
+  ALOG_ASSERT(clients_.count(conn));
+  clients_.erase(clients_.find(conn));
+}
+
+bool BlackBoard::NoNewStripesFor(const SeqNumberVec& seq_nums) const {
+  ALOG_ASSERT(seq_nums.size() == most_recent_stripe_seq_nums.size());
+  for (auto state_seq_num = seq_nums.begin(),
+            held_seq_num = most_recent_stripe_seq_nums_.begin();
+       state_seq_num != seq_nums.end(); ++state_seq_num, ++held_seq_num) {
+    if (*state_seq_num < *held_seq_num) {
+      return false;
+    }
+  }
+  return true;
+}
+
+avd::vnc::StripePtrVec BlackBoard::WaitForSenderWork(
+    const VncClientConnection* conn) {
+  std::unique_lock<std::mutex> guard(m_);
+  auto& state = GetStateForClient(conn);
+  D("Waiting for stripe...");
+  while (!state.closed &&
+         (!state.ready_to_receive || NoNewStripesFor(state.stripe_seq_nums))) {
+    state.new_frame_cv.wait(guard);
+  }
+  D("At least one new stripe is available, should unblock %p", conn);
+  state.ready_to_receive = false;
+  auto new_stripes = frame_buffer_watcher_->StripesNewerThan(
+      state.orientation, state.stripe_seq_nums);
+  for (auto& s : new_stripes) {
+    state.stripe_seq_nums[s->index] = s->seq_number;
+  }
+  return new_stripes;
+}
+
+void BlackBoard::WaitForAtLeastOneClientConnection() {
+  std::unique_lock<std::mutex> guard(m_);
+  while (clients_.empty()) {
+    new_client_cv_.wait(guard);
+  }
+}
+
+void BlackBoard::SetOrientation(const VncClientConnection* conn,
+                                ScreenOrientation orientation) {
+  std::lock_guard<std::mutex> guard(m_);
+  auto& state = GetStateForClient(conn);
+  state.orientation = orientation;
+  // After an orientation change the vnc client will need all stripes from
+  // the new orientation, regardless of age.
+  ResetToZero(&state.stripe_seq_nums);
+}
+
+void BlackBoard::SignalClientNeedsEntireScreen(
+    const VncClientConnection* conn) {
+  std::lock_guard<std::mutex> guard(m_);
+  ResetToZero(&GetStateForClient(conn).stripe_seq_nums);
+}
+
+void BlackBoard::ResetToZero(SeqNumberVec* seq_nums) {
+  seq_nums->assign(FrameBufferWatcher::StripesPerFrame(), StripeSeqNumber{});
+}
+
+void BlackBoard::FrameBufferUpdateRequestReceived(
+    const VncClientConnection* conn) {
+  std::lock_guard<std::mutex> guard(m_);
+  D("Received frame buffer update request");
+  auto& state = GetStateForClient(conn);
+  state.ready_to_receive = true;
+  state.new_frame_cv.notify_one();
+}
+
+void BlackBoard::StopWaiting(const VncClientConnection* conn) {
+  std::lock_guard<std::mutex> guard(m_);
+  auto& state = GetStateForClient(conn);
+  state.closed = true;
+  // Wake up the thread that might be in WaitForSenderWork()
+  state.new_frame_cv.notify_one();
+}
+
+void BlackBoard::set_frame_buffer_watcher(
+    avd::vnc::FrameBufferWatcher* frame_buffer_watcher) {
+  std::lock_guard<std::mutex> guard(m_);
+  frame_buffer_watcher_ = frame_buffer_watcher;
+}
+
+void BlackBoard::set_jpeg_quality_level(int quality_level) {
+  // NOTE all vnc clients share a common jpeg quality level because the
+  // server doesn't compress per-client. The quality level for all clients
+  // will be whatever the most recent set was by any client.
+  std::lock_guard<std::mutex> guard(m_);
+  if (quality_level < kJpegMinQualityEncoding ||
+      quality_level > kJpegMaxQualityEncoding) {
+    ALOGW("Bogus jpeg quality level: %d. Quality must be in range [%d, %d]",
+          quality_level, kJpegMinQualityEncoding, kJpegMaxQualityEncoding);
+    return;
+  }
+  jpeg_quality_level_ = 55 + (5 * (quality_level + 32));
+  D("jpeg quality level set to %d%%:", jpeg_quality_level_);
+}
+
+BlackBoard::ClientFBUState& BlackBoard::GetStateForClient(
+    const VncClientConnection* conn) {
+  ALOG_ASSERT(clients_.count(conn));
+  return clients_[conn];
+}
diff --git a/guest/frontend/vnc_server/blackboard.h b/guest/frontend/vnc_server/blackboard.h
new file mode 100644
index 0000000..49a169f
--- /dev/null
+++ b/guest/frontend/vnc_server/blackboard.h
@@ -0,0 +1,102 @@
+#ifndef DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_BLACKBOARD_H_
+#define DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_BLACKBOARD_H_
+
+#include "vnc_utils.h"
+
+#include <android-base/thread_annotations.h>
+
+#include <unordered_map>
+#include <mutex>
+#include <condition_variable>
+#include <memory>
+
+namespace avd {
+namespace vnc {
+
+class VncClientConnection;
+class FrameBufferWatcher;
+using StripePtrVec = std::vector<std::shared_ptr<const Stripe>>;
+using SeqNumberVec = std::vector<StripeSeqNumber>;
+
+SeqNumberVec MakeSeqNumberVec();
+
+class BlackBoard {
+ private:
+  struct ClientFBUState {
+    bool ready_to_receive{};
+    ScreenOrientation orientation{};
+    std::condition_variable new_frame_cv;
+    SeqNumberVec stripe_seq_nums = MakeSeqNumberVec();
+    bool closed{};
+  };
+
+ public:
+  class Registerer {
+   public:
+    Registerer(BlackBoard* bb, const VncClientConnection* conn)
+        : bb_{bb}, conn_{conn} {
+      bb->Register(conn);
+    }
+    ~Registerer() {
+      bb_->Unregister(conn_);
+    }
+    Registerer(const Registerer&) = delete;
+    Registerer& operator=(const Registerer&) = delete;
+
+   private:
+    BlackBoard* bb_{};
+    const VncClientConnection* conn_{};
+  };
+
+  BlackBoard() = default;
+  BlackBoard(const BlackBoard&) = delete;
+  BlackBoard& operator=(const BlackBoard&) = delete;
+
+  bool NoNewStripesFor(const SeqNumberVec& seq_nums) const REQUIRES(m_);
+  void NewStripeReady(int index, StripeSeqNumber seq_num);
+  void Register(const VncClientConnection* conn);
+  void Unregister(const VncClientConnection* conn);
+
+  StripePtrVec WaitForSenderWork(const VncClientConnection* conn);
+
+  void WaitForAtLeastOneClientConnection();
+
+  void FrameBufferUpdateRequestReceived(const VncClientConnection* conn);
+  // Setting orientation implies needing the entire screen
+  void SetOrientation(const VncClientConnection* conn,
+                      ScreenOrientation orientation);
+  void SignalClientNeedsEntireScreen(const VncClientConnection* conn);
+
+  void StopWaiting(const VncClientConnection* conn);
+
+  void set_frame_buffer_watcher(FrameBufferWatcher* frame_buffer_watcher);
+
+  // quality_level must be the value received from the client, in the range
+  // [kJpegMinQualityEncoding, kJpegMaxQualityEncoding], else it is ignored.
+  void set_jpeg_quality_level(int quality_level);
+
+  int jpeg_quality_level() const {
+    std::lock_guard<std::mutex> guard(m_);
+    return jpeg_quality_level_;
+  }
+
+ private:
+  ClientFBUState& GetStateForClient(const VncClientConnection* conn)
+      REQUIRES(m_);
+  static void ResetToZero(SeqNumberVec* seq_nums);
+
+  mutable std::mutex m_;
+  SeqNumberVec most_recent_stripe_seq_nums_ GUARDED_BY(m_) = MakeSeqNumberVec();
+  std::unordered_map<const VncClientConnection*, ClientFBUState> clients_
+      GUARDED_BY(m_);
+  int jpeg_quality_level_ GUARDED_BY(m_) = 100;
+  std::condition_variable new_client_cv_;
+  // NOTE the FrameBufferWatcher pointer itself should be
+  // guarded, but not the pointee.
+  FrameBufferWatcher* frame_buffer_watcher_ GUARDED_BY(m_){};
+};
+
+}  // namespace vnc
+}  // namespace avd
+
+#endif
diff --git a/guest/frontend/vnc_server/frame_buffer_watcher.cpp b/guest/frontend/vnc_server/frame_buffer_watcher.cpp
new file mode 100644
index 0000000..c592765
--- /dev/null
+++ b/guest/frontend/vnc_server/frame_buffer_watcher.cpp
@@ -0,0 +1,173 @@
+#include "vnc_utils.h"
+#include "frame_buffer_watcher.h"
+#include <ThreadSafeQueue.hpp>
+
+#include <algorithm>
+#include <cstdint>
+#include <cstring>
+#include <iterator>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <utility>
+
+#define LOG_TAG "GceVNCServer"
+#include <cutils/log.h>
+
+using avd::vnc::FrameBufferWatcher;
+
+FrameBufferWatcher::FrameBufferWatcher(BlackBoard* bb)
+    : bb_{bb}, hwcomposer{bb_} {
+  for (auto& stripes_vec : stripes_) {
+    std::generate_n(std::back_inserter(stripes_vec),
+                    SimulatedHWComposer::NumberOfStripes(),
+                    std::make_shared<Stripe>);
+  }
+  bb_->set_frame_buffer_watcher(this);
+  auto num_workers = std::max(std::thread::hardware_concurrency(), 1u);
+  std::generate_n(std::back_inserter(workers_), num_workers, [this] {
+    return std::thread{&FrameBufferWatcher::Worker, this};
+  });
+}
+
+FrameBufferWatcher::~FrameBufferWatcher() {
+  {
+    std::lock_guard<std::mutex> guard(m_);
+    closed_ = true;
+  }
+  for (auto& tid : workers_) {
+    tid.join();
+  }
+}
+
+bool FrameBufferWatcher::closed() const {
+  std::lock_guard<std::mutex> guard(m_);
+  return closed_;
+}
+
+avd::vnc::Stripe FrameBufferWatcher::Rotated(Stripe stripe) {
+  LOG_ALWAYS_FATAL_IF(stripe.orientation == ScreenOrientation::Landscape,
+                      "Rotating a landscape stripe, this is a mistake");
+  auto w = stripe.width;
+  auto h = stripe.height;
+  const auto& raw = stripe.raw_data;
+  Message rotated(raw.size(), 0xAA);
+  for (std::uint16_t i = 0; i < w; ++i) {
+    for (std::uint16_t j = 0; j < h; ++j) {
+      auto to = (i * h + j) * BytesPerPixel();
+      auto from = (w - (i + 1) + w * j) * BytesPerPixel();
+      ALOG_ASSERT(from < raw.size());
+      ALOG_ASSERT(to < rotated.size());
+      std::memcpy(&rotated[to], &raw[from], BytesPerPixel());
+    }
+  }
+  std::swap(stripe.x, stripe.y);
+  std::swap(stripe.width, stripe.height);
+  stripe.raw_data = std::move(rotated);
+  stripe.orientation = ScreenOrientation::Landscape;
+  return stripe;
+}
+
+bool FrameBufferWatcher::StripeIsDifferentFromPrevious(
+    const Stripe& stripe) const {
+  return Stripes(stripe.orientation)[stripe.index]->raw_data != stripe.raw_data;
+}
+
+avd::vnc::StripePtrVec FrameBufferWatcher::StripesNewerThan(
+    ScreenOrientation orientation, const SeqNumberVec& seq_numbers) const {
+  std::lock_guard<std::mutex> guard(stripes_lock_);
+  const auto& stripes = Stripes(orientation);
+  ALOG_ASSERT(seq_numbers.size() == stripes.size());
+  StripePtrVec new_stripes;
+  auto seq_number_it = seq_numbers.begin();
+  std::copy_if(stripes.begin(), stripes.end(), std::back_inserter(new_stripes),
+               [seq_number_it](const StripePtrVec::value_type& s) mutable {
+                 return *(seq_number_it++) < s->seq_number;
+               });
+  return new_stripes;
+}
+
+avd::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
+    ScreenOrientation orientation) {
+  return stripes_[static_cast<int>(orientation)];
+}
+
+const avd::vnc::StripePtrVec& FrameBufferWatcher::Stripes(
+    ScreenOrientation orientation) const {
+  return stripes_[static_cast<int>(orientation)];
+}
+
+bool FrameBufferWatcher::UpdateMostRecentSeqNumIfStripeIsNew(
+    const Stripe& stripe) {
+  if (most_recent_identical_stripe_seq_nums_[stripe.index] <=
+      stripe.seq_number) {
+    most_recent_identical_stripe_seq_nums_[stripe.index] = stripe.seq_number;
+    return true;
+  }
+  return false;
+}
+
+bool FrameBufferWatcher::UpdateStripeIfStripeIsNew(
+    const std::shared_ptr<const Stripe>& stripe) {
+  std::lock_guard<std::mutex> guard(stripes_lock_);
+  if (UpdateMostRecentSeqNumIfStripeIsNew(*stripe)) {
+    Stripes(stripe->orientation)[stripe->index] = stripe;
+    return true;
+  }
+  return false;
+}
+
+void FrameBufferWatcher::CompressStripe(JpegCompressor* jpeg_compressor,
+                                        Stripe* stripe) {
+  stripe->jpeg_data = jpeg_compressor->Compress(
+      stripe->raw_data, bb_->jpeg_quality_level(), 0, 0, stripe->width,
+      stripe->height, stripe->width);
+}
+
+void FrameBufferWatcher::Worker() {
+  JpegCompressor jpeg_compressor;
+#ifdef FUZZ_TEST_VNC
+  std::default_random_engine e{std::random_device{}()};
+  std::uniform_int_distribution<int> random{0, 2};
+#endif
+  while (!closed()) {
+    auto portrait_stripe = hwcomposer.GetNewStripe();
+    if (closed()) {
+      break;
+    }
+    {
+      // TODO(haining) use if (with init) and else for c++17 instead of extra
+      // scope and continue
+      // if (std::lock_guard guard(stripes_lock_); /*condition*/) { }
+      std::lock_guard<std::mutex> guard(stripes_lock_);
+      if (!StripeIsDifferentFromPrevious(portrait_stripe)) {
+        UpdateMostRecentSeqNumIfStripeIsNew(portrait_stripe);
+        continue;
+      }
+    }
+    auto seq_num = portrait_stripe.seq_number;
+    auto index = portrait_stripe.index;
+    auto landscape_stripe = Rotated(portrait_stripe);
+    auto stripes = {std::make_shared<Stripe>(std::move(portrait_stripe)),
+                    std::make_shared<Stripe>(std::move(landscape_stripe))};
+    for (auto& stripe : stripes) {
+#ifdef FUZZ_TEST_VNC
+      if (random(e)) {
+        usleep(10000);
+      }
+#endif
+      CompressStripe(&jpeg_compressor, stripe.get());
+    }
+    bool any_new_stripes = false;
+    for (auto& stripe : stripes) {
+      any_new_stripes = UpdateStripeIfStripeIsNew(stripe) || any_new_stripes;
+    }
+    if (any_new_stripes) {
+      bb_->NewStripeReady(index, seq_num);
+    }
+  }
+}
+
+int FrameBufferWatcher::StripesPerFrame() {
+  return SimulatedHWComposer::NumberOfStripes();
+}
diff --git a/guest/frontend/vnc_server/frame_buffer_watcher.h b/guest/frontend/vnc_server/frame_buffer_watcher.h
new file mode 100644
index 0000000..540f3db
--- /dev/null
+++ b/guest/frontend/vnc_server/frame_buffer_watcher.h
@@ -0,0 +1,62 @@
+#ifndef DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_FRAME_BUFFER_WATCHER_H_
+#define DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_FRAME_BUFFER_WATCHER_H_
+
+#include "blackboard.h"
+#include "jpeg_compressor.h"
+#include "simulated_hw_composer.h"
+
+#include <vector>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <utility>
+
+namespace avd {
+namespace vnc {
+class FrameBufferWatcher {
+ public:
+  explicit FrameBufferWatcher(BlackBoard* bb);
+  FrameBufferWatcher(const FrameBufferWatcher&) = delete;
+  FrameBufferWatcher& operator=(const FrameBufferWatcher&) = delete;
+  ~FrameBufferWatcher();
+
+  StripePtrVec StripesNewerThan(ScreenOrientation orientation,
+                                const SeqNumberVec& seq_num) const;
+  static int StripesPerFrame();
+
+ private:
+  static Stripe Rotated(Stripe stripe);
+
+  bool closed() const;
+  bool StripeIsDifferentFromPrevious(const Stripe& stripe) const
+      REQUIRES(stripes_lock_);
+  // returns true if stripe is still considered new and seq number was updated
+  bool UpdateMostRecentSeqNumIfStripeIsNew(const Stripe& stripe)
+      REQUIRES(stripes_lock_);
+  // returns true if stripe is still considered new and was updated
+  bool UpdateStripeIfStripeIsNew(const std::shared_ptr<const Stripe>& stripe)
+      EXCLUDES(stripes_lock_);
+  // Compresses stripe->raw_data to stripe->jpeg_data
+  void CompressStripe(JpegCompressor* jpeg_compressor, Stripe* stripe);
+  void Worker();
+  void Updater();
+
+  StripePtrVec& Stripes(ScreenOrientation orientation) REQUIRES(stripes_lock_);
+  const StripePtrVec& Stripes(ScreenOrientation orientation) const
+      REQUIRES(stripes_lock_);
+
+  std::vector<std::thread> workers_;
+  mutable std::mutex stripes_lock_;
+  std::array<StripePtrVec, kNumOrientations> stripes_ GUARDED_BY(stripes_lock_);
+  SeqNumberVec most_recent_identical_stripe_seq_nums_
+      GUARDED_BY(stripes_lock_) = MakeSeqNumberVec();
+  mutable std::mutex m_;
+  bool closed_ GUARDED_BY(m_){};
+  BlackBoard* bb_{};
+  SimulatedHWComposer hwcomposer{bb_};
+};
+
+}  // namespace vnc
+}  // namespace avd
+
+#endif
diff --git a/guest/frontend/vnc_server/jpeg_compressor.cpp b/guest/frontend/vnc_server/jpeg_compressor.cpp
new file mode 100644
index 0000000..3129184
--- /dev/null
+++ b/guest/frontend/vnc_server/jpeg_compressor.cpp
@@ -0,0 +1,65 @@
+#include "jpeg_compressor.h"
+#include "vnc_utils.h"
+
+#include <stdio.h>  // stdio.h must appear before jpeglib.h
+#include <jpeglib.h>
+
+#define LOG_TAG "GceVNCServer"
+#include <cutils/log.h>
+
+using avd::vnc::JpegCompressor;
+
+namespace {
+void InitCinfo(jpeg_compress_struct* cinfo, jpeg_error_mgr* err,
+               std::uint16_t width, std::uint16_t height, int jpeg_quality) {
+  cinfo->err = jpeg_std_error(err);
+  jpeg_create_compress(cinfo);
+
+  cinfo->image_width = width;
+  cinfo->image_height = height;
+  cinfo->input_components = avd::vnc::BytesPerPixel();
+  cinfo->in_color_space = JCS_EXT_RGBX;
+
+  jpeg_set_defaults(cinfo);
+  jpeg_set_quality(cinfo, jpeg_quality, true);
+}
+}  // namespace
+
+avd::vnc::Message JpegCompressor::Compress(const Message& frame,
+                                           int jpeg_quality, std::uint16_t x,
+                                           std::uint16_t y, std::uint16_t width,
+                                           std::uint16_t height,
+                                           int screen_width) {
+  jpeg_compress_struct cinfo{};
+  jpeg_error_mgr err{};
+  InitCinfo(&cinfo, &err, width, height, jpeg_quality);
+
+  auto* compression_buffer = buffer_.get();
+  auto compression_buffer_size = buffer_capacity_;
+  jpeg_mem_dest(&cinfo, &compression_buffer, &compression_buffer_size);
+  jpeg_start_compress(&cinfo, true);
+
+  while (cinfo.next_scanline < cinfo.image_height) {
+    auto row = static_cast<JSAMPROW>(const_cast<std::uint8_t*>(
+        &frame[(y * screen_width * BytesPerPixel()) +
+               (cinfo.next_scanline * BytesPerPixel() * screen_width) +
+               (x * BytesPerPixel())]));
+    jpeg_write_scanlines(&cinfo, &row, 1);
+  }
+  jpeg_finish_compress(&cinfo);
+  jpeg_destroy_compress(&cinfo);
+
+  UpdateBuffer(compression_buffer, compression_buffer_size);
+  return {compression_buffer, compression_buffer + compression_buffer_size};
+}
+
+void JpegCompressor::UpdateBuffer(std::uint8_t* compression_buffer,
+                                  unsigned long compression_buffer_size) {
+  if (buffer_capacity_ < compression_buffer_size) {
+    ALOG_ASSERT(buffer_ != compression_buffer);
+    buffer_capacity_ = compression_buffer_size;
+    buffer_.reset(compression_buffer);
+  } else {
+    ALOG_ASSERT(buffer_ == compression_buffer);
+  }
+}
diff --git a/guest/frontend/vnc_server/jpeg_compressor.h b/guest/frontend/vnc_server/jpeg_compressor.h
new file mode 100644
index 0000000..db93267
--- /dev/null
+++ b/guest/frontend/vnc_server/jpeg_compressor.h
@@ -0,0 +1,41 @@
+#ifndef DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_JPEG_COMPRESSOR_H_
+#define DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_JPEG_COMPRESSOR_H_
+
+#include "vnc_utils.h"
+
+#include <memory>
+#include <cstdlib>
+#include <cstdint>
+
+namespace avd {
+namespace vnc {
+
+// libjpeg-turbo with jpeg_mem_dest (using memory as a destination) is funky.
+// If you give it a buffer that is big enough it will use it.
+// If you give it a buffer that is too small, it will allocate a new buffer
+// but will NOT free the buffer you gave it.
+// This class keeps track of the capacity of the working buffer, and frees the
+// old buffer if libjpeg-turbo silently discards it.
+class JpegCompressor {
+ public:
+  Message Compress(const Message& frame, int jpeg_quality, std::uint16_t x,
+                   std::uint16_t y, std::uint16_t width, std::uint16_t height,
+                   int screen_width);
+
+ private:
+  void UpdateBuffer(std::uint8_t* compression_buffer,
+                    unsigned long compression_buffer_size);
+  struct Freer {
+    void operator()(void* p) const {
+      std::free(p);
+    }
+  };
+
+  std::unique_ptr<std::uint8_t, Freer> buffer_;
+  unsigned long buffer_capacity_{};
+};
+
+}  // namespace vnc
+}  // namespace avd
+
+#endif
diff --git a/guest/frontend/vnc_server/keysyms.h b/guest/frontend/vnc_server/keysyms.h
new file mode 100644
index 0000000..7973957
--- /dev/null
+++ b/guest/frontend/vnc_server/keysyms.h
@@ -0,0 +1,94 @@
+#ifndef DEVICE_GOOGLE_GCE_KEYSYMS_H_
+#define DEVICE_GOOGLE_GCE_KEYSYMS_H_
+
+namespace avd {
+namespace xk {
+
+constexpr uint32_t BackSpace = 0xff08,
+          Tab = 0xff09,
+          Return = 0xff0d,
+          Enter = Return,
+          Escape = 0xff1b,
+          MultiKey = 0xff20,
+          Insert = 0xff63,
+          Delete = 0xffff,
+          Pause = 0xff13,
+          Home = 0xff50,
+          End = 0xff57,
+          PageUp = 0xff55,
+          PageDown = 0xff56,
+          Left = 0xff51,
+          Up = 0xff52,
+          Right = 0xff53,
+          Down = 0xff54,
+          F1 = 0xffbe,
+          F2 = 0xffbf,
+          F3 = 0xffc0,
+          F4 = 0xffc1,
+          F5 = 0xffc2,
+          F6 = 0xffc3,
+          F7 = 0xffc4,
+          F8 = 0xffc5,
+          F9 = 0xffc6,
+          F10 = 0xffc7,
+          F11 = 0xffc8,
+          F12 = 0xffc9,
+          F13 = 0xffca,
+          F14 = 0xffcb,
+          F15 = 0xffcc,
+          F16 = 0xffcd,
+          F17 = 0xffce,
+          F18 = 0xffcf,
+          F19 = 0xffd0,
+          F20 = 0xffd1,
+          F21 = 0xffd2,
+          F22 = 0xffd3,
+          F23 = 0xffd4,
+          F24 = 0xffd5,
+          ShiftLeft = 0xffe1,
+          ShiftRight = 0xffe2,
+          ControlLeft = 0xffe3,
+          ControlRight = 0xffe4,
+          MetaLeft = 0xffe7,
+          MetaRight = 0xffe8,
+          AltLeft = 0xffe9,
+          AltRight = 0xffea,
+          CapsLock = 0xffe5,
+          NumLock = 0xff7f,
+          ScrollLock = 0xff14,
+          Keypad0 = 0xffb0,
+          Keypad1 = 0xffb1,
+          Keypad2 = 0xffb2,
+          Keypad3 = 0xffb3,
+          Keypad4 = 0xffb4,
+          Keypad5 = 0xffb5,
+          Keypad6 = 0xffb6,
+          Keypad7 = 0xffb7,
+          Keypad8 = 0xffb8,
+          Keypad9 = 0xffb9,
+          KeypadMultiply = 0xffaa,
+          KeypadSubtract = 0xffad,
+          KeypadAdd = 0xffab,
+          KeypadDecimal = 0xffae,
+          KeypadEnter = 0xff8d,
+          KeypadDivide = 0xffaf,
+          KeypadEqual = 0xffbd,
+          PlusMinus = 0xb1,
+          SysReq = 0xff15,
+          LineFeed = 0xff0a,
+          KeypadSeparator = 0xffac,
+          Yen = 0xa5,
+          Cancel = 0xff69,
+          Undo = 0xff65,
+          Redo = 0xff66,
+          Find = 0xff68,
+          Print = 0xff61,
+          VolumeDown = 0x1008ff11,
+          Mute = 0x1008ff12,
+          VolumeUp = 0x1008ff13,
+          Menu = 0xff67,
+          VNCMenu = 0xffed;  // VNC seems to translate MENU to this
+
+}  // namespace xk
+}  // namespace avd
+#endif
diff --git a/guest/frontend/vnc_server/main.cpp b/guest/frontend/vnc_server/main.cpp
new file mode 100644
index 0000000..2436a65
--- /dev/null
+++ b/guest/frontend/vnc_server/main.cpp
@@ -0,0 +1,25 @@
+#include "vnc_server.h"
+
+#include <signal.h>
+#include <algorithm>
+#include <string>
+
+namespace {
+constexpr int kVncServerPort = 6444;
+
+// TODO(haining) use gflags when available
+bool HasAggressiveFlag(int argc, char* argv[]) {
+  const std::string kAggressive = "--aggressive";
+  auto end = argv + argc;
+  return std::find(argv, end, kAggressive) != end;
+}
+}  // namespace
+
+int main(int argc, char* argv[]) {
+  struct sigaction new_action, old_action;
+  memset(&new_action, 0, sizeof(new_action));
+  new_action.sa_handler = SIG_IGN;
+  sigaction(SIGPIPE, &new_action, &old_action);
+  avd::vnc::VncServer vnc_server(kVncServerPort, HasAggressiveFlag(argc, argv));
+  vnc_server.MainLoop();
+}
diff --git a/guest/frontend/vnc_server/simulated_hw_composer.cpp b/guest/frontend/vnc_server/simulated_hw_composer.cpp
new file mode 100644
index 0000000..84a87d2
--- /dev/null
+++ b/guest/frontend/vnc_server/simulated_hw_composer.cpp
@@ -0,0 +1,106 @@
+#include "simulated_hw_composer.h"
+
+using avd::vnc::SimulatedHWComposer;
+
+SimulatedHWComposer::SimulatedHWComposer(BlackBoard* bb)
+    :
+#ifdef FUZZ_TEST_VNC
+      engine_{std::random_device{}()},
+#endif
+      control_{GceFrameBufferControl::getInstance()},
+      bb_{bb},
+      stripes_(kMaxQueueElements, &SimulatedHWComposer::EraseHalfOfElements) {
+  void* p{};
+  GceFrameBuffer::OpenAndMapFrameBuffer(&p, &frame_buffer_fd_);
+  frame_buffer_memory_ = static_cast<char*>(p);
+  stripe_maker_ = std::thread(&SimulatedHWComposer::MakeStripes, this);
+}
+
+SimulatedHWComposer::~SimulatedHWComposer() {
+  close();
+  stripe_maker_.join();
+  GceFrameBuffer::UnmapAndCloseFrameBuffer(frame_buffer_memory_,
+                                           frame_buffer_fd_);
+}
+
+avd::vnc::Stripe SimulatedHWComposer::GetNewStripe() {
+  auto s = stripes_.Pop();
+#ifdef FUZZ_TEST_VNC
+  if (random_(engine_)) {
+    usleep(7000);
+    stripes_.Push(std::move(s));
+    s = stripes_.Pop();
+  }
+#endif
+  return s;
+}
+
+bool SimulatedHWComposer::closed() {
+  std::lock_guard<std::mutex> guard(m_);
+  return closed_;
+}
+
+void SimulatedHWComposer::close() {
+  std::lock_guard<std::mutex> guard(m_);
+  closed_ = true;
+}
+
+// Assuming the number of stripes is less than half the size of the queue
+// this will be safe as the newest stripes won't be lost. In the real
+// hwcomposer, where stripes are coming in a different order, the full
+// queue case would probably need a different approach to be safe.
+void SimulatedHWComposer::EraseHalfOfElements(
+    ThreadSafeQueue<Stripe>::QueueImpl* q) {
+  q->erase(q->begin(), std::next(q->begin(), kMaxQueueElements / 2));
+}
+
+void SimulatedHWComposer::MakeStripes() {
+  std::uint32_t previous_seq_num{};
+  auto screen_height = ActualScreenHeight();
+  Message raw_screen;
+  std::uint64_t stripe_seq_num = 1;
+  while (!closed()) {
+    bb_->WaitForAtLeastOneClientConnection();
+    int y_offset{};
+    control_.WaitForFrameBufferChangeSince(previous_seq_num, &y_offset,
+                                           &previous_seq_num, nullptr);
+
+    const auto* frame_start =
+        frame_buffer_memory_ + y_offset * ActualScreenWidth() * BytesPerPixel();
+    raw_screen.assign(frame_start, frame_start + ScreenSizeInBytes());
+
+    for (int i = 0; i < kNumStripes; ++i) {
+      ++stripe_seq_num;
+      std::uint16_t y = (screen_height / kNumStripes) * i;
+
+      // Last frames on the right and/or bottom handle extra pixels
+      // when a screen dimension is not evenly divisible by Frame::kNumSlots.
+      std::uint16_t height =
+          screen_height / kNumStripes +
+          (i + 1 == kNumStripes ? screen_height % kNumStripes : 0);
+      const auto* raw_start =
+          &raw_screen[y * ActualScreenWidth() * BytesPerPixel()];
+      const auto* raw_end =
+          raw_start + (height * ActualScreenWidth() * BytesPerPixel());
+      // creating a named object and setting individual data members in order
+      // to make klp happy
+      // TODO (haining) construct this inside the call when not compiling
+      // on klp
+      Stripe s{};
+      s.index = i;
+      s.frame_id = previous_seq_num;
+      s.x = 0;
+      s.y = y;
+      s.width = ActualScreenWidth();
+      s.height = height;
+      s.raw_data.assign(raw_start, raw_end);
+      s.seq_number = StripeSeqNumber{stripe_seq_num};
+      s.orientation = ScreenOrientation::Portrait;
+      stripes_.Push(std::move(s));
+    }
+  }
+}
+
+int SimulatedHWComposer::NumberOfStripes() {
+  return kNumStripes;
+}
diff --git a/guest/frontend/vnc_server/simulated_hw_composer.h b/guest/frontend/vnc_server/simulated_hw_composer.h
new file mode 100644
index 0000000..3b68548
--- /dev/null
+++ b/guest/frontend/vnc_server/simulated_hw_composer.h
@@ -0,0 +1,57 @@
+#ifndef DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_SIMULATED_HW_COMPOSER_H_
+#define DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_SIMULATED_HW_COMPOSER_H_
+
+#include "blackboard.h"
+
+#include <GceFrameBuffer.h>
+#include <GceFrameBufferControl.h>
+#include <ThreadSafeQueue.hpp>
+
+#include <mutex>
+#include <thread>
+
+#include <condition_variable>
+#ifdef FUZZ_TEST_VNC
+#include <random>
+#endif
+
+namespace avd {
+namespace vnc {
+class SimulatedHWComposer {
+ public:
+  SimulatedHWComposer(BlackBoard* bb);
+  SimulatedHWComposer(const SimulatedHWComposer&) = delete;
+  SimulatedHWComposer& operator=(const SimulatedHWComposer&) = delete;
+  ~SimulatedHWComposer();
+
+  Stripe GetNewStripe();
+
+  // NOTE not constexpr on purpose
+  static int NumberOfStripes();
+
+ private:
+  bool closed();
+  void close();
+  static void EraseHalfOfElements(ThreadSafeQueue<Stripe>::QueueImpl* q);
+  void MakeStripes();
+
+#ifdef FUZZ_TEST_VNC
+  std::default_random_engine engine_;
+  std::uniform_int_distribution<int> random_ =
+      std::uniform_int_distribution<int>{0, 2};
+#endif
+  static constexpr int kNumStripes = 8;
+  constexpr static std::size_t kMaxQueueElements = 64;
+  bool closed_ GUARDED_BY(m_){};
+  std::mutex m_;
+  GceFrameBufferControl& control_;
+  BlackBoard* bb_{};
+  ThreadSafeQueue<Stripe> stripes_;
+  std::thread stripe_maker_;
+  char* frame_buffer_memory_{};
+  int frame_buffer_fd_{};
+};
+}  // namespace vnc
+}  // namespace avd
+
+#endif
diff --git a/guest/frontend/vnc_server/tcp_socket.cpp b/guest/frontend/vnc_server/tcp_socket.cpp
new file mode 100644
index 0000000..2ccdcf9
--- /dev/null
+++ b/guest/frontend/vnc_server/tcp_socket.cpp
@@ -0,0 +1,61 @@
+#include "tcp_socket.h"
+#include <cerrno>
+
+#include <cutils/sockets.h>
+#define LOG_TAG "GceVNCServer"
+#include <cutils/log.h>
+
+using avd::vnc::ClientSocket;
+using avd::vnc::ServerSocket;
+using avd::vnc::Message;
+
+Message ClientSocket::Recv(size_t length) {
+  Message buf(length);
+  ssize_t total_read = 0;
+  while (total_read < static_cast<ssize_t>(length)) {
+    auto just_read = read(fd_, &buf[total_read], buf.size() - total_read);
+    if (just_read <= 0) {
+      if (just_read < 0) {
+        ALOGE("read() error: %s", strerror(errno));
+      }
+      other_side_closed_ = true;
+      return Message{};
+    }
+    total_read += just_read;
+  }
+  ALOG_ASSERT(total_read == static_cast<ssize_t>(length));
+  return buf;
+}
+
+ssize_t ClientSocket::Send(const uint8_t* data, std::size_t size) {
+  std::lock_guard<std::mutex> lock(send_lock_);
+  ssize_t written{};
+  while (written < static_cast<ssize_t>(size)) {
+    auto just_written = write(fd_, data + written, size - written);
+    if (just_written <= 0) {
+      ALOGI("Couldn't write to vnc client: %s", strerror(errno));
+      return just_written;
+    }
+    written += just_written;
+  }
+  return written;
+}
+
+ssize_t ClientSocket::Send(const Message& message) {
+  return Send(&message[0], message.size());
+}
+
+ServerSocket::ServerSocket(int port)
+    : fd_{socket_inaddr_any_server(port, SOCK_STREAM)} {
+  if (fd_ < 0) {
+    LOG_FATAL("Couldn't open streaming server on port %d", port);
+  }
+}
+
+ClientSocket ServerSocket::Accept() {
+  int client = accept(fd_, nullptr, nullptr);
+  if (client < 0) {
+    LOG_FATAL("Error attemping to accept: %s", strerror(errno));
+  }
+  return ClientSocket{client};
+}
diff --git a/guest/frontend/vnc_server/tcp_socket.h b/guest/frontend/vnc_server/tcp_socket.h
new file mode 100644
index 0000000..701ea0e
--- /dev/null
+++ b/guest/frontend/vnc_server/tcp_socket.h
@@ -0,0 +1,87 @@
+#ifndef DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_TCPSOCKET_H_
+#define DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_TCPSOCKET_H_
+
+#include "vnc_utils.h"
+
+#include <mutex>
+#include <cstdint>
+#include <cstddef>
+
+#include <unistd.h>
+
+namespace avd {
+namespace vnc {
+
+class ServerSocket;
+
+// Recv and Send wait until all data has been received or sent.
+// Send is thread safe in this regard, Recv is not.
+class ClientSocket {
+ public:
+  ClientSocket(ClientSocket&& other) : fd_{other.fd_} {
+    other.fd_ = -1;
+  }
+
+  ClientSocket& operator=(ClientSocket&& other) {
+    if (fd_ >= 0) {
+      close(fd_);
+    }
+    fd_ = other.fd_;
+    other.fd_ = -1;
+    return *this;
+  }
+
+  ClientSocket(const ClientSocket&) = delete;
+  ClientSocket& operator=(const ClientSocket&) = delete;
+
+  ~ClientSocket() {
+    if (fd_ >= 0) {
+      close(fd_);
+    }
+  }
+
+  Message Recv(std::size_t length);
+  ssize_t Send(const std::uint8_t* data, std::size_t size);
+  ssize_t Send(const Message& message);
+
+  template <std::size_t N>
+  ssize_t Send(const std::uint8_t (&data)[N]) {
+    return Send(data, N);
+  }
+
+  bool closed() const {
+    return other_side_closed_;
+  }
+
+ private:
+  friend ServerSocket;
+  explicit ClientSocket(int fd) : fd_(fd) {}
+
+  int fd_ = -1;
+  bool other_side_closed_{};
+  std::mutex send_lock_;
+};
+
+class ServerSocket {
+ public:
+  explicit ServerSocket(int port);
+
+  ServerSocket(const ServerSocket&) = delete;
+  ServerSocket& operator=(const ServerSocket&) = delete;
+
+  ~ServerSocket() {
+    if (fd_ >= 0) {
+      close(fd_);
+    }
+  }
+
+  ClientSocket Accept();
+
+ private:
+  int fd_ = -1;
+};
+
+}  // namespace vnc
+}  // namespace avd
+
+#endif
diff --git a/guest/frontend/vnc_server/virtual_inputs.cpp b/guest/frontend/vnc_server/virtual_inputs.cpp
new file mode 100644
index 0000000..c946d57
--- /dev/null
+++ b/guest/frontend/vnc_server/virtual_inputs.cpp
@@ -0,0 +1,19 @@
+#include "virtual_inputs.h"
+#include <mutex>
+
+using avd::vnc::VirtualInputs;
+
+void VirtualInputs::GenerateKeyPressEvent(int code, bool down) {
+  std::lock_guard<std::mutex> guard(m_);
+  virtual_keyboard_.GenerateKeyPressEvent(code, down);
+}
+
+void VirtualInputs::PressPowerButton(bool down) {
+  std::lock_guard<std::mutex> guard(m_);
+  virtual_power_button_.HandleButtonPressEvent(down);
+}
+
+void VirtualInputs::HandlePointerEvent(bool touch_down, int x, int y) {
+  std::lock_guard<std::mutex> guard(m_);
+  virtual_touch_pad_.HandlePointerEvent(touch_down, x, y);
+}
diff --git a/guest/frontend/vnc_server/virtual_inputs.h b/guest/frontend/vnc_server/virtual_inputs.h
new file mode 100644
index 0000000..fba9ede
--- /dev/null
+++ b/guest/frontend/vnc_server/virtual_inputs.h
@@ -0,0 +1,32 @@
+#ifndef DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_VIRTUAL_INPUTS_H_
+#define DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_VIRTUAL_INPUTS_H_
+
+#include "VirtualInputDevice.h"
+#include "vnc_utils.h"
+
+#include <linux/input.h>
+#include <android-base/thread_annotations.h>
+
+#include <mutex>
+
+namespace avd {
+namespace vnc {
+
+class VirtualInputs {
+ public:
+  void GenerateKeyPressEvent(int code, bool down);
+  void PressPowerButton(bool down);
+  void HandlePointerEvent(bool touch_down, int x, int y);
+
+ private:
+  std::mutex m_;
+  VirtualKeyboard virtual_keyboard_ GUARDED_BY(m_){"remote-keyboard"};
+  VirtualTouchPad virtual_touch_pad_ GUARDED_BY(m_){
+      "remote-touchpad", ActualScreenWidth(), ActualScreenHeight()};
+  VirtualButton virtual_power_button_ GUARDED_BY(m_){"remote-power", KEY_POWER};
+};
+
+}  // namespace vnc
+}  // namespace avd
+
+#endif
diff --git a/guest/frontend/vnc_server/vnc_client_connection.cpp b/guest/frontend/vnc_server/vnc_client_connection.cpp
new file mode 100644
index 0000000..b751cf6
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_client_connection.cpp
@@ -0,0 +1,697 @@
+#include "vnc_client_connection.h"
+#include "vnc_utils.h"
+#include "tcp_socket.h"
+#include "keysyms.h"
+
+#include "InitialMetadataReader.h"
+#include <GceFrameBuffer.h>
+#include <gce_sensors_message.h>
+#include <sensors.h>
+
+#include <netinet/in.h>
+#include <sys/time.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstdint>
+#include <cstring>
+#include <memory>
+#include <mutex>
+#include <string>
+#include <thread>
+#include <utility>
+#include <vector>
+
+#ifdef LOG_TAG
+#undef LOG_TAG
+#endif
+#define LOG_TAG "GceVNCServer"
+#include <cutils/log.h>
+
+using avd::vnc::Message;
+using avd::vnc::VncClientConnection;
+using avd::vnc::Stripe;
+using avd::vnc::StripePtrVec;
+
+namespace {
+class BigEndianChecker {
+ public:
+  BigEndianChecker() {
+    uint32_t u = 1;
+    is_big_endian_ = *reinterpret_cast<const char*>(&u) == 0;
+  }
+  bool operator()() const {
+    return is_big_endian_;
+  }
+
+ private:
+  bool is_big_endian_{};
+};
+
+const BigEndianChecker ImBigEndian;
+
+constexpr int32_t kDesktopSizeEncoding = -223;
+constexpr int32_t kTightEncoding = 7;
+
+// These are the lengths not counting the first byte. The first byte
+// indicates the message type.
+constexpr size_t kSetPixelFormatLength = 19;
+constexpr size_t kFramebufferUpdateRequestLength = 9;
+constexpr size_t kSetEncodingsLength = 3;  // more bytes follow
+constexpr size_t kKeyEventLength = 7;
+constexpr size_t kPointerEventLength = 5;
+constexpr size_t kClientCutTextLength = 7;  // more bytes follow
+
+void AppendInNetworkByteOrder(Message* msg, const std::uint8_t b) {
+  msg->push_back(b);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const std::uint16_t s) {
+  const std::uint16_t n = htons(s);
+  auto p = reinterpret_cast<const std::uint8_t*>(&n);
+  msg->insert(msg->end(), p, p + sizeof n);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const std::uint32_t w) {
+  const std::uint32_t n = htonl(w);
+  auto p = reinterpret_cast<const std::uint8_t*>(&n);
+  msg->insert(msg->end(), p, p + sizeof n);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const int32_t w) {
+  std::uint32_t u{};
+  std::memcpy(&u, &w, sizeof u);
+  AppendInNetworkByteOrder(msg, u);
+}
+
+void AppendInNetworkByteOrder(Message* msg, const std::string& str) {
+  msg->insert(msg->end(), str.begin(), str.end());
+}
+
+void AppendToMessage(Message*) {}
+
+template <typename T, typename... Ts>
+void AppendToMessage(Message* msg, T v, Ts... vals) {
+  AppendInNetworkByteOrder(msg, v);
+  AppendToMessage(msg, vals...);
+}
+
+template <typename... Ts>
+Message CreateMessage(Ts... vals) {
+  Message m;
+  AppendToMessage(&m, vals...);
+  return m;
+}
+
+std::string HostName() {
+  return avd::InitialMetadataReader::getInstance()->GetInstanceHostname();
+}
+
+std::uint16_t uint16_tAt(const void* p) {
+  std::uint16_t u{};
+  std::memcpy(&u, p, sizeof u);
+  return ntohs(u);
+}
+
+std::uint32_t uint32_tAt(const void* p) {
+  std::uint32_t u{};
+  std::memcpy(&u, p, sizeof u);
+  return ntohl(u);
+}
+
+std::int32_t int32_tAt(const void* p) {
+  std::uint32_t u{};
+  std::memcpy(&u, p, sizeof u);
+  u = ntohl(u);
+  std::int32_t s{};
+  std::memcpy(&s, &u, sizeof s);
+  return s;
+}
+
+std::uint32_t RedVal(std::uint32_t pixel) {
+  return (pixel >> GceFrameBuffer::kRedShift) &
+         ((0x1 << GceFrameBuffer::kRedBits) - 1);
+}
+
+std::uint32_t BlueVal(std::uint32_t pixel) {
+  return (pixel >> GceFrameBuffer::kBlueShift) &
+         ((0x1 << GceFrameBuffer::kBlueBits) - 1);
+}
+
+std::uint32_t GreenVal(std::uint32_t pixel) {
+  return (pixel >> GceFrameBuffer::kGreenShift) &
+         ((0x1 << GceFrameBuffer::kGreenBits) - 1);
+}
+}
+namespace avd {
+namespace vnc {
+bool operator==(const VncClientConnection::FrameBufferUpdateRequest& lhs,
+                const VncClientConnection::FrameBufferUpdateRequest& rhs) {
+  return lhs.x_pos == rhs.x_pos && lhs.y_pos == rhs.y_pos &&
+         lhs.width == rhs.width && lhs.height == rhs.height;
+}
+
+bool operator!=(const VncClientConnection::FrameBufferUpdateRequest& lhs,
+                const VncClientConnection::FrameBufferUpdateRequest& rhs) {
+  return !(lhs == rhs);
+}
+}  // namespace vnc
+}  // namespace avd  // namespace
+
+VncClientConnection::VncClientConnection(ClientSocket client,
+                                         VirtualInputs* virtual_inputs,
+                                         BlackBoard* bb, bool aggressive)
+    : client_{std::move(client)},
+      sensor_event_hal_{avd::SharedFD::SocketSeqPacketClient(
+          gce_sensors_message::kSensorsHALSocketName)},
+      virtual_inputs_{virtual_inputs},
+      bb_{bb} {
+  frame_buffer_request_handler_tid_ = std::thread(
+      &VncClientConnection::FrameBufferUpdateRequestHandler, this, aggressive);
+}
+
+VncClientConnection::~VncClientConnection() {
+  {
+    std::lock_guard<std::mutex> guard(m_);
+    closed_ = true;
+  }
+  bb_->StopWaiting(this);
+  frame_buffer_request_handler_tid_.join();
+}
+
+void VncClientConnection::StartSession() {
+  SetupProtocol();
+  if (client_.closed()) {
+    return;
+  }
+  SetupSecurityType();
+  if (client_.closed()) {
+    return;
+  }
+  GetClientInit();
+  if (client_.closed()) {
+    return;
+  }
+  SendServerInit();
+  if (client_.closed()) {
+    return;
+  }
+  NormalSession();
+  ALOGI("vnc session terminated");
+}
+
+bool VncClientConnection::closed() {
+  std::lock_guard<std::mutex> guard(m_);
+  return closed_;
+}
+
+void VncClientConnection::SetupProtocol() {
+  static constexpr char kRFBVersion[] = "RFB 003.008\n";
+  static constexpr auto kVersionLen = (sizeof kRFBVersion) - 1;
+  client_.Send(reinterpret_cast<const std::uint8_t*>(kRFBVersion), kVersionLen);
+  auto client_protocol = client_.Recv(kVersionLen);
+  if (std::memcmp(&client_protocol[0], kRFBVersion,
+                  std::min(kVersionLen, client_protocol.size())) != 0) {
+    client_protocol.push_back('\0');
+    ALOGE("vnc client wants a different protocol: %s",
+          reinterpret_cast<const char*>(&client_protocol[0]));
+  }
+}
+
+void VncClientConnection::SetupSecurityType() {
+  static constexpr std::uint8_t kNoneSecurity = 0x1;
+  // The first '0x1' indicates the number of items that follow
+  static constexpr std::uint8_t kOnlyNoneSecurity[] = {0x01, kNoneSecurity};
+  client_.Send(kOnlyNoneSecurity);
+  auto client_security = client_.Recv(1);
+  if (client_.closed()) {
+    return;
+  }
+  if (client_security.front() != kNoneSecurity) {
+    ALOGE("vnc client is asking for security type %d",
+          static_cast<int>(client_security.front()));
+  }
+  static constexpr std::uint8_t kZero[4] = {};
+  client_.Send(kZero);
+}
+
+void VncClientConnection::GetClientInit() {
+  auto client_shared = client_.Recv(1);
+}
+
+void VncClientConnection::SendServerInit() {
+  const std::string server_name = HostName();
+  std::lock_guard<std::mutex> guard(m_);
+  auto server_init = CreateMessage(
+      static_cast<std::uint16_t>(ScreenWidth()),
+      static_cast<std::uint16_t>(ScreenHeight()), pixel_format_.bits_per_pixel,
+      pixel_format_.depth, pixel_format_.big_endian, pixel_format_.true_color,
+      pixel_format_.red_max, pixel_format_.green_max, pixel_format_.blue_max,
+      pixel_format_.red_shift, pixel_format_.green_shift,
+      pixel_format_.blue_shift, std::uint16_t{},  // padding
+      std::uint8_t{},                             // padding
+      static_cast<std::uint32_t>(server_name.size()), server_name);
+  client_.Send(server_init);
+}
+
+Message VncClientConnection::MakeFrameBufferUpdateHeader(
+    std::uint16_t num_stripes) {
+  return CreateMessage(std::uint8_t{0},  // message-type
+                       std::uint8_t{},   // padding
+                       std::uint16_t{num_stripes});
+}
+
+void VncClientConnection::AppendRawStripeHeader(Message* frame_buffer_update,
+                                                const Stripe& stripe) {
+  static constexpr int32_t kRawEncoding = 0;
+  AppendToMessage(frame_buffer_update, std::uint16_t{stripe.x},
+                  std::uint16_t{stripe.y}, std::uint16_t{stripe.width},
+                  std::uint16_t{stripe.height}, kRawEncoding);
+}
+
+void VncClientConnection::AppendJpegSize(Message* frame_buffer_update,
+                                         size_t jpeg_size) {
+  constexpr size_t kJpegSizeOneByteMax = 127;
+  constexpr size_t kJpegSizeTwoByteMax = 16383;
+  constexpr size_t kJpegSizeThreeByteMax = 4194303;
+
+  if (jpeg_size <= kJpegSizeOneByteMax) {
+    AppendToMessage(frame_buffer_update, static_cast<std::uint8_t>(jpeg_size));
+  } else if (jpeg_size <= kJpegSizeTwoByteMax) {
+    auto sz = static_cast<std::uint32_t>(jpeg_size);
+    AppendToMessage(frame_buffer_update,
+                    static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
+                    static_cast<std::uint8_t>((sz >> 7) & 0xFF));
+  } else {
+    if (jpeg_size > kJpegSizeThreeByteMax) {
+      LOG_FATAL("jpeg size is too big: %d must be under %zu", jpeg_size,
+                kJpegSizeThreeByteMax);
+    }
+    const auto sz = static_cast<std::uint32_t>(jpeg_size);
+    AppendToMessage(frame_buffer_update,
+                    static_cast<std::uint8_t>((sz & 0x7F) | 0x80),
+                    static_cast<std::uint8_t>(((sz >> 7) & 0x7F) | 0x80),
+                    static_cast<std::uint8_t>((sz >> 14) & 0xFF));
+  }
+}
+
+void VncClientConnection::AppendRawStripe(Message* frame_buffer_update,
+                                          const Stripe& stripe) const {
+  using Pixel = GceFrameBuffer::Pixel;
+  auto& fbu = *frame_buffer_update;
+  AppendRawStripeHeader(&fbu, stripe);
+  auto init_size = fbu.size();
+  fbu.insert(fbu.end(), stripe.raw_data.begin(), stripe.raw_data.end());
+  for (size_t i = init_size; i < fbu.size(); i += sizeof(Pixel)) {
+    ALOG_ASSERT((i + sizeof(Pixel)) < fbu.size());
+    Pixel raw_pixel{};
+    std::memcpy(&raw_pixel, &fbu[i], sizeof raw_pixel);
+    auto red = RedVal(raw_pixel);
+    auto green = GreenVal(raw_pixel);
+    auto blue = BlueVal(raw_pixel);
+    Pixel pixel = Pixel{red} << pixel_format_.red_shift |
+                  Pixel{blue} << pixel_format_.blue_shift |
+                  Pixel{green} << pixel_format_.green_shift;
+
+    if (bool(pixel_format_.big_endian) != ImBigEndian()) {
+      // flip them bits (refactor into function)
+      auto p = reinterpret_cast<char*>(&pixel);
+      std::swap(p[0], p[3]);
+      std::swap(p[1], p[2]);
+    }
+    ALOG_ASSERT(i + sizeof pixel <= fbu.size());
+    std::memcpy(&fbu[i], &pixel, sizeof pixel);
+  }
+}
+
+Message VncClientConnection::MakeRawFrameBufferUpdate(
+    const StripePtrVec& stripes) const {
+  auto fbu =
+      MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
+  for (auto& stripe : stripes) {
+    AppendRawStripe(&fbu, *stripe);
+  }
+  return fbu;
+}
+
+void VncClientConnection::AppendJpegStripeHeader(Message* frame_buffer_update,
+                                                 const Stripe& stripe) {
+  static constexpr std::uint8_t kJpegEncoding = 0x90;
+  AppendToMessage(frame_buffer_update, stripe.x, stripe.y, stripe.width,
+                  stripe.height, kTightEncoding, kJpegEncoding);
+  AppendJpegSize(frame_buffer_update, stripe.jpeg_data.size());
+}
+
+void VncClientConnection::AppendJpegStripe(Message* frame_buffer_update,
+                                           const Stripe& stripe) {
+  AppendJpegStripeHeader(frame_buffer_update, stripe);
+  frame_buffer_update->insert(frame_buffer_update->end(),
+                              stripe.jpeg_data.begin(), stripe.jpeg_data.end());
+}
+
+Message VncClientConnection::MakeJpegFrameBufferUpdate(
+    const StripePtrVec& stripes) {
+  auto fbu =
+      MakeFrameBufferUpdateHeader(static_cast<std::uint16_t>(stripes.size()));
+  for (auto& stripe : stripes) {
+    AppendJpegStripe(&fbu, *stripe);
+  }
+  return fbu;
+}
+
+Message VncClientConnection::MakeFrameBufferUpdate(
+    const StripePtrVec& stripes) {
+  return use_jpeg_compression_ ? MakeJpegFrameBufferUpdate(stripes)
+                               : MakeRawFrameBufferUpdate(stripes);
+}
+
+void VncClientConnection::FrameBufferUpdateRequestHandler(bool aggressive) {
+  BlackBoard::Registerer reg(bb_, this);
+  const StripeSeqNumber kBeginningOfTime{};
+
+  while (!closed()) {
+    auto stripes = bb_->WaitForSenderWork(this);
+    if (closed()) {
+      break;
+    }
+    LOG_ALWAYS_FATAL_IF(stripes.empty(), "Got 0 stripes");
+    {
+      // lock here so a portrait frame can't be sent after a landscape
+      // DesktopSize update, or vice versa.
+      std::lock_guard<std::mutex> guard(m_);
+      D("Sending update in %s mode",
+        current_orientation_ == ScreenOrientation::Portrait ? "portrait"
+                                                            : "landscape");
+      client_.Send(MakeFrameBufferUpdate(stripes));
+    }
+    if (aggressive) {
+      bb_->FrameBufferUpdateRequestReceived(this);
+    }
+  }
+}
+
+void VncClientConnection::SendDesktopSizeUpdate() {
+  static constexpr int32_t kDesktopSizeEncoding = -223;
+  client_.Send(CreateMessage(std::uint8_t{0},   // message-type,
+                             std::uint8_t{},    // padding
+                             std::uint16_t{1},  // one pseudo rectangle
+                             std::uint16_t{0}, std::uint16_t{0},
+                             static_cast<std::uint16_t>(ScreenWidth()),
+                             static_cast<std::uint16_t>(ScreenHeight()),
+                             kDesktopSizeEncoding));
+}
+
+bool VncClientConnection::IsUrgent(
+    const FrameBufferUpdateRequest& update_request) const {
+  return !update_request.incremental ||
+         update_request != previous_update_request_;
+}
+
+void VncClientConnection::HandleFramebufferUpdateRequest() {
+  auto msg = client_.Recv(kFramebufferUpdateRequestLength);
+  if (msg.size() != kFramebufferUpdateRequestLength) {
+    return;
+  }
+  FrameBufferUpdateRequest fbur{msg[1] == 0, uint16_tAt(&msg[1]),
+                                uint16_tAt(&msg[3]), uint16_tAt(&msg[5]),
+                                uint16_tAt(&msg[7])};
+  if (IsUrgent(fbur)) {
+    bb_->SignalClientNeedsEntireScreen(this);
+  }
+  bb_->FrameBufferUpdateRequestReceived(this);
+  previous_update_request_ = fbur;
+}
+
+void VncClientConnection::HandleSetEncodings() {
+  auto msg = client_.Recv(kSetEncodingsLength);
+  if (msg.size() != kSetEncodingsLength) {
+    return;
+  }
+  auto count = uint16_tAt(&msg[1]);
+  auto encodings = client_.Recv(count * sizeof(int32_t));
+  if (encodings.size() % sizeof(int32_t) != 0) {
+    return;
+  }
+  {
+    std::lock_guard<std::mutex> guard(m_);
+    use_jpeg_compression_ = false;
+  }
+  for (size_t i = 0; i < encodings.size(); i += sizeof(int32_t)) {
+    auto enc = int32_tAt(&encodings[i]);
+    D("client requesting encoding: %d\n", enc);
+    if (enc == kTightEncoding) {
+      // This is a deviation from the spec which says that if a jpeg quality
+      // level is not specified, tight encoding won't use jpeg.
+      std::lock_guard<std::mutex> guard(m_);
+      use_jpeg_compression_ = true;
+    }
+    if (kJpegMinQualityEncoding <= enc && enc <= kJpegMaxQualityEncoding) {
+      D("jpeg compression level: %d", enc);
+      bb_->set_jpeg_quality_level(enc);
+    }
+    if (enc == kDesktopSizeEncoding) {
+      supports_desktop_size_encoding_ = true;
+    }
+  }
+}
+
+void VncClientConnection::HandleSetPixelFormat() {
+  std::lock_guard<std::mutex> guard(m_);
+  auto msg = client_.Recv(kSetPixelFormatLength);
+  if (msg.size() != kSetPixelFormatLength) {
+    return;
+  }
+  pixel_format_.bits_per_pixel = msg[3];
+  pixel_format_.depth = msg[4];
+  pixel_format_.big_endian = msg[5];
+  pixel_format_.true_color = msg[7];
+  pixel_format_.red_max = uint16_tAt(&msg[8]);
+  pixel_format_.green_max = uint16_tAt(&msg[10]);
+  pixel_format_.blue_max = uint16_tAt(&msg[12]);
+  pixel_format_.red_shift = msg[13];
+  pixel_format_.green_shift = msg[14];
+  pixel_format_.blue_shift = msg[15];
+}
+
+void VncClientConnection::HandlePointerEvent() {
+  auto msg = client_.Recv(kPointerEventLength);
+  if (msg.size() != kPointerEventLength) {
+    return;
+  }
+  std::uint8_t button_mask = msg[0];
+  auto x_pos = uint16_tAt(&msg[1]);
+  auto y_pos = uint16_tAt(&msg[3]);
+  {
+    std::lock_guard<std::mutex> guard(m_);
+    if (current_orientation_ == ScreenOrientation::Landscape) {
+      std::tie(x_pos, y_pos) =
+          std::make_pair(ActualScreenWidth() - y_pos, x_pos);
+    }
+  }
+  virtual_inputs_->HandlePointerEvent(button_mask, x_pos, y_pos);
+}
+
+void VncClientConnection::UpdateAccelerometer(float x, float y, float z) {
+  // Discard the event if we don't have a connection to the HAL.
+  if (!sensor_event_hal_->IsOpen()) {
+    ALOGE("sensor event client not open");
+    return;
+  }
+  timespec current_time{};
+  clock_gettime(CLOCK_MONOTONIC, &current_time);
+  // Construct the sensor message.
+  gce_sensors_message message{};
+  message.version = sizeof message;
+  message.sensor = avd::sensors_constants::kAccelerometerHandle;
+  message.type = SENSOR_TYPE_ACCELEROMETER;
+  message.timestamp = current_time.tv_sec * static_cast<int64_t>(1000000000) +
+                      current_time.tv_nsec;
+  message.data[0] = x;
+  message.data[1] = y;
+  message.data[2] = z;
+
+  std::array<iovec, 1> msg_iov{};
+  msg_iov[0].iov_base = &message;
+  msg_iov[0].iov_len = sizeof(sensors_event_t);
+
+  msghdr msg;
+  msg.msg_name = nullptr;
+  msg.msg_namelen = 0;
+  msg.msg_iov = msg_iov.data();
+  msg.msg_iovlen = msg_iov.size();
+  msg.msg_control = nullptr;
+  msg.msg_controllen = 0;
+  msg.msg_flags = 0;
+  if (sensor_event_hal_->SendMsg(&msg, 0) == -1) {
+    ALOGE("%s: Could not send sensor data. (%s).", __FUNCTION__,
+          sensor_event_hal_->StrError());
+  }
+}
+
+VncClientConnection::Coordinates VncClientConnection::CoordinatesForOrientation(
+    ScreenOrientation orientation) const {
+  // Compute the acceleration vector that we need to send to mimic
+  // this change.
+  constexpr float g = 9.81;
+  constexpr float angle = 20.0;
+  const float cos_angle = std::cos(angle / M_PI);
+  const float sin_angle = std::sin(angle / M_PI);
+  const float z = g * sin_angle;
+  switch (orientation) {
+    case ScreenOrientation::Portrait:
+      return {0, g * cos_angle, z};
+    case ScreenOrientation::Landscape:
+      return {g * cos_angle, 0, z};
+  }
+}
+
+int VncClientConnection::ScreenWidth() const {
+  return current_orientation_ == ScreenOrientation::Portrait
+             ? ActualScreenWidth()
+             : ActualScreenHeight();
+}
+
+int VncClientConnection::ScreenHeight() const {
+  return current_orientation_ == ScreenOrientation::Portrait
+             ? ActualScreenHeight()
+             : ActualScreenWidth();
+}
+
+void VncClientConnection::SetScreenOrientation(ScreenOrientation orientation) {
+  std::lock_guard<std::mutex> guard(m_);
+  auto coords = CoordinatesForOrientation(orientation);
+  UpdateAccelerometer(coords.x, coords.y, coords.z);
+  if (supports_desktop_size_encoding_) {
+    auto previous_orientation = current_orientation_;
+    current_orientation_ = orientation;
+    if (current_orientation_ != previous_orientation &&
+        supports_desktop_size_encoding_) {
+      SendDesktopSizeUpdate();
+      bb_->SetOrientation(this, current_orientation_);
+      // TODO not sure if I should be sending a frame update along with this,
+      // or just letting the next FBUR handle it. This seems to me like it's
+      // sending one more frame buffer update than was requested, which is
+      // maybe a violation of the spec?
+    }
+  }
+}
+
+bool VncClientConnection::RotateIfIsRotationCommand(std::uint32_t key) {
+  // Due to different configurations on different platforms we're supporting
+  // a set of options for rotating the screen. These are similar to what
+  // the emulator supports and has supported.
+  // ctrl+left and ctrl+right work on windows and linux
+  // command+left and command+right work on Mac
+  // ctrl+fn+F11 and ctrl+fn+F12 work when chromoting to ubuntu from a Mac
+  if (!control_key_down_ && !meta_key_down_) {
+    return false;
+  }
+  switch (key) {
+    case avd::xk::Right:
+    case avd::xk::F12:
+      D("switching to portrait");
+      SetScreenOrientation(ScreenOrientation::Portrait);
+      break;
+    case avd::xk::Left:
+    case avd::xk::F11:
+      D("switching to landscape");
+      SetScreenOrientation(ScreenOrientation::Landscape);
+      break;
+    default:
+      return false;
+  }
+  return true;
+}
+
+void VncClientConnection::HandleKeyEvent() {
+  auto msg = client_.Recv(kKeyEventLength);
+  if (msg.size() != kKeyEventLength) {
+    return;
+  }
+
+  auto key = uint32_tAt(&msg[3]);
+  bool key_down = msg[0];
+  switch (key) {
+    case avd::xk::ControlLeft:
+    case avd::xk::ControlRight:
+      control_key_down_ = key_down;
+      break;
+    case avd::xk::MetaLeft:
+    case avd::xk::MetaRight:
+      meta_key_down_ = key_down;
+      break;
+    case avd::xk::F5:
+      key = avd::xk::Menu;
+      break;
+    case avd::xk::F7:
+      virtual_inputs_->PressPowerButton(key_down);
+      return;
+    default:
+      break;
+  }
+
+  if (RotateIfIsRotationCommand(key)) {
+    return;
+  }
+
+  virtual_inputs_->GenerateKeyPressEvent(key, key_down);
+}
+
+void VncClientConnection::HandleClientCutText() {
+  auto msg = client_.Recv(kClientCutTextLength);
+  if (msg.size() != kClientCutTextLength) {
+    return;
+  }
+  auto len = uint32_tAt(&msg[3]);
+  client_.Recv(len);
+}
+
+void VncClientConnection::NormalSession() {
+  static constexpr std::uint8_t kSetPixelFormatMessage{0};
+  static constexpr std::uint8_t kSetEncodingsMessage{2};
+  static constexpr std::uint8_t kFramebufferUpdateRequestMessage{3};
+  static constexpr std::uint8_t kKeyEventMessage{4};
+  static constexpr std::uint8_t kPointerEventMessage{5};
+  static constexpr std::uint8_t kClientCutTextMessage{6};
+  while (true) {
+    if (client_.closed()) {
+      return;
+    }
+    auto msg = client_.Recv(1);
+    if (client_.closed()) {
+      return;
+    }
+    auto msg_type = msg.front();
+    D("Received message type %d\n", static_cast<int>(msg_type));
+
+    switch (msg_type) {
+      case kSetPixelFormatMessage:
+        HandleSetPixelFormat();
+        break;
+
+      case kSetEncodingsMessage:
+        HandleSetEncodings();
+        break;
+
+      case kFramebufferUpdateRequestMessage:
+        HandleFramebufferUpdateRequest();
+        break;
+
+      case kKeyEventMessage:
+        HandleKeyEvent();
+        break;
+
+      case kPointerEventMessage:
+        HandlePointerEvent();
+        break;
+
+      case kClientCutTextMessage:
+        HandleClientCutText();
+        break;
+
+      default:
+        ALOGW("message type not handled: %d", static_cast<int>(msg_type));
+        break;
+    }
+  }
+}
diff --git a/guest/frontend/vnc_server/vnc_client_connection.h b/guest/frontend/vnc_server/vnc_client_connection.h
new file mode 100644
index 0000000..09f7c94
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_client_connection.h
@@ -0,0 +1,158 @@
+#ifndef DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_VNC_CLIENT_CONNECTION_H_
+#define DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_VNC_CLIENT_CONNECTION_H_
+
+#include "blackboard.h"
+#include "virtual_inputs.h"
+#include "vnc_utils.h"
+#include "tcp_socket.h"
+
+#include <android-base/thread_annotations.h>
+#include <SharedFD.h>
+
+#include <cstdint>
+#include <memory>
+#include <mutex>
+#include <thread>
+#include <vector>
+
+namespace avd {
+namespace vnc {
+
+class VncClientConnection {
+ public:
+  VncClientConnection(ClientSocket client, VirtualInputs* virtual_inputs,
+                      BlackBoard* bb, bool aggressive);
+  VncClientConnection(const VncClientConnection&) = delete;
+  VncClientConnection& operator=(const VncClientConnection&) = delete;
+  ~VncClientConnection();
+
+  void StartSession();
+
+ private:
+  struct PixelFormat {
+    std::uint8_t bits_per_pixel;
+    std::uint8_t depth;
+    std::uint8_t big_endian;
+    std::uint8_t true_color;
+    std::uint16_t red_max;
+    std::uint16_t green_max;
+    std::uint16_t blue_max;
+    std::uint8_t red_shift;
+    std::uint8_t green_shift;
+    std::uint8_t blue_shift;
+  };
+
+  struct FrameBufferUpdateRequest {
+    bool incremental;
+    std::uint16_t x_pos;
+    std::uint16_t y_pos;
+    std::uint16_t width;
+    std::uint16_t height;
+  };
+
+  friend bool operator==(const FrameBufferUpdateRequest&,
+                         const FrameBufferUpdateRequest&);
+  friend bool operator!=(const FrameBufferUpdateRequest&,
+                         const FrameBufferUpdateRequest&);
+
+  bool closed();
+  void SetupProtocol();
+  void SetupSecurityType();
+
+  void GetClientInit();
+
+  void SendServerInit() EXCLUDES(m_);
+  static Message MakeFrameBufferUpdateHeader(std::uint16_t num_stripes);
+
+  static void AppendRawStripeHeader(Message* frame_buffer_update,
+                                    const Stripe& stripe);
+  void AppendRawStripe(Message* frame_buffer_update, const Stripe& stripe) const
+      REQUIRES(m_);
+  Message MakeRawFrameBufferUpdate(const StripePtrVec& stripes) const
+      REQUIRES(m_);
+
+  static void AppendJpegSize(Message* frame_buffer_update, size_t jpeg_size);
+  static void AppendJpegStripeHeader(Message* frame_buffer_update,
+                                     const Stripe& stripe);
+  static void AppendJpegStripe(Message* frame_buffer_update,
+                               const Stripe& stripe);
+  static Message MakeJpegFrameBufferUpdate(const StripePtrVec& stripes);
+
+  Message MakeFrameBufferUpdate(const StripePtrVec& frame) REQUIRES(m_);
+
+  void FrameBufferUpdateRequestHandler(bool aggressive) EXCLUDES(m_);
+
+  void SendDesktopSizeUpdate() REQUIRES(m_);
+
+  bool IsUrgent(const FrameBufferUpdateRequest& update_request) const;
+  static StripeSeqNumber MostRecentStripeSeqNumber(const StripePtrVec& stripes);
+
+  void HandleFramebufferUpdateRequest() EXCLUDES(m_);
+
+  void HandleSetEncodings();
+
+  void HandleSetPixelFormat();
+
+  void HandlePointerEvent() EXCLUDES(m_);
+
+  void UpdateAccelerometer(float x, float y, float z);
+
+  struct Coordinates {
+    float x;
+    float y;
+    float z;
+  };
+
+  Coordinates CoordinatesForOrientation(ScreenOrientation orientation) const;
+
+  int ScreenWidth() const REQUIRES(m_);
+
+  int ScreenHeight() const REQUIRES(m_);
+
+  void SetScreenOrientation(ScreenOrientation orientation) EXCLUDES(m_);
+
+  // Returns true if key is special and the screen was rotated.
+  bool RotateIfIsRotationCommand(std::uint32_t key);
+
+  void HandleKeyEvent();
+
+  void HandleClientCutText();
+
+  void NormalSession();
+
+  mutable std::mutex m_;
+  ClientSocket client_;
+  avd::SharedFD sensor_event_hal_;
+  bool control_key_down_ = false;
+  bool meta_key_down_ = false;
+  VirtualInputs* virtual_inputs_{};
+
+  FrameBufferUpdateRequest previous_update_request_{};
+  BlackBoard* bb_;
+  bool use_jpeg_compression_ GUARDED_BY(m_) = false;
+
+  std::thread frame_buffer_request_handler_tid_;
+  bool closed_ GUARDED_BY(m_){};
+
+  PixelFormat pixel_format_ GUARDED_BY(m_) = {
+      std::uint8_t{32},  // bits per pixel
+      std::uint8_t{8},   // depth
+      std::uint8_t{},    // big_endian
+      std::uint8_t{},    // true_color
+      std::uint16_t{},   // red_max, (maxes not used when true color flag is 0)
+      std::uint16_t{},   // green_max
+      std::uint16_t{},   // blue_max
+      std::uint8_t{},  // red_shift (shifts not used when true color flag is 0)
+      std::uint8_t{},  // green_shift
+      std::uint8_t{},  // blue_shift
+  };
+
+  bool supports_desktop_size_encoding_ = false;
+  ScreenOrientation current_orientation_ GUARDED_BY(m_) =
+      ScreenOrientation::Portrait;
+};
+
+}  // namespace vnc
+}  // namespace avd
+
+#endif
diff --git a/guest/frontend/vnc_server/vnc_server.cpp b/guest/frontend/vnc_server/vnc_server.cpp
new file mode 100644
index 0000000..50d2a02
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_server.cpp
@@ -0,0 +1,36 @@
+#include "blackboard.h"
+#include "frame_buffer_watcher.h"
+#include "jpeg_compressor.h"
+#include "tcp_socket.h"
+#include "virtual_inputs.h"
+#include "vnc_client_connection.h"
+#include "vnc_server.h"
+#include "vnc_utils.h"
+
+using avd::vnc::VncServer;
+
+VncServer::VncServer(int port, bool aggressive)
+    : server_(port), frame_buffer_watcher_{&bb_}, aggressive_{aggressive} {}
+
+void VncServer::MainLoop() {
+  while (true) {
+    auto connection = server_.Accept();
+    StartClient(std::move(connection));
+  }
+}
+
+void VncServer::StartClient(ClientSocket sock) {
+  std::thread t(&VncServer::StartClientThread, this, std::move(sock));
+  t.detach();
+}
+
+void VncServer::StartClientThread(ClientSocket sock) {
+  // NOTE if VncServer is expected to be destroyed, we have a problem here.
+  // All of the client threads will be pointing to the VncServer's
+  // data members. In the current setup, if the VncServer is destroyed with
+  // clients still running, the clients will all be left with dangling
+  // pointers.
+  VncClientConnection client(std::move(sock), &virtual_inputs_, &bb_,
+                             aggressive_);
+  client.StartSession();
+}
diff --git a/guest/frontend/vnc_server/vnc_server.h b/guest/frontend/vnc_server/vnc_server.h
new file mode 100644
index 0000000..f80f692
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_server.h
@@ -0,0 +1,43 @@
+#ifndef DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_VNC_SERVER_H_
+#define DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_VNC_SERVER_H_
+
+#include "blackboard.h"
+#include "frame_buffer_watcher.h"
+#include "jpeg_compressor.h"
+#include "tcp_socket.h"
+#include "virtual_inputs.h"
+#include "vnc_client_connection.h"
+#include "vnc_utils.h"
+
+#include <thread>
+#include <string>
+#include <utility>
+
+namespace avd {
+namespace vnc {
+
+class VncServer {
+ public:
+  explicit VncServer(int port, bool aggressive);
+
+  VncServer(const VncServer&) = delete;
+  VncServer& operator=(const VncServer&) = delete;
+
+  [[noreturn]] void MainLoop();
+
+ private:
+  void StartClient(ClientSocket sock);
+
+  void StartClientThread(ClientSocket sock);
+
+  ServerSocket server_;
+  VirtualInputs virtual_inputs_;
+  BlackBoard bb_;
+  FrameBufferWatcher frame_buffer_watcher_;
+  bool aggressive_{};
+};
+
+}  // namespace vnc
+}  // namespace avd
+
+#endif
diff --git a/guest/frontend/vnc_server/vnc_utils.h b/guest/frontend/vnc_server/vnc_utils.h
new file mode 100644
index 0000000..54cf159
--- /dev/null
+++ b/guest/frontend/vnc_server/vnc_utils.h
@@ -0,0 +1,82 @@
+#ifndef DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_VNC_UTILS_H_
+#define DEVICE_GOOGLE_GCE_GCE_UTILS_GCE_VNC_SERVER_VNC_UTILS_H_
+
+#include <GceFrameBuffer.h>
+
+#include <vector>
+#include <array>
+#include <utility>
+#include <cstdint>
+
+#undef D
+#ifdef GCE_VNC_DEBUG
+#define D(...) ALOGD(__VA_ARGS__)
+#else
+#define D(...) ((void)0)
+#endif
+
+namespace avd {
+namespace vnc {
+
+// TODO(haining) when the hwcomposer gives a sequence number type, use that
+// instead. It might just work to replace this class with a type alias
+// using StripeSeqNumber = whatever_the_hwcomposer_uses;
+class StripeSeqNumber {
+ public:
+  StripeSeqNumber() = default;
+  explicit StripeSeqNumber(std::uint64_t t) : t_{t} {}
+  bool operator<(const StripeSeqNumber& other) const {
+    return t_ < other.t_;
+  }
+
+  bool operator<=(const StripeSeqNumber& other) const {
+    return t_ <= other.t_;
+  }
+
+ private:
+  std::uint64_t t_{};
+};
+
+using Message = std::vector<std::uint8_t>;
+
+constexpr int32_t kJpegMaxQualityEncoding = -23;
+constexpr int32_t kJpegMinQualityEncoding = -32;
+
+enum class ScreenOrientation { Portrait, Landscape };
+constexpr int kNumOrientations = 2;
+
+struct Stripe {
+  int index = -1;
+  std::uint64_t frame_id{};
+  std::uint16_t x{};
+  std::uint16_t y{};
+  std::uint16_t width{};
+  std::uint16_t height{};
+  Message raw_data{};
+  Message jpeg_data{};
+  StripeSeqNumber seq_number{};
+  ScreenOrientation orientation{};
+};
+
+inline constexpr int BytesPerPixel() {
+  return sizeof(GceFrameBuffer::Pixel);
+}
+
+// The width of the screen regardless of orientation. Does not change.
+inline int ActualScreenWidth() {
+  return GceFrameBuffer::getInstance().x_res();
+}
+
+// The height of the screen regardless of orientation. Does not change.
+inline int ActualScreenHeight() {
+  return GceFrameBuffer::getInstance().y_res();
+}
+
+inline int ScreenSizeInBytes() {
+  return ActualScreenWidth() * ActualScreenHeight() * BytesPerPixel();
+}
+
+}  // namespace vnc
+}  // namespace avd
+
+#endif
diff --git a/guest/hals/gralloc/legacy/Android.mk b/guest/hals/gralloc/legacy/Android.mk
new file mode 100644
index 0000000..af4de7a
--- /dev/null
+++ b/guest/hals/gralloc/legacy/Android.mk
@@ -0,0 +1,58 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+GCE_GRALLOC_COMMON_SRC_FILES := \
+    gralloc.cpp \
+    framebuffer.cpp \
+    mapper.cpp
+
+GCE_GRALLOC_COMMON_CFLAGS:= \
+    -DLOG_TAG=\"gralloc_gce_x86\" \
+    -Wno-missing-field-initializers \
+    -Wall -Werror \
+    $(GCE_VERSION_CFLAGS)
+
+GCE_GRALLOC_COMMON_C_INCLUDES := \
+    device/google/gce/include
+
+GCE_GRALLOC_COMMON_SHARED_LIBRARIES := \
+    liblog \
+    libutils \
+    libcutils \
+    libgceframebuffer
+
+GCE_GRALLOC_COMMON_STATIC_LIBRARIES := \
+    libgcemetadata
+
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := gralloc.gce_x86
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -ge 21; echo $$?))
+LOCAL_MODULE_RELATIVE_PATH := hw
+else
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+endif
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := $(GCE_GRALLOC_COMMON_SRC_FILES)
+
+LOCAL_CFLAGS := $(GCE_GRALLOC_COMMON_CFLAGS)
+LOCAL_C_INCLUDES := $(GCE_GRALLOC_COMMON_C_INCLUDES)
+LOCAL_SHARED_LIBRARIES := $(GCE_GRALLOC_COMMON_SHARED_LIBRARIES)
+LOCAL_STATIC_LIBRARIES := $(GCE_GRALLOC_COMMON_STATIC_LIBRARIES)
+LOCAL_VENDOR_MODULE := true
+include $(BUILD_SHARED_LIBRARY)
+
diff --git a/guest/hals/gralloc/legacy/framebuffer.cpp b/guest/hals/gralloc/legacy/framebuffer.cpp
new file mode 100644
index 0000000..759a55a
--- /dev/null
+++ b/guest/hals/gralloc/legacy/framebuffer.cpp
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <sys/mman.h>
+
+#include <dlfcn.h>
+
+#include <cutils/ashmem.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+
+#include <sys/system_properties.h>
+
+#include <hardware/hardware.h>
+#include <hardware/gralloc.h>
+
+#include <fcntl.h>
+#include <errno.h>
+#include <sys/ioctl.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <cutils/atomic.h>
+#include <private/android_filesystem_config.h>
+
+#if defined(__ANDROID__)
+#include <linux/fb.h>
+#endif
+
+#include "gralloc_gce_priv.h"
+
+#include <GceFrameBuffer.h>
+#include <GceFrameBufferControl.h>
+#include <RegionRegistry.h>
+#include "AutoResources.h"
+
+/*****************************************************************************/
+
+struct fb_context_t {
+  framebuffer_device_t  device;
+};
+
+/*****************************************************************************/
+
+static int fb_setSwapInterval(struct framebuffer_device_t* dev, int interval) {
+  if (interval < dev->minSwapInterval || interval > dev->maxSwapInterval) {
+    return -EINVAL;
+  }
+  // FIXME: implement fb_setSwapInterval
+  return 0;
+}
+
+/*
+ * These functions (and probably the entire framebuffer device) are most likely
+ * not used when the hardware composer device is present, however is hard to be
+ * 100% sure.
+ */
+static int fb_setUpdateRect(
+    struct framebuffer_device_t* dev __unused, int l, int t, int w, int h) {
+  if (((w|h) <= 0) || ((l|t)<0)) {
+    return -EINVAL;
+  }
+  // TODO(jemoreira): Find a way to broadcast this with the framebuffer control.
+  return 0;
+}
+
+static int fb_post(struct framebuffer_device_t* dev __unused, buffer_handle_t buffer) {
+  const int yoffset = YOffsetFromHandle(buffer);
+  if (yoffset >= 0) {
+    int retval =
+        GceFrameBufferControl::getInstance().BroadcastFrameBufferChanged(
+            yoffset);
+    if (retval) ALOGI("Failed to post framebuffer");
+
+    return retval;
+  }
+  return -1;
+}
+
+/*****************************************************************************/
+
+int initUserspaceFrameBuffer(struct private_module_t* module) {
+  avd::LockGuard<pthread_mutex_t> guard(module->lock);
+  if (module->framebuffer) {
+    return 0;
+  }
+
+  int fd;
+  if (!GceFrameBuffer::OpenFrameBuffer(&fd)) {
+    return -errno;
+  }
+
+  const GceFrameBuffer& config = GceFrameBuffer::getInstance();
+
+  /*
+   * map the framebuffer
+   */
+  module->framebuffer =
+      new private_handle_t(fd,
+                           config.total_buffer_size(),
+                           config.hal_format(),
+                           config.x_res(),
+                           config.y_res(),
+                           config.line_length() / (config.bits_per_pixel() / 8),
+                           private_handle_t::PRIV_FLAGS_FRAMEBUFFER);
+  reference_region("framebuffer_init", module->framebuffer);
+
+  return 0;
+}
+
+
+/*****************************************************************************/
+
+static int fb_close(struct hw_device_t *dev) {
+  fb_context_t* ctx = (fb_context_t*)dev;
+  if (ctx) {
+    free(ctx);
+  }
+  return 0;
+}
+
+int fb_device_open(
+    hw_module_t const* module, const char* name, hw_device_t** device) {
+  int status = -EINVAL;
+  if (!strcmp(name, GRALLOC_HARDWARE_FB0)) {
+    /* initialize our state here */
+    fb_context_t* dev = (fb_context_t*) malloc(sizeof(*dev));
+    LOG_FATAL_IF(!dev, "%s: malloc returned NULL.", __FUNCTION__);
+    memset(dev, 0, sizeof(*dev));
+
+    /* initialize the procs */
+    dev->device.common.tag = HARDWARE_DEVICE_TAG;
+    dev->device.common.version = 0;
+    dev->device.common.module = const_cast<hw_module_t*>(module);
+    dev->device.common.close = fb_close;
+    dev->device.setSwapInterval = fb_setSwapInterval;
+    dev->device.post            = fb_post;
+    dev->device.setUpdateRect   = fb_setUpdateRect;
+
+    private_module_t* m = (private_module_t*)module;
+
+    status = initUserspaceFrameBuffer(m);
+
+    const GceFrameBuffer& config = GceFrameBuffer::getInstance();
+
+    if (status >= 0) {
+      int stride = config.line_length() / (config.bits_per_pixel() / 8);
+      int format = config.hal_format();
+      const_cast<uint32_t&>(dev->device.flags) = 0;
+      const_cast<uint32_t&>(dev->device.width) = config.x_res();
+      const_cast<uint32_t&>(dev->device.height) = config.y_res();
+      const_cast<int&>(dev->device.stride) = stride;
+      const_cast<int&>(dev->device.format) = format;
+      const_cast<float&>(dev->device.xdpi) = config.dpi();
+      const_cast<float&>(dev->device.ydpi) = config.dpi();
+      // TODO (jemoreira): DRY!! Managed by the vsync thread in the hwcomposer
+      const_cast<float&>(dev->device.fps) = (60 * 1000) / 1000.0f;
+      const_cast<int&>(dev->device.minSwapInterval) = 1;
+      const_cast<int&>(dev->device.maxSwapInterval) = 1;
+      *device = &dev->device.common;
+    }
+  }
+  return status;
+}
diff --git a/guest/hals/gralloc/legacy/gralloc.cpp b/guest/hals/gralloc/legacy/gralloc.cpp
new file mode 100644
index 0000000..8b8e652
--- /dev/null
+++ b/guest/hals/gralloc/legacy/gralloc.cpp
@@ -0,0 +1,304 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <limits.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/ioctl.h>
+
+#include <cutils/ashmem.h>
+#include <cutils/log.h>
+#include <cutils/atomic.h>
+#include <utils/String8.h>
+
+#include <hardware/hardware.h>
+#include <hardware/gralloc.h>
+
+#include <api_level_fixes.h>
+
+#include "GceFrameBuffer.h"
+#include "GceFrameBufferControl.h"
+#include "RegionRegistry.h"
+#include "gralloc_gce_priv.h"
+#include "AutoResources.h"
+
+/*****************************************************************************/
+
+static int gralloc_alloc_buffer(
+    alloc_device_t* /*dev*/, int format, int w, int h,
+    buffer_handle_t* pHandle, int* pStrideInPixels) {
+  int err = 0;
+  int fd = -1;
+  static int sequence = 0;
+
+  int bytes_per_pixel = formatToBytesPerPixel(format);
+  int bytes_per_line;
+  int stride_in_pixels;
+  int size = 0;
+  // SwiftShader can't handle RGB_888, so fail fast and hard if we try to create
+  // a gralloc buffer in this format.
+  ALOG_ASSERT(format != HAL_PIXEL_FORMAT_RGB_888);
+  if (format == HAL_PIXEL_FORMAT_YV12) {
+    bytes_per_line = GceFrameBuffer::align(bytes_per_pixel * w, 16);
+  } else {
+    bytes_per_line = GceFrameBuffer::align(bytes_per_pixel * w);
+  }
+  size = roundUpToPageSize(size + formatToBytesPerFrame(format, w, h));
+  size += PAGE_SIZE;
+  fd = ashmem_create_region(
+      android::String8::format(
+          "gralloc-%d.%d", getpid(), sequence++).string(),
+      size);
+  if (fd < 0) {
+    ALOGE("couldn't create ashmem (%s)", strerror(-errno));
+    err = -errno;
+  }
+
+  if (err == 0) {
+    stride_in_pixels = bytes_per_line / bytes_per_pixel;
+    private_handle_t* hnd =
+        new private_handle_t(fd, size, format, w, h, stride_in_pixels, 0);
+    void* base = reference_region(__FUNCTION__, hnd);
+    if (base) {
+      *pHandle = hnd;
+      *pStrideInPixels = stride_in_pixels;
+    } else {
+      err = -EIO;
+    }
+  }
+
+  ALOGE_IF(err, "gralloc failed err=%s", strerror(-err));
+
+  return err;
+}
+
+/*****************************************************************************/
+
+static int ensure_framebuffer_allocated(private_module_t* m) {
+  // allocate the framebuffer
+  if (m->framebuffer == NULL) {
+    // The framebuffer is mapped once and forever.
+    int err = initUserspaceFrameBuffer(m);
+    if (err < 0) {
+      ALOGE("Failed to map framebuffer (%d)", errno);
+      return err;
+    }
+  }
+  return 0;
+}
+
+static int gralloc_free_framebuffer(private_handle_t const * hnd) {
+    static const GceFrameBuffer& config = GceFrameBuffer::getInstance();
+    static GceFrameBufferControl& fb_control =
+        GceFrameBufferControl::getInstance();
+
+    // free this buffer
+    const size_t bufferSize = config.line_length() * config.y_res();
+    int index = hnd->frame_offset / bufferSize;
+    return fb_control.UnsetBufferBits(1LU << index);
+}
+
+// Allocates a framebuffer taken from the set of buffers given by buffer_mask.
+static int gralloc_alloc_framebuffer(alloc_device_t* dev,
+                                     buffer_handle_t* pHandle,
+                                     int* pStrideInPixels,
+                                     uint32_t buffer_mask) {
+  static const GceFrameBuffer& config = GceFrameBuffer::getInstance();
+  static GceFrameBufferControl& fb_control =
+      GceFrameBufferControl::getInstance();
+
+  private_module_t* m = reinterpret_cast<private_module_t*>(
+      dev->common.module);
+
+  ensure_framebuffer_allocated(m);
+
+  const uint32_t numBuffers = GceFrameBuffer::kNumBuffers;
+  const size_t bufferSize = config.bufferSize();
+
+  // Paranoia: Force the mask to be valid
+  buffer_mask &= (1LU << numBuffers) - 1;
+
+  uint32_t bit = fb_control.GetAndSetNextAvailableBufferBit(buffer_mask);
+  if (!bit) {
+    // All buffers in the mask have been allocated already or there was another
+    // error
+    return -ENOMEM;
+  }
+
+  int frame_offset = 0;
+  while (bit != 1LU) {
+    bit >>= 1;
+    frame_offset += bufferSize;
+  }
+
+  int stride_in_pixels = config.line_length() / (config.bits_per_pixel() / 8);
+
+  // create a "fake" handle for it
+  private_handle_t* hnd = new private_handle_t(
+      dup(m->framebuffer->fd), config.total_buffer_size(),
+      config.hal_format(), config.x_res(), config.y_res(),
+      stride_in_pixels, private_handle_t::PRIV_FLAGS_FRAMEBUFFER, frame_offset);
+
+  *pHandle = hnd;
+  if (pStrideInPixels) {
+    *pStrideInPixels = stride_in_pixels;
+  }
+
+  return 0;
+}
+
+static int gralloc_alloc_sf_framebuffer(alloc_device_t* dev,
+                                        buffer_handle_t* pHandle,
+                                        int* pStrideInPixels) {
+  uint32_t mask = (1LU << GceFrameBuffer::kNumSfBuffers) - 1LU;
+  // Skip the first buffers since those are for HWC usage
+  mask <<= GceFrameBuffer::kNumHwcBuffers;
+  return gralloc_alloc_framebuffer(dev, pHandle, pStrideInPixels, mask);
+}
+
+static int gralloc_alloc_hwc_framebuffer(alloc_device_t* dev,
+                                         buffer_handle_t* pHandle) {
+  // Use the first kNumHwcBuffers for hwcomposer
+  uint32_t mask = (1LU << GceFrameBuffer::kNumHwcBuffers) - 1LU;
+  return gralloc_alloc_framebuffer(dev, pHandle, NULL, mask);
+}
+
+/*****************************************************************************/
+
+static int gralloc_alloc(
+    alloc_device_t* dev, int w, int h, int format, int usage,
+    buffer_handle_t* pHandle, int* pStrideInPixels) {
+  if (!pHandle || !pStrideInPixels)
+    return -EINVAL;
+
+  int err;
+  if (usage & GRALLOC_USAGE_HW_FB) {
+    err = gralloc_alloc_sf_framebuffer(dev, pHandle, pStrideInPixels);
+  } else {
+    err = gralloc_alloc_buffer(dev, format, w, h, pHandle, pStrideInPixels);
+  }
+
+  if (err < 0) {
+    return err;
+  }
+  return 0;
+}
+
+static int gralloc_free(alloc_device_t* /*dev*/, buffer_handle_t handle) {
+  if (private_handle_t::validate(handle) < 0) {
+    return -EINVAL;
+  }
+
+  int retval = 0;
+
+  private_handle_t const* hnd = reinterpret_cast<private_handle_t const*>(
+    handle);
+  if (hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER) {
+    retval = gralloc_free_framebuffer(hnd);
+  } else {
+    retval = unreference_region(__FUNCTION__, hnd);
+  }
+
+  close(hnd->fd);
+  delete hnd;
+  return retval;
+}
+
+/*****************************************************************************/
+
+static int gralloc_close(struct hw_device_t *dev) {
+  priv_alloc_device_t* ctx = reinterpret_cast<priv_alloc_device_t*>(dev);
+  if (ctx) {
+    /* TODO: keep a list of all buffer_handle_t created, and free them
+     * all here.
+     */
+    free(ctx);
+  }
+  return 0;
+}
+
+static int gralloc_device_open(
+    const hw_module_t* module, const char* name, hw_device_t** device) {
+  int status = -EINVAL;
+  if (!strcmp(name, GRALLOC_HARDWARE_GPU0)) {
+    priv_alloc_device_t *dev;
+    dev = (priv_alloc_device_t*) malloc(sizeof(*dev));
+    LOG_FATAL_IF(!dev, "%s: malloc returned NULL.", __FUNCTION__);
+
+    /* initialize our state here */
+    memset(dev, 0, sizeof(*dev));
+
+    /* initialize the procs */
+    dev->device.common.tag = HARDWARE_DEVICE_TAG;
+    dev->device.common.version = 0;
+    dev->device.common.module = const_cast<hw_module_t*>(module);
+    dev->device.common.close = gralloc_close;
+
+    dev->device.alloc   = gralloc_alloc;
+    dev->device.free    = gralloc_free;
+    dev->alloc_hwc_framebuffer = gralloc_alloc_hwc_framebuffer;
+
+
+    *device = &dev->device.common;
+    status = 0;
+  } else {
+    status = fb_device_open(module, name, device);
+  }
+  return status;
+}
+
+/*****************************************************************************/
+
+static struct hw_module_methods_t gralloc_module_methods = {
+  GCE_STATIC_INITIALIZER(open) gralloc_device_open
+};
+
+struct private_module_t HAL_MODULE_INFO_SYM = {
+  GCE_STATIC_INITIALIZER(base) {
+    GCE_STATIC_INITIALIZER(common) {
+      GCE_STATIC_INITIALIZER(tag) HARDWARE_MODULE_TAG,
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+      GCE_STATIC_INITIALIZER(version_major) GRALLOC_MODULE_API_VERSION_0_2,
+#else
+      GCE_STATIC_INITIALIZER(version_major) 1,
+#endif
+      GCE_STATIC_INITIALIZER(version_minor) 0,
+      GCE_STATIC_INITIALIZER(id) GRALLOC_HARDWARE_MODULE_ID,
+      GCE_STATIC_INITIALIZER(name) "GCE X86 Graphics Memory Allocator Module",
+      GCE_STATIC_INITIALIZER(author) "The Android Open Source Project",
+      GCE_STATIC_INITIALIZER(methods) &gralloc_module_methods,
+      GCE_STATIC_INITIALIZER(dso) NULL,
+      GCE_STATIC_INITIALIZER(reserved) {0},
+    },
+    GCE_STATIC_INITIALIZER(registerBuffer) gralloc_register_buffer,
+    GCE_STATIC_INITIALIZER(unregisterBuffer) gralloc_unregister_buffer,
+    GCE_STATIC_INITIALIZER(lock) gralloc_lock,
+    GCE_STATIC_INITIALIZER(unlock) gralloc_unlock,
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+    GCE_STATIC_INITIALIZER(perform) NULL,
+    GCE_STATIC_INITIALIZER(lock_ycbcr) gralloc_lock_ycbcr,
+#endif
+  },
+  GCE_STATIC_INITIALIZER(framebuffer) 0,
+  GCE_STATIC_INITIALIZER(lock) PTHREAD_MUTEX_INITIALIZER,
+};
diff --git a/guest/hals/gralloc/legacy/gralloc_gce_priv.h b/guest/hals/gralloc/legacy/gralloc_gce_priv.h
new file mode 100644
index 0000000..a194b3e
--- /dev/null
+++ b/guest/hals/gralloc/legacy/gralloc_gce_priv.h
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef GRALLOC_GCE_PRIV_H_
+#define GRALLOC_GCE_PRIV_H_
+
+#include <stdint.h>
+#include <limits.h>
+#include <sys/cdefs.h>
+#include <sys/mman.h>
+#include <hardware/gralloc.h>
+#include <Pthread.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <cutils/native_handle.h>
+#include <cutils/log.h>
+
+#include <linux/fb.h>
+
+#include "GceFrameBuffer.h"
+
+#ifndef GRALLOC_MODULE_API_VERSION_0_2
+// This structure will be defined in later releases of Android. Declare it
+// here to allow us to structure the code well.
+struct android_ycbcr {
+  void* y;
+  void* cb;
+  void* cr;
+  size_t ystride;
+  size_t cstride;
+  size_t chroma_step;
+  uint32_t reserved[8];
+};
+#endif
+
+/*****************************************************************************/
+
+struct private_handle_t;
+
+struct private_module_t {
+  gralloc_module_t base;
+
+  private_handle_t* framebuffer;
+  pthread_mutex_t lock;
+};
+
+int initUserspaceFrameBuffer(struct private_module_t* module);
+
+/*****************************************************************************/
+
+struct priv_alloc_device_t {
+  alloc_device_t  device;
+  // Creates handles for the hwcomposer-specific framebuffers
+  int (*alloc_hwc_framebuffer)(alloc_device_t* m,
+                                buffer_handle_t* handle);
+};
+
+/*****************************************************************************/
+
+struct private_handle_t : public native_handle {
+  enum {
+    PRIV_FLAGS_FRAMEBUFFER = 0x00000001
+  };
+
+  // file-descriptors
+  int     fd;
+  // ints
+  int     magic;
+  int     flags;
+  int     format;
+  int     x_res;
+  int     y_res;
+  int     stride_in_pixels;
+  // Use to indicate which frame we're using.
+  int     frame_offset;
+  int     total_size;
+  int     lock_level;
+
+  static inline int sNumInts() {
+    return (((sizeof(private_handle_t) - sizeof(native_handle_t))/sizeof(int)) - sNumFds);
+  }
+  static const int sNumFds = 1;
+  static const int sMagic = 0x3141592;
+
+  private_handle_t(int fd, int size, int format, int x_res, int y_res,
+                   int stride_in_pixels, int flags, int frame_offset = 0)
+      : fd(fd),
+        magic(sMagic),
+        flags(flags),
+        format(format),
+        x_res(x_res),
+        y_res(y_res),
+        stride_in_pixels(stride_in_pixels),
+        frame_offset(frame_offset),
+        total_size(size),
+        lock_level(0) {
+    version = sizeof(native_handle);
+    numInts = sNumInts();
+    numFds = sNumFds;
+  }
+
+  ~private_handle_t() {
+    magic = 0;
+  }
+
+  static int validate(const native_handle* h) {
+    const private_handle_t* hnd = (const private_handle_t*)h;
+    if (!h || h->version != sizeof(native_handle) ||
+        h->numInts != sNumInts() || h->numFds != sNumFds ||
+        hnd->magic != sMagic) {
+      ALOGE("invalid gralloc handle (at %p)", h);
+      return -EINVAL;
+    }
+    return 0;
+  }
+};
+
+
+static inline int formatToBytesPerPixel(int format) {
+  switch (format) {
+    case HAL_PIXEL_FORMAT_RGBA_8888:
+    case HAL_PIXEL_FORMAT_RGBX_8888:
+    case HAL_PIXEL_FORMAT_BGRA_8888:
+#if GCE_PLATFORM_SDK_AFTER(J)
+    // The camera 3.0 implementation assumes that IMPLEMENTATION_DEFINED
+    // means HAL_PIXEL_FORMAT_RGBA_8888
+    case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+#endif
+      return 4;
+    case HAL_PIXEL_FORMAT_RGB_888:
+      return 3;
+    case HAL_PIXEL_FORMAT_RGB_565:
+    case HAL_PIXEL_FORMAT_YV12:
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+    case HAL_PIXEL_FORMAT_YCbCr_420_888:
+#endif
+      return 2;
+#if GCE_PLATFORM_SDK_AFTER(J)
+    case HAL_PIXEL_FORMAT_BLOB:
+      return 1;
+#endif
+    default:
+      ALOGE("%s: unknown format=%d", __FUNCTION__, format);
+      return 4;
+  }
+}
+
+static inline void formatToYcbcr(
+    int format, int width, int height, void* base_v, android_ycbcr* out) {
+  char* it = static_cast<char*>(base_v);
+  // Clear reserved fields;
+  memset(out, 0, sizeof(*out));
+  switch (format) {
+    case HAL_PIXEL_FORMAT_YV12:
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+    case HAL_PIXEL_FORMAT_YCbCr_420_888:
+#endif
+      out->ystride = GceFrameBuffer::align(width, 16);
+      out->cstride = GceFrameBuffer::align(out->ystride / 2, 16);
+      out->chroma_step = 1;
+      out->y = it;
+      it += out->ystride * height;
+      out->cr = it;
+      it += out->cstride * height / 2;
+      out->cb = it;
+      break;
+    default:
+      ALOGE("%s: can't deal with format=0x%x (%s)",
+            __FUNCTION__, format, pixel_format_to_string(format));
+  }
+}
+
+static inline int formatToBytesPerFrame(int format, int w, int h) {
+  int bytes_per_pixel = formatToBytesPerPixel(format);
+  int w16, h16;
+  int y_size, c_size;
+
+  switch (format) {
+#if GCE_PLATFORM_SDK_AFTER(J)
+    // BLOB is used to allocate buffers for JPEG formatted data. Bytes per pixel
+    // is 1, the desired buffer size is in w, and h should be 1. We refrain from
+    // adding additional padding, although the caller is likely to round
+    // up to a page size.
+    case HAL_PIXEL_FORMAT_BLOB:
+      return bytes_per_pixel * w * h;
+#endif
+    case HAL_PIXEL_FORMAT_YV12:
+#ifdef GRALLOC_MODULE_API_VERSION_0_2
+    case HAL_PIXEL_FORMAT_YCbCr_420_888:
+#endif
+      android_ycbcr strides;
+      formatToYcbcr(format, w, h, NULL, &strides);
+      y_size = strides.ystride * h;
+      c_size = strides.cstride * h / 2;
+      return (y_size + 2 * c_size + GceFrameBuffer::kSwiftShaderPadding);
+    /*case HAL_PIXEL_FORMAT_RGBA_8888:
+    case HAL_PIXEL_FORMAT_RGBX_8888:
+    case HAL_PIXEL_FORMAT_BGRA_8888:
+    case HAL_PIXEL_FORMAT_RGB_888:
+    case HAL_PIXEL_FORMAT_RGB_565:*/
+    default:
+      w16 = GceFrameBuffer::align(w, 16);
+      h16 = GceFrameBuffer::align(h, 16);
+      return bytes_per_pixel * w16 * h16 + GceFrameBuffer::kSwiftShaderPadding;
+  }
+}
+
+// Calculates the yoffset from a framebuffer handle. I checks the given handle
+// for errors first. Returns the yoffset (non negative integer) or -1 if there
+// is an error.
+static inline int YOffsetFromHandle(buffer_handle_t buffer_hnd) {
+    if (!buffer_hnd) {
+    ALOGE("Attempt to post null buffer");
+    return -1;
+  }
+  if (private_handle_t::validate(buffer_hnd) < 0) {
+    ALOGE("Attempt to post non-gce handle");
+    return -1;
+  }
+  const private_handle_t* hnd =
+      reinterpret_cast<private_handle_t const*>(buffer_hnd);
+  if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
+    ALOGE("Attempt to post non-framebuffer");
+    return -1;
+  }
+
+  const GceFrameBuffer& config = GceFrameBuffer::getInstance();
+  return hnd->frame_offset / config.line_length();
+}
+
+int fb_device_open(
+    const hw_module_t* module, const char* name, hw_device_t** device);
+
+int gralloc_lock(
+    gralloc_module_t const* module,
+    buffer_handle_t handle, int usage,
+    int l, int t, int w, int h,
+    void** vaddr);
+
+int gralloc_unlock(
+    gralloc_module_t const* module, buffer_handle_t handle);
+
+int gralloc_register_buffer(
+    gralloc_module_t const* module, buffer_handle_t handle);
+
+int gralloc_unregister_buffer(
+    gralloc_module_t const* module, buffer_handle_t handle);
+
+int gralloc_lock_ycbcr(
+    struct gralloc_module_t const* module,
+    buffer_handle_t handle, int usage,
+    int l, int t, int w, int h,
+    struct android_ycbcr *ycbcr);
+
+#endif /* GRALLOC_GCE_PRIV_H_ */
diff --git a/guest/hals/gralloc/legacy/mapper.cpp b/guest/hals/gralloc/legacy/mapper.cpp
new file mode 100644
index 0000000..a75cdc9
--- /dev/null
+++ b/guest/hals/gralloc/legacy/mapper.cpp
@@ -0,0 +1,126 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <limits.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cutils/hashmap.h>
+#include <cutils/log.h>
+#include <cutils/atomic.h>
+
+#include <hardware/hardware.h>
+#include <hardware/gralloc.h>
+#include <system/graphics.h>
+
+#include "RegionRegistry.h"
+
+#include "gralloc_gce_priv.h"
+#include "remoter_framework_pkt.h"
+
+#define DEBUG_REFERENCES 1
+#define DEBUG_MAX_LOCK_LEVEL 20
+
+/*****************************************************************************/
+
+int gralloc_register_buffer(gralloc_module_t const* /*module*/,
+                            buffer_handle_t handle) {
+  if (private_handle_t::validate(handle) < 0) {
+    return -EINVAL;
+  }
+
+  private_handle_t* hnd = (private_handle_t*)handle;
+  if (reference_region(__FUNCTION__, hnd)) {
+    return 0;
+  } else {
+    return -EIO;
+  }
+}
+
+int gralloc_unregister_buffer(gralloc_module_t const* /*module*/,
+                              buffer_handle_t handle) {
+  if (private_handle_t::validate(handle) < 0) {
+    return -EINVAL;
+  }
+  private_handle_t* hnd = (private_handle_t*)handle;
+  return unreference_region("gralloc_unregister_buffer", hnd);
+}
+
+int gralloc_lock(
+    gralloc_module_t const* /*module*/, buffer_handle_t handle, int /*usage*/,
+    int /*l*/, int /*t*/, int /*w*/, int /*h*/,
+    void** vaddr) {
+  if (private_handle_t::validate(handle) < 0) {
+    return -EINVAL;
+  }
+  if (!vaddr) {
+    return -EINVAL;
+  }
+  private_handle_t* hnd = (private_handle_t*)handle;
+#if DEBUG_REFERENCES
+  if (hnd->lock_level > DEBUG_MAX_LOCK_LEVEL) {
+    LOG_FATAL("%s: unbalanced lock detected. lock level = %d",
+              __FUNCTION__, hnd->lock_level);
+  }
+  ++hnd->lock_level;
+#endif
+  void* base = reference_region("gralloc_lock", hnd);
+  *vaddr = reinterpret_cast<unsigned char*>(base)
+      + hnd->frame_offset;
+  return 0;
+}
+
+int gralloc_unlock(
+    gralloc_module_t const* /*module*/, buffer_handle_t handle) {
+  if (private_handle_t::validate(handle) < 0) {
+    return -EINVAL;
+  }
+  private_handle_t* hnd = (private_handle_t*) handle;
+#if DEBUG_REFERENCES
+  if (hnd->lock_level <= 0) {
+    LOG_FATAL("%s unbalanced unlock detected. lock level = %d",
+              __FUNCTION__, hnd->lock_level);
+  }
+  --hnd->lock_level;
+#endif
+  unreference_region("gralloc_unlock", hnd);
+  return 0;
+}
+
+int gralloc_lock_ycbcr(
+    gralloc_module_t const* /*module*/, buffer_handle_t handle, int /*usage*/,
+    int /*l*/, int /*t*/, int /*w*/, int /*h*/,
+    struct android_ycbcr* ycbcr) {
+  if (private_handle_t::validate(handle) < 0) {
+    return -EINVAL;
+  }
+  private_handle_t* hnd = (private_handle_t*)handle;
+#if DEBUG_REFERENCES
+  if (hnd->lock_level > DEBUG_MAX_LOCK_LEVEL) {
+    LOG_FATAL("%s: unbalanced lock detected. lock level = %d",
+              __FUNCTION__, hnd->lock_level);
+  }
+  ++hnd->lock_level;
+#endif
+  void* base = reference_region("gralloc_lock_ycbcr", hnd);
+  formatToYcbcr(hnd->format, hnd->x_res, hnd->y_res, base, ycbcr);
+  return 0;
+}
diff --git a/guest/hals/hwcomposer/legacy/Android.mk b/guest/hals/hwcomposer/legacy/Android.mk
new file mode 100644
index 0000000..713d055
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/Android.mk
@@ -0,0 +1,75 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+# HAL module implemenation stored in
+# hw/<OVERLAY_HARDWARE_MODULE_ID>.<ro.product.board>.so
+
+# Old hwcomposer, relies on GLES composition
+include $(CLEAR_VARS)
+include $(LOCAL_PATH)/hwcomposer.mk
+LOCAL_CFLAGS += -DUSE_OLD_HWCOMPOSER
+LOCAL_MODULE := hwcomposer.gce_x86-deprecated
+include $(BUILD_SHARED_LIBRARY)
+
+# New hwcomposer, performs software composition
+include $(CLEAR_VARS)
+include $(LOCAL_PATH)/hwcomposer.mk
+LOCAL_MODULE := hwcomposer.gce_x86
+LOCAL_VENDOR_MODULE := true
+include $(BUILD_SHARED_LIBRARY)
+
+# An executable to run some tests
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := hwc_tests.gce_x86
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := \
+    libgceframebuffer \
+    liblog \
+    libcutils \
+    libutils \
+    libsync \
+    libhardware \
+    libjpeg \
+    $(GCE_STLPORT_LIBS)
+
+LOCAL_STATIC_LIBRARIES := \
+    libgcemetadata \
+    libyuv_static
+
+LOCAL_SRC_FILES := \
+    hwc_tests.cpp \
+    gce_composer.cpp \
+    base_composer.cpp \
+    geometry_utils.cpp
+
+
+LOCAL_CFLAGS += \
+    -DLOG_TAG=\"hwc_tests\" \
+    $(GCE_VERSION_CFLAGS)
+
+LOCAL_C_INCLUDES := \
+    device/google/gce/hwcomposer \
+    device/google/gce/include \
+    bionic \
+    $(GCE_STLPORT_INCLUDES)
+
+include device/google/gce/libs/base/libbase.mk
+LOCAL_SHARED_LIBRARIES += $(GCE_LIBBASE_LIB_NAME)
+LOCAL_C_INCLUDES += $(GCE_LIBBASE_INCLUDE_DIR)
+
+include $(BUILD_EXECUTABLE)
diff --git a/guest/hals/hwcomposer/legacy/base_composer.cpp b/guest/hals/hwcomposer/legacy/base_composer.cpp
new file mode 100644
index 0000000..dd23076
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/base_composer.cpp
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "base_composer.h"
+
+#include <cutils/log.h>
+#include "GceFrameBufferControl.h"
+#include "gralloc_gce_priv.h"
+
+namespace avd {
+
+namespace {
+
+int BroadcastFrameBufferChanged (int yoffset) {
+  return GceFrameBufferControl::getInstance().BroadcastFrameBufferChanged(
+      yoffset);
+}
+
+}
+
+BaseComposer::BaseComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns)
+    : fb_broadcaster_(BroadcastFrameBufferChanged),
+      vsync_base_timestamp_(vsync_base_timestamp),
+      vsync_period_ns_(vsync_period_ns) {
+  const gralloc_module_t* gralloc_module;
+  hw_get_module(GRALLOC_HARDWARE_MODULE_ID,
+                reinterpret_cast<const hw_module_t**>(&gralloc_module));
+}
+
+BaseComposer::~BaseComposer() {}
+
+FbBroadcaster BaseComposer::ReplaceFbBroadcaster(
+    FbBroadcaster fb_broadcaster) {
+  FbBroadcaster tmp = fb_broadcaster_;
+  fb_broadcaster_ = fb_broadcaster;
+  return tmp;
+}
+
+void BaseComposer::Dump(char* buff __unused, int buff_len __unused) {}
+
+
+int BaseComposer::PostFrameBuffer(buffer_handle_t buffer) {
+  const int yoffset = YOffsetFromHandle(buffer);
+  // If the broadcaster is NULL or could not get a good yoffset just ignore it.
+  if (fb_broadcaster_ && yoffset >= 0) {
+    int retval = fb_broadcaster_(yoffset);
+    if (retval){
+      ALOGI("Failed to post framebuffer");
+      return -1;
+    }
+  }
+
+  return yoffset;
+}
+
+int BaseComposer::PrepareLayers(size_t num_layers, gce_hwc_layer* layers) {
+  // find unsupported overlays
+  for (size_t i = 0; i < num_layers; i++) {
+    if (IS_TARGET_FRAMEBUFFER(layers[i].compositionType)) {
+      continue;
+    }
+    layers[i].compositionType = HWC_FRAMEBUFFER;
+  }
+  return 0;
+}
+
+int BaseComposer::SetLayers(size_t num_layers, gce_hwc_layer* layers) {
+  for (size_t idx = 0; idx < num_layers; idx++) {
+    if (IS_TARGET_FRAMEBUFFER(layers[idx].compositionType)) {
+      return PostFrameBuffer(layers[idx].handle);
+    }
+  }
+  return -1;
+}
+
+}  // namespace avd
diff --git a/guest/hals/hwcomposer/legacy/base_composer.h b/guest/hals/hwcomposer/legacy/base_composer.h
new file mode 100644
index 0000000..8a17b6a
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/base_composer.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GCE_OLD_HWCOMPOSER_GCE_COMPOSER_H
+#define GCE_OLD_HWCOMPOSER_GCE_COMPOSER_H
+
+#include <hardware/gralloc.h>
+#include "hwcomposer_common.h"
+
+namespace avd {
+
+typedef int (*FbBroadcaster)(int);
+
+class BaseComposer {
+ public:
+  BaseComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns);
+  ~BaseComposer();
+
+  // Sets the composition type of each layer and returns the number of layers
+  // to be composited by the hwcomposer.
+  int PrepareLayers(size_t num_layers, gce_hwc_layer* layers);
+  // Returns the yoffset that was broadcasted or a negative number if there was
+  // an error.
+  int SetLayers(size_t num_layers, gce_hwc_layer* layers);
+  // Returns yoffset of the handle or negative on error.
+  int PostFrameBuffer(buffer_handle_t handle);
+  // Changes the broadcaster, gives the ability to report more than just the
+  // yoffset by using a wrapper like the StatsKeepingComposer. Returns the old
+  // broadcaster. Passing a NULL pointer will cause the composer to not
+  // broadcast at all.
+  FbBroadcaster ReplaceFbBroadcaster(FbBroadcaster);
+  void Dump(char* buff, int buff_len);
+ protected:
+  int64_t vsync_base_timestamp_;
+  int32_t vsync_period_ns_;
+ private:
+  FbBroadcaster fb_broadcaster_;
+};
+
+}  // namespace avd
+
+#endif
diff --git a/guest/hals/hwcomposer/legacy/gce_composer.cpp b/guest/hals/hwcomposer/legacy/gce_composer.cpp
new file mode 100644
index 0000000..412e818
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/gce_composer.cpp
@@ -0,0 +1,704 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "gce_composer.h"
+#include <GceFrameBuffer.h>
+#include <GceFrameBufferControl.h>
+#include <cutils/log.h>
+#include <hardware/hwcomposer.h>
+#include <hardware/hwcomposer_defs.h>
+#include <libyuv.h>
+#include <system/graphics.h>
+#include <algorithm>
+#include <cstdlib>
+#include <utility>
+#include <vector>
+#include "geometry_utils.h"
+#include "hwcomposer_common.h"
+
+namespace avd {
+
+namespace {
+
+// Ensures that the layer does not include any inconsistencies
+int SanityCheckLayer(const gce_hwc_layer& layer) {
+  // Check displayFrame
+  if (layer.displayFrame.left > layer.displayFrame.right ||
+      layer.displayFrame.top > layer.displayFrame.bottom) {
+    ALOGE(
+        "%s: Malformed rectangle (displayFrame): [left = %d, right = %d, top = "
+        "%d, bottom = %d]",
+        __FUNCTION__,
+        layer.displayFrame.left,
+        layer.displayFrame.right,
+        layer.displayFrame.top,
+        layer.displayFrame.bottom);
+    return -EINVAL;
+  }
+  // Check sourceCrop
+  if (layer.sourceCrop.left > layer.sourceCrop.right ||
+      layer.sourceCrop.top > layer.sourceCrop.bottom) {
+    ALOGE(
+        "%s: Malformed rectangle (sourceCrop): [left = %d, right = %d, top = "
+        "%d, bottom = %d]",
+        __FUNCTION__,
+        layer.sourceCrop.left,
+        layer.sourceCrop.right,
+        layer.sourceCrop.top,
+        layer.sourceCrop.bottom);
+    return -EINVAL;
+  }
+  const private_handle_t* p_handle =
+      reinterpret_cast<const private_handle_t*>(layer.handle);
+  if (!p_handle) {
+    ALOGE("Layer has a NULL buffer handle");
+    return -EINVAL;
+  }
+  if (layer.sourceCrop.left < 0 || layer.sourceCrop.top < 0 ||
+      layer.sourceCrop.right > p_handle->x_res ||
+      layer.sourceCrop.bottom > p_handle->y_res) {
+    ALOGE(
+        "%s: Invalid sourceCrop for buffer handle: sourceCrop = [left = %d, "
+        "right = %d, top = %d, bottom = %d], handle = [width = %d, height = "
+        "%d]",
+        __FUNCTION__,
+        layer.sourceCrop.left,
+        layer.sourceCrop.right,
+        layer.sourceCrop.top,
+        layer.sourceCrop.bottom,
+        p_handle->x_res,
+        p_handle->y_res);
+    return -EINVAL;
+  }
+  return 0;
+}
+
+bool LayerNeedsScaling(const gce_hwc_layer& layer) {
+  int from_w = layer.sourceCrop.right - layer.sourceCrop.left;
+  int from_h = layer.sourceCrop.bottom - layer.sourceCrop.top;
+  int to_w = layer.displayFrame.right - layer.displayFrame.left;
+  int to_h = layer.displayFrame.bottom - layer.displayFrame.top;
+
+  bool not_rot_scale = from_w != to_w || from_h != to_h;
+  bool rot_scale = from_w != to_h || from_h != to_w;
+
+  bool needs_rot = layer.transform & HAL_TRANSFORM_ROT_90;
+
+  return needs_rot ? rot_scale : not_rot_scale;
+}
+
+bool LayerNeedsBlending(const gce_hwc_layer& layer) {
+  return layer.blending != HWC_BLENDING_NONE;
+}
+
+bool LayerNeedsAttenuation(const gce_hwc_layer& layer) {
+  return layer.blending == HWC_BLENDING_COVERAGE;
+}
+
+struct BufferSpec;
+typedef int (*ConverterFunction)(const BufferSpec& src, const BufferSpec& dst,
+                                 bool v_flip);
+int DoCopy(const BufferSpec& src, const BufferSpec& dst, bool v_flip);
+int ConvertFromYV12(const BufferSpec& src, const BufferSpec& dst, bool v_flip);
+ConverterFunction GetConverter(uint32_t format) {
+  switch (format) {
+    case HAL_PIXEL_FORMAT_RGBA_8888:
+    case HAL_PIXEL_FORMAT_RGBX_8888:
+    case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+      return &DoCopy;
+
+    case HAL_PIXEL_FORMAT_YV12:
+      return &ConvertFromYV12;
+
+    // Unsupported formats
+    // TODO(jemoreira): Conversion from these formats should be implemented as
+    // we find evidence of its usage.
+    // case HAL_PIXEL_FORMAT_BGRA_8888:
+
+    // case HAL_PIXEL_FORMAT_RGB_888:
+    // case HAL_PIXEL_FORMAT_RGB_565:
+
+    // case HAL_PIXEL_FORMAT_sRGB_A_8888:
+    // case HAL_PIXEL_FORMAT_sRGB_X_8888:
+
+    // case HAL_PIXEL_FORMAT_Y8:
+    // case HAL_PIXEL_FORMAT_Y16:
+
+    // case HAL_PIXEL_FORMAT_RAW_SENSOR:
+    // case HAL_PIXEL_FORMAT_BLOB:
+
+    // case HAL_PIXEL_FORMAT_YCbCr_420_888:
+    // case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+    // case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+    // case HAL_PIXEL_FORMAT_YCbCr_422_I:
+    default:
+      ALOGW("Unsupported format: 0x%04x, returning null converter function",
+            format);
+  }
+  return NULL;
+}
+
+// Whether we support a given format
+bool IsFormatSupported(uint32_t format) {
+  return GetConverter(format) != NULL;
+}
+
+bool CanCompositeLayer(const gce_hwc_layer& layer) {
+  if (layer.handle == NULL) {
+    ALOGW("%s received a layer with a null handler", __FUNCTION__);
+    return false;
+  }
+  int format = reinterpret_cast<const private_handle_t*>(layer.handle)->format;
+  if (!IsFormatSupported(format)) {
+    ALOGD("Unsupported pixel format: 0x%x, doing software composition instead",
+          format);
+    return false;
+  }
+  return true;
+}
+
+/*******************************************************************************
+Libyuv's convert functions only allow the combination of any rotation (multiple
+of 90 degrees) and a vertical flip, but not horizontal flips.
+Surfaceflinger's transformations are expressed in terms of a vertical flip, a
+horizontal flip and/or a single 90 degrees clockwise rotation (see
+NATIVE_WINDOW_TRANSFORM_HINT documentation on system/window.h for more insight).
+The following code allows to turn a horizontal flip into a 180 degrees rotation
+and a vertical flip.
+*******************************************************************************/
+libyuv::RotationMode GetRotationFromTransform(uint32_t transform) {
+  uint32_t rotation =
+      (transform & HAL_TRANSFORM_ROT_90) ? 1 : 0;          // 1 * ROT90 bit
+  rotation += (transform & HAL_TRANSFORM_FLIP_H) ? 2 : 0;  // 2 * VFLIP bit
+  return static_cast<libyuv::RotationMode>(90 * rotation);
+}
+
+bool GetVFlipFromTransform(uint32_t transform) {
+  // vertical flip xor horizontal flip
+  return ((transform & HAL_TRANSFORM_FLIP_V) >> 1) ^
+         (transform & HAL_TRANSFORM_FLIP_H);
+}
+
+struct BufferSpec {
+  uint8_t* buffer;
+  size_t size;
+  int width;
+  int height;
+  int stride;
+  int crop_x;
+  int crop_y;
+  int crop_width;
+  int crop_height;
+  uint32_t format;
+
+  BufferSpec(uint8_t* buffer, size_t size, int width, int height, int stride)
+      : buffer(buffer),
+        size(size),
+        width(width),
+        height(height),
+        stride(stride),
+        crop_x(0),
+        crop_y(0),
+        crop_width(width),
+        crop_height(height),
+        format(HAL_PIXEL_FORMAT_RGBA_8888) {}
+};
+
+int ConvertFromYV12(const BufferSpec& src, const BufferSpec& dst, bool v_flip) {
+  // use the stride in pixels as the source width
+  int stride_in_pixels = src.stride / formatToBytesPerPixel(src.format);
+
+  // The following calculation of plane offsets and alignments are based on
+  // swiftshader's Sampler::setTextureLevel() implementation
+  // (Renderer/Sampler.cpp:225)
+  uint8_t* src_y = src.buffer;
+  int stride_y = stride_in_pixels;
+  uint8_t* src_v = src_y + stride_y * src.height;
+  int stride_v = GceFrameBuffer::align(stride_y / 2, 16);
+  uint8_t* src_u = src_v + stride_v * src.height / 2;
+  int stride_u = GceFrameBuffer::align(stride_y / 2, 16);
+
+  // Adjust for crop
+  src_y += src.crop_y * stride_y + src.crop_x;
+  src_v += (src.crop_y / 2) * stride_v + (src.crop_x / 2);
+  src_u += (src.crop_y / 2) * stride_u + (src.crop_x / 2);
+  uint8_t* dst_buffer = dst.buffer + dst.crop_y * dst.stride +
+                        dst.crop_x * formatToBytesPerPixel(dst.format);
+
+  // YV12 is the same as I420, with the U and V planes swapped
+  return libyuv::I420ToARGB(src_y, stride_y, src_v, stride_v, src_u, stride_u,
+                            dst_buffer, dst.stride, dst.crop_width,
+                            v_flip ? -dst.crop_height : dst.crop_height);
+}
+
+int DoConversion(const BufferSpec& src, const BufferSpec& dst, bool v_flip) {
+  return (*GetConverter(src.format))(src, dst, v_flip);
+}
+
+int DoCopy(const BufferSpec& src, const BufferSpec& dst, bool v_flip) {
+  // Point to the upper left corner of the crop rectangle
+  uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+                        src.crop_x * formatToBytesPerPixel(src.format);
+  uint8_t* dst_buffer = dst.buffer + dst.crop_y * dst.stride +
+                        dst.crop_x * formatToBytesPerPixel(dst.format);
+  int width = src.crop_width;
+  int height = src.crop_height;
+
+  if (v_flip) {
+    height = -height;
+  }
+
+  // HAL formats are named based on the order of the pixel componets on the
+  // byte stream, while libyuv formats are named based on the order of those
+  // pixel components in an integer written from left to right. So
+  // libyuv::FOURCC_ARGB is equivalent to HAL_PIXEL_FORMAT_BGRA_8888.
+  return libyuv::ARGBCopy(src_buffer, src.stride, dst_buffer, dst.stride, width,
+                          height);
+}
+
+int DoRotation(const BufferSpec& src, const BufferSpec& dst,
+               libyuv::RotationMode rotation, bool v_flip) {
+  // Point to the upper left corner of the crop rectangles
+  uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+                        src.crop_x * formatToBytesPerPixel(src.format);
+  uint8_t* dst_buffer = dst.buffer + dst.crop_y * dst.stride +
+                        dst.crop_x * formatToBytesPerPixel(dst.format);
+  int width = src.crop_width;
+  int height = src.crop_height;
+
+  if (v_flip) {
+    height = -height;
+  }
+
+  return libyuv::ARGBRotate(src_buffer, src.stride, dst_buffer, dst.stride,
+                            width, height, rotation);
+}
+
+int DoScaling(const BufferSpec& src, const BufferSpec& dst, bool v_flip) {
+  // Point to the upper left corner of the crop rectangles
+  uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+                        src.crop_x * formatToBytesPerPixel(src.format);
+  uint8_t* dst_buffer = dst.buffer + dst.crop_y * dst.stride +
+                        dst.crop_x * formatToBytesPerPixel(dst.format);
+  int src_width = src.crop_width;
+  int src_height = src.crop_height;
+  int dst_width = dst.crop_width;
+  int dst_height = dst.crop_height;
+
+  if (v_flip) {
+    src_height = -src_height;
+  }
+
+  return libyuv::ARGBScale(src_buffer, src.stride, src_width, src_height,
+                           dst_buffer, dst.stride, dst_width, dst_height,
+                           libyuv::kFilterBilinear);
+}
+
+int DoAttenuation(const BufferSpec& src, const BufferSpec& dest, bool v_flip) {
+  // Point to the upper left corner of the crop rectangles
+  uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+                        src.crop_x * formatToBytesPerPixel(src.format);
+  uint8_t* dst_buffer = dest.buffer + dest.crop_y * dest.stride +
+                        dest.crop_x * formatToBytesPerPixel(dest.format);
+  int width = dest.crop_width;
+  int height = dest.crop_height;
+
+  if (v_flip) {
+    height = -height;
+  }
+
+  return libyuv::ARGBAttenuate(src_buffer, src.stride, dst_buffer, dest.stride,
+                               width, height);
+}
+
+int DoBlending(const BufferSpec& src, const BufferSpec& dest, bool v_flip) {
+  // Point to the upper left corner of the crop rectangles
+  uint8_t* src_buffer = src.buffer + src.crop_y * src.stride +
+                        src.crop_x * formatToBytesPerPixel(src.format);
+  uint8_t* dst_buffer = dest.buffer + dest.crop_y * dest.stride +
+                        dest.crop_x * formatToBytesPerPixel(dest.format);
+  int width = dest.crop_width;
+  int height = dest.crop_height;
+
+  if (v_flip) {
+    height = -height;
+  }
+
+  // libyuv's ARGB format is hwcomposer's BGRA format, since blending only cares
+  // for the position of alpha in the pixel and not the position of the colors
+  // this function is perfectly usable.
+  return libyuv::ARGBBlend(src_buffer, src.stride, dst_buffer, dest.stride,
+                           dst_buffer, dest.stride, width, height);
+}
+
+}  // namespace
+
+// Returns a handle to the appropriate framebuffer to use:
+// - the one provided by surfaceflinger if it is doing any GLES composition
+// - the next hwc-only framebuffer otherwise
+// Takes care of rotating the hwc-only framebuffers
+buffer_handle_t GceComposer::FindFrameBuffer(int num_layers,
+                                             gce_hwc_layer* layers) {
+  buffer_handle_t* fb_handle = NULL;
+  bool use_hwc_fb = true;
+  // The framebuffer target is usually the last layer in the list, so iterate in
+  // reverse
+  for (int idx = num_layers - 1; idx >= 0; --idx) {
+    if (IS_TARGET_FRAMEBUFFER(layers[idx].compositionType)) {
+      fb_handle = &layers[idx].handle;
+    } else if (layers[idx].compositionType != HWC_OVERLAY) {
+      use_hwc_fb = false;
+      // Just in case the FB target was not found yet
+      if (fb_handle) break;
+    }
+  }
+  if (use_hwc_fb && !hwc_framebuffers_.empty()) {
+    fb_handle = &hwc_framebuffers_[next_hwc_framebuffer_];
+    next_hwc_framebuffer_ =
+        (next_hwc_framebuffer_ + 1) % hwc_framebuffers_.size();
+  }
+
+  return *fb_handle;
+}
+
+void GceComposer::CompositeLayer(gce_hwc_layer* src_layer,
+                                 buffer_handle_t dst_handle) {
+  libyuv::RotationMode rotation =
+      GetRotationFromTransform(src_layer->transform);
+
+  const private_handle_t* src_priv_handle =
+      reinterpret_cast<const private_handle_t*>(src_layer->handle);
+  const private_handle_t* dst_priv_handle =
+      reinterpret_cast<const private_handle_t*>(dst_handle);
+
+  bool needs_conversion = src_priv_handle->format != dst_priv_handle->format;
+  bool needs_scaling = LayerNeedsScaling(*src_layer);
+  bool needs_rotation = rotation != libyuv::kRotate0;
+  bool needs_transpose = needs_rotation && rotation != libyuv::kRotate180;
+  bool needs_vflip = GetVFlipFromTransform(src_layer->transform);
+  bool needs_attenuation = LayerNeedsAttenuation(*src_layer);
+  bool needs_blending = LayerNeedsBlending(*src_layer);
+  bool needs_copy = !(needs_conversion || needs_scaling || needs_rotation ||
+                      needs_vflip || needs_attenuation || needs_blending);
+
+  uint8_t* src_buffer;
+  uint8_t* dst_buffer;
+  int retval = gralloc_module_->lock(
+      gralloc_module_, src_layer->handle, GRALLOC_USAGE_SW_READ_OFTEN, 0, 0,
+      src_priv_handle->x_res, src_priv_handle->y_res,
+      reinterpret_cast<void**>(&src_buffer));
+  if (retval) {
+    ALOGE("Got error code %d from lock function", retval);
+    return;
+  }
+  retval = gralloc_module_->lock(gralloc_module_, dst_handle,
+                                 GRALLOC_USAGE_SW_WRITE_OFTEN, 0, 0,
+                                 dst_priv_handle->x_res, dst_priv_handle->y_res,
+                                 reinterpret_cast<void**>(&dst_buffer));
+  if (retval) {
+    ALOGE("Got error code %d from lock function", retval);
+    // TODO(jemoreira): Use a lock_guard-like object.
+    gralloc_module_->unlock(gralloc_module_, src_priv_handle);
+    return;
+  }
+
+  BufferSpec src_layer_spec(src_buffer, src_priv_handle->total_size,
+                            src_priv_handle->x_res, src_priv_handle->y_res,
+                            src_priv_handle->stride_in_pixels *
+                                formatToBytesPerPixel(src_priv_handle->format));
+  src_layer_spec.crop_x = src_layer->sourceCrop.left;
+  src_layer_spec.crop_y = src_layer->sourceCrop.top;
+  src_layer_spec.crop_width =
+      src_layer->sourceCrop.right - src_layer->sourceCrop.left;
+  src_layer_spec.crop_height =
+      src_layer->sourceCrop.bottom - src_layer->sourceCrop.top;
+  src_layer_spec.format = src_priv_handle->format;
+
+  BufferSpec dst_layer_spec(dst_buffer, dst_priv_handle->total_size,
+                            dst_priv_handle->x_res, dst_priv_handle->y_res,
+                            dst_priv_handle->stride_in_pixels *
+                                formatToBytesPerPixel(dst_priv_handle->format));
+  dst_layer_spec.crop_x = src_layer->displayFrame.left;
+  dst_layer_spec.crop_y = src_layer->displayFrame.top;
+  dst_layer_spec.crop_width =
+      src_layer->displayFrame.right - src_layer->displayFrame.left;
+  dst_layer_spec.crop_height =
+      src_layer->displayFrame.bottom - src_layer->displayFrame.top;
+  dst_layer_spec.format = dst_priv_handle->format;
+
+  // Add the destination layer to the bottom of the buffer stack
+  std::vector<BufferSpec> dest_buffer_stack(1, dst_layer_spec);
+
+  // If more than operation is to be performed, a temporary buffer is needed for
+  // each additional operation
+
+  // N operations need N destination buffers, the destination layer (the
+  // framebuffer) is one of them, so only N-1 temporary buffers are needed.
+  // Vertical flip is not taken into account because it can be done together
+  // with any other operation.
+  int needed_tmp_buffers = (needs_conversion ? 1 : 0) +
+                           (needs_scaling ? 1 : 0) + (needs_rotation ? 1 : 0) +
+                           (needs_attenuation ? 1 : 0) +
+                           (needs_blending ? 1 : 0) + (needs_copy ? 1 : 0) - 1;
+
+  int x_res = src_layer->displayFrame.right - src_layer->displayFrame.left;
+  int y_res = src_layer->displayFrame.bottom - src_layer->displayFrame.top;
+  size_t output_frame_size =
+      x_res * y_res * formatToBytesPerPixel(dst_priv_handle->format);
+  while (needed_tmp_buffers > 0) {
+    BufferSpec tmp(
+        RotateTmpBuffer(needed_tmp_buffers), output_frame_size, x_res, y_res,
+        // There should be no danger of overflow aligning the stride because
+        // these sizes are taken from the displayFrame rectangle which is always
+        // smaller than the framebuffer, the framebuffer in turn has aligned
+        // stride and these buffers are the size of the framebuffer.
+        GceFrameBuffer::align(
+            x_res * formatToBytesPerPixel(dst_priv_handle->format), 16));
+    dest_buffer_stack.push_back(tmp);
+    needed_tmp_buffers--;
+  }
+
+  // Conversion and scaling should always be the first operations, so that every
+  // other operation works on equally sized frames (garanteed to fit in the tmp
+  // buffers)
+
+  // TODO(jemoreira): We are converting to ARGB as the first step under the
+  // assumption that scaling ARGB is faster than scaling I420 (the most common).
+  // This should be confirmed with testing.
+  if (needs_conversion) {
+    BufferSpec& dst_buffer_spec = dest_buffer_stack.back();
+    if (needs_scaling || needs_transpose) {
+      // If a rotation or a scaling operation are needed the dimensions at the
+      // top of the buffer stack are wrong (wrong sizes for scaling, swapped
+      // width and height for 90 and 270 rotations).
+      // Make width and height match the crop sizes on the source
+      int src_width = src_layer_spec.crop_width;
+      int src_height = src_layer_spec.crop_height;
+      int dst_stride = GceFrameBuffer::align(
+          src_width * formatToBytesPerPixel(dst_priv_handle->format), 16);
+      size_t needed_size = dst_stride * src_height;
+      dst_buffer_spec.width = src_width;
+      dst_buffer_spec.height = src_height;
+      // Ajust the stride accordingly
+      dst_buffer_spec.stride = dst_stride;
+      // Crop sizes also need to be adjusted
+      dst_buffer_spec.crop_width = src_width;
+      dst_buffer_spec.crop_height = src_height;
+      dst_buffer_spec.size = needed_size;
+      // crop_x and y are fine at 0, format is already set to match destination
+
+      // In case of a scale, the source frame may be bigger than the default tmp
+      // buffer size
+      if (needed_size > tmp_buffer_.size() / kNumTmpBufferPieces) {
+        dst_buffer_spec.buffer = GetSpecialTmpBuffer(needed_size);
+      }
+    }
+    retval = DoConversion(src_layer_spec, dst_buffer_spec, needs_vflip);
+    if (retval) {
+      ALOGE("Got error code %d from DoConversion function", retval);
+    }
+    needs_vflip = false;
+    src_layer_spec = dst_buffer_spec;
+    dest_buffer_stack.pop_back();
+  }
+
+  if (needs_scaling) {
+    BufferSpec& dst_buffer_spec = dest_buffer_stack.back();
+    if (needs_transpose) {
+      // If a rotation is needed, the temporary buffer has the correct size but
+      // needs to be transposed and have its stride updated accordingly. The
+      // crop sizes also needs to be transposed, but not the x and y since they
+      // are both zero in a temporary buffer (and it is a temporary buffer
+      // because a rotation will be performed next).
+      std::swap(dst_buffer_spec.width, dst_buffer_spec.height);
+      std::swap(dst_buffer_spec.crop_width, dst_buffer_spec.crop_height);
+      // TODO (jemoreira): Aligment (To align here may cause the needed size to
+      // be bigger than the buffer, so care should be taken)
+      dst_buffer_spec.stride = dst_buffer_spec.width *
+                               formatToBytesPerPixel(dst_priv_handle->format);
+    }
+    retval = DoScaling(src_layer_spec, dst_buffer_spec, needs_vflip);
+    needs_vflip = false;
+    if (retval) {
+      ALOGE("Got error code %d from DoScaling function", retval);
+    }
+    src_layer_spec = dst_buffer_spec;
+    dest_buffer_stack.pop_back();
+  }
+
+  if (needs_rotation) {
+    retval = DoRotation(src_layer_spec, dest_buffer_stack.back(), rotation,
+                        needs_vflip);
+    needs_vflip = false;
+    if (retval) {
+      ALOGE("Got error code %d from DoTransform function", retval);
+    }
+    src_layer_spec = dest_buffer_stack.back();
+    dest_buffer_stack.pop_back();
+  }
+
+  if (needs_attenuation) {
+    retval =
+        DoAttenuation(src_layer_spec, dest_buffer_stack.back(), needs_vflip);
+    needs_vflip = false;
+    if (retval) {
+      ALOGE("Got error code %d from DoBlending function", retval);
+    }
+    src_layer_spec = dest_buffer_stack.back();
+    dest_buffer_stack.pop_back();
+  }
+
+  if (needs_copy) {
+    retval = DoCopy(src_layer_spec, dest_buffer_stack.back(), needs_vflip);
+    needs_vflip = false;
+    if (retval) {
+      ALOGE("Got error code %d from DoBlending function", retval);
+    }
+    src_layer_spec = dest_buffer_stack.back();
+    dest_buffer_stack.pop_back();
+  }
+
+  // Blending (if needed) should always be the last operation, so that it reads
+  // and writes in the destination layer and not some temporary buffer.
+  if (needs_blending) {
+    retval = DoBlending(src_layer_spec, dest_buffer_stack.back(), needs_vflip);
+    needs_vflip = false;
+    if (retval) {
+      ALOGE("Got error code %d from DoBlending function", retval);
+    }
+    // Don't need to assign destination to source in the last one
+    dest_buffer_stack.pop_back();
+  }
+
+  gralloc_module_->unlock(gralloc_module_, src_priv_handle);
+  gralloc_module_->unlock(gralloc_module_, dst_priv_handle);
+}
+
+/* static */ const int GceComposer::kNumTmpBufferPieces = 2;
+
+GceComposer::GceComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns)
+    : BaseComposer(vsync_base_timestamp, vsync_period_ns),
+      tmp_buffer_(kNumTmpBufferPieces *
+                  GceFrameBuffer::getInstance().bufferSize()),
+      next_hwc_framebuffer_(0) {
+  hw_get_module(GRALLOC_HARDWARE_MODULE_ID,
+                reinterpret_cast<const hw_module_t**>(&gralloc_module_));
+  gralloc_module_->common.methods->open(
+      reinterpret_cast<const hw_module_t*>(gralloc_module_),
+      GRALLOC_HARDWARE_GPU0,
+      reinterpret_cast<hw_device_t**>(&gralloc_dev_));
+  for (int i = 0; i < GceFrameBuffer::kNumHwcBuffers; ++i) {
+    buffer_handle_t tmp;
+    gralloc_dev_->alloc_hwc_framebuffer(
+        reinterpret_cast<alloc_device_t*>(gralloc_dev_), &tmp);
+    hwc_framebuffers_.push_back(tmp);
+  }
+}
+
+GceComposer::~GceComposer() {
+  // Free the hwc fb handles
+  for (int idx = 0; idx < hwc_framebuffers_.size(); ++idx) {
+    gralloc_dev_->device.free(reinterpret_cast<alloc_device_t*>(gralloc_dev_),
+                              hwc_framebuffers_[idx]);
+  }
+
+  // close devices
+  gralloc_dev_->device.common.close(reinterpret_cast<hw_device_t*>(gralloc_dev_));
+}
+
+int GceComposer::PrepareLayers(size_t num_layers, gce_hwc_layer* layers) {
+  int composited_layers_count = 0;
+
+  // Loop over layers in inverse order of z-index
+  for (size_t layer_index = num_layers; layer_index > 0;) {
+    // Decrement here to be able to compare unsigned integer with 0 in the
+    // loop condition
+    --layer_index;
+    if (IS_TARGET_FRAMEBUFFER(layers[layer_index].compositionType)) {
+      continue;
+    }
+    if (layers[layer_index].flags & HWC_SKIP_LAYER) {
+      continue;
+    }
+    if (layers[layer_index].compositionType == HWC_BACKGROUND) {
+      layers[layer_index].compositionType = HWC_FRAMEBUFFER;
+      continue;
+    }
+    layers[layer_index].compositionType = HWC_OVERLAY;
+    // Hwcomposer cannot draw below software-composed layers, so we need
+    // to mark those HWC_FRAMEBUFFER as well.
+    for (size_t top_idx = layer_index + 1; top_idx < num_layers; ++top_idx) {
+      // layers marked as skip are in a state that makes them unreliable to
+      // read, so it's best to assume they cover the whole screen
+      if (layers[top_idx].flags & HWC_SKIP_LAYER ||
+          (layers[top_idx].compositionType == HWC_FRAMEBUFFER &&
+           LayersOverlap(layers[layer_index], layers[top_idx]))) {
+        layers[layer_index].compositionType = HWC_FRAMEBUFFER;
+        break;
+      }
+    }
+    if (layers[layer_index].compositionType == HWC_OVERLAY &&
+        !CanCompositeLayer(layers[layer_index])) {
+      layers[layer_index].compositionType = HWC_FRAMEBUFFER;
+    }
+    if (layers[layer_index].compositionType == HWC_OVERLAY) {
+      ++composited_layers_count;
+    }
+  }
+  return composited_layers_count;
+}
+
+int GceComposer::SetLayers(size_t num_layers, gce_hwc_layer* layers) {
+  int targetFbs = 0;
+  buffer_handle_t fb_handle = FindFrameBuffer(num_layers, layers);
+  if (!fb_handle) {
+    ALOGE("%s: framebuffer handle is null", __FUNCTION__);
+    return -1;
+  }
+  // TODO(jemoreira): Lock all HWC_OVERLAY layers and the framebuffer before
+  // this loop and unlock them after. The way it's done now causes the target
+  // framebuffer to be locked and unlocked many times, if regions are
+  // implemented it will also be true for every layer that covers more than one
+  // region.
+  for (size_t idx = 0; idx < num_layers; idx++) {
+    if (IS_TARGET_FRAMEBUFFER(layers[idx].compositionType)) {
+      ++targetFbs;
+    } else if (layers[idx].compositionType == HWC_OVERLAY &&
+               !(layers[idx].flags & HWC_SKIP_LAYER)) {
+      if (SanityCheckLayer(layers[idx])) {
+        ALOGE("Layer (%d) failed sanity check", idx);
+        return -EINVAL;
+      }
+      CompositeLayer(&layers[idx], fb_handle);
+    }
+  }
+  if (targetFbs != 1) {
+    ALOGW("Saw %d layers, posted=%d", num_layers, targetFbs);
+  }
+  return PostFrameBuffer(fb_handle);
+}
+
+uint8_t* GceComposer::RotateTmpBuffer(unsigned int order) {
+  return &tmp_buffer_[(order % kNumTmpBufferPieces) * tmp_buffer_.size() /
+                      kNumTmpBufferPieces];
+}
+
+uint8_t* GceComposer::GetSpecialTmpBuffer(size_t needed_size) {
+  special_tmp_buffer_.resize(needed_size);
+  return &special_tmp_buffer_[0];
+}
+
+}  // namespace avd
diff --git a/guest/hals/hwcomposer/legacy/gce_composer.h b/guest/hals/hwcomposer/legacy/gce_composer.h
new file mode 100644
index 0000000..aa5171d
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/gce_composer.h
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GCE_HWCOMPOSER_GCE_COMPOSER_H
+#define GCE_HWCOMPOSER_GCE_COMPOSER_H
+
+#include <hardware/gralloc.h>
+#include "gralloc_gce_priv.h"
+#include "hwcomposer_common.h"
+#include "base_composer.h"
+
+#include <vector>
+
+namespace avd {
+
+class GceComposer : public BaseComposer {
+ public:
+  GceComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns);
+  ~GceComposer();
+
+  // override
+  int PrepareLayers(size_t num_layers, gce_hwc_layer* layers);
+  // override
+  int SetLayers(size_t num_layers, gce_hwc_layer* layers);
+
+ protected:
+  static const int kNumTmpBufferPieces;
+  uint8_t* RotateTmpBuffer(unsigned int order);
+  uint8_t* GetSpecialTmpBuffer(size_t needed_size);
+  buffer_handle_t FindFrameBuffer(int num_layers, gce_hwc_layer* layers);
+  void CompositeLayer(gce_hwc_layer* src_layer, buffer_handle_t dst_layer);
+  std::vector<uint8_t> tmp_buffer_;
+  std::vector<uint8_t> special_tmp_buffer_;
+  const gralloc_module_t* gralloc_module_;
+  priv_alloc_device_t* gralloc_dev_;
+  std::vector<buffer_handle_t> hwc_framebuffers_;
+  int next_hwc_framebuffer_;
+};
+
+}  // namespace avd
+
+#endif
diff --git a/guest/hals/hwcomposer/legacy/geometry_utils.cpp b/guest/hals/hwcomposer/legacy/geometry_utils.cpp
new file mode 100644
index 0000000..6102d81
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/geometry_utils.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "geometry_utils.h"
+#include <algorithm>
+#include <utility>
+
+namespace avd {
+
+bool LayersOverlap(const gce_hwc_layer& layer1, const gce_hwc_layer& layer2) {
+  int left1 = layer1.displayFrame.left;
+  int right1 = layer1.displayFrame.right;
+  int top1 = layer1.displayFrame.top;
+  int bottom1 = layer1.displayFrame.bottom;
+
+  int left2 = layer2.displayFrame.left;
+  int right2 = layer2.displayFrame.right;
+  int top2 = layer2.displayFrame.top;
+  int bottom2 = layer2.displayFrame.bottom;
+
+  bool overlap_x = left1 < right2 && left2 < right1;
+  bool overlap_y = top1 < bottom2 && top2 < bottom1;
+
+  return overlap_x && overlap_y;
+}
+
+}  // namespace avd
diff --git a/guest/hals/hwcomposer/legacy/geometry_utils.h b/guest/hals/hwcomposer/legacy/geometry_utils.h
new file mode 100644
index 0000000..a575d2f
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/geometry_utils.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GCE_HWCOMPOSER_GEOMETRY_UTILS_H
+#define GCE_HWCOMPOSER_GEOMETRY_UTILS_H
+
+#include "hwcomposer_common.h"
+
+namespace avd {
+
+bool LayersOverlap(const gce_hwc_layer& layer1, const gce_hwc_layer& layer2);
+
+}  // namespace avd
+
+#endif
diff --git a/guest/hals/hwcomposer/legacy/hwc_tests.cpp b/guest/hals/hwcomposer/legacy/hwc_tests.cpp
new file mode 100644
index 0000000..86a3c6e
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/hwc_tests.cpp
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "gce_composer.h"
+#include <stdio.h>
+
+// This executable is only intended to perform simple tests on the hwcomposer
+// functionality. It should not be part of the images, but rather be included
+// (via scp) when needed to test specific scenarios that are hard to reproduce
+// in the normal operation of the device.
+
+class HWC_Tester : public avd::GceComposer {
+ public:
+  HWC_Tester() : avd::GceComposer(int64_t(0), int32_t(16000000)) {}
+  int RunTest() {
+    // Allocate two buffers (1x1 and 800x1280)
+    buffer_handle_t src_handle;
+    int src_stride;
+    int res = gralloc_dev_->device.alloc(&gralloc_dev_->device,
+                                         1,
+                                         1,
+                                         HAL_PIXEL_FORMAT_RGBA_8888,
+                                         GRALLOC_USAGE_SW_READ_OFTEN,
+                                         &src_handle,
+                                         &src_stride);
+    if (res) {
+      fprintf(stderr, "Error allocating source buffer, see logs for details\n");
+      return -1;
+    }
+    buffer_handle_t dst_handle;
+    int dst_stride;
+    res = gralloc_dev_->device.alloc(&gralloc_dev_->device,
+                                     800,
+                                     1280,
+                                     HAL_PIXEL_FORMAT_RGBA_8888,
+                                     GRALLOC_USAGE_SW_WRITE_OFTEN,
+                                     &dst_handle,
+                                     &dst_stride);
+    if (res) {
+      fprintf(stderr,
+              "Error allocating destination buffer, see logs for details\n");
+      return -1;
+    }
+    // Create a mock layer requesting a sinple copy of the pixels so that DoCopy gets called
+    gce_hwc_layer src_layer;
+    src_layer.compositionType = HWC_OVERLAY;
+    src_layer.hints = 0;
+    src_layer.flags = 0;
+    src_layer.handle = src_handle;
+
+    // No transformation, just a copy
+    src_layer.transform = 0;
+    src_layer.blending = HWC_BLENDING_NONE;
+
+    src_layer.sourceCrop.top = 0;
+    src_layer.sourceCrop.left = 0;
+    src_layer.sourceCrop.bottom = 1;
+    src_layer.sourceCrop.right = 1;
+
+    src_layer.displayFrame.top = 0;
+    src_layer.displayFrame.left = 0;
+    src_layer.displayFrame.bottom = 1;
+    src_layer.displayFrame.right = 1;
+
+    src_layer.visibleRegionScreen.numRects = 0;
+    src_layer.visibleRegionScreen.rects = NULL;
+
+    src_layer.acquireFenceFd = -1;
+    src_layer.releaseFenceFd = -1;
+    // Call CompositeLayer
+    CompositeLayer(&src_layer, dst_handle);
+    // If we got this far without a SEGFAULT we call it success
+    printf("OK\n");
+    gralloc_dev_->device.free(&gralloc_dev_->device, src_handle);
+    gralloc_dev_->device.free(&gralloc_dev_->device, dst_handle);
+    return 0;
+  }
+};
+
+int main() {
+  HWC_Tester t;
+  return t.RunTest();
+}
diff --git a/guest/hals/hwcomposer/legacy/hwcomposer.cpp b/guest/hals/hwcomposer/legacy/hwcomposer.cpp
new file mode 100644
index 0000000..670186b
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/hwcomposer.cpp
@@ -0,0 +1,389 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// Versions of hwcomposer we implement:
+// JB: 0.3
+// JB-MR1 to N : 1.1
+// N-MR1 to ... : We report 1.1 but SurfaceFlinger has the option to use an
+// adapter to treat our 1.1 hwcomposer as a 2.0. If SF stops using that adapter
+// to support 1.1 implementations it can be copied into gce from
+// frameworks/native/services/surfaceflinger/DisplayHardware/HWC2On1Adapter.*
+
+#include <api_level_fixes.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <math.h>
+#include <poll.h>
+#include <pthread.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#include <sys/ioctl.h>
+#include <sys/mman.h>
+#include <sys/resource.h>
+#include <sys/time.h>
+
+#define HWC_REMOVE_DEPRECATED_VERSIONS 1
+
+#include <cutils/compiler.h>
+#include <cutils/log.h>
+#include <cutils/properties.h>
+#include <hardware/gralloc.h>
+#include <hardware/hardware.h>
+#include <hardware/hwcomposer.h>
+#include <hardware/hwcomposer_defs.h>
+#include <utils/String8.h>
+#include <utils/Vector.h>
+
+#include <GceFrameBuffer.h>
+#include <GceFrameBufferControl.h>
+#include <gralloc_gce_priv.h>
+#include <remoter_framework_pkt.h>
+#include <sync/sync.h>
+
+#include "gce_composer.h"
+#include "geometry_utils.h"
+#include "hwcomposer_common.h"
+#include "base_composer.h"
+#include "stats_keeper.h"
+
+
+#ifdef USE_OLD_HWCOMPOSER
+typedef avd::BaseComposer InnerComposerType;
+#else
+typedef avd::GceComposer InnerComposerType;
+#endif
+
+
+#ifdef GATHER_STATS
+typedef avd::StatsKeepingComposer<InnerComposerType> ComposerType;
+#else
+typedef InnerComposerType ComposerType;
+#endif
+
+struct gce_hwc_composer_device_1_t {
+  gce_hwc_device base;
+  const hwc_procs_t* procs;
+  pthread_t vsync_thread;
+  int64_t vsync_base_timestamp;
+  int32_t vsync_period_ns;
+  ComposerType* composer;
+};
+
+static void dump_layer(gce_hwc_layer const* l) {
+  ALOGI(
+      "\ttype=%d, flags=%08x, handle=%p, tr=%02x, blend=%04x, "
+      "{%d,%d,%d,%d}, {%d,%d,%d,%d}",
+      l->compositionType, l->flags, l->handle, l->transform, l->blending,
+      l->sourceCrop.left, l->sourceCrop.top, l->sourceCrop.right,
+      l->sourceCrop.bottom, l->displayFrame.left, l->displayFrame.top,
+      l->displayFrame.right, l->displayFrame.bottom);
+}
+
+#if GCE_PLATFORM_SDK_BEFORE(J_MR1)
+static int gce_hwc_prepare(gce_hwc_device* dev, hwc_layer_list_t* list) {
+#else
+static int gce_hwc_prepare(gce_hwc_device* dev, size_t numDisplays,
+                           hwc_display_contents_1_t** displays) {
+  if (!numDisplays || !displays) return 0;
+
+  hwc_display_contents_1_t* list = displays[HWC_DISPLAY_PRIMARY];
+
+  if (!list) return 0;
+#endif
+  int composited_layers_count =
+      reinterpret_cast<gce_hwc_composer_device_1_t*>(dev)
+          ->composer->PrepareLayers(list->numHwLayers, &list->hwLayers[0]);
+  return 0;
+}
+
+#if GCE_PLATFORM_SDK_BEFORE(J_MR1)
+int gce_hwc_set(struct hwc_composer_device* dev, hwc_display_t dpy,
+                hwc_surface_t sur, hwc_layer_list_t* list) {
+  reinterpret_cast<gce_hwc_composer_device_1_t*>(dev)->composer->SetLayers(
+      list->numHwLayers, &list->hwLayers[0]);
+  return 0;
+}
+#else
+static int gce_hwc_set(gce_hwc_device* dev, size_t numDisplays,
+                       hwc_display_contents_1_t** displays) {
+  if (!numDisplays || !displays) return 0;
+
+  hwc_display_contents_1_t* contents = displays[HWC_DISPLAY_PRIMARY];
+  if (!contents) return 0;
+
+  gce_hwc_layer* layers = &contents->hwLayers[0];
+  reinterpret_cast<gce_hwc_composer_device_1_t*>(dev)->composer->SetLayers(
+      contents->numHwLayers, layers);
+
+  int closedFds = 0;
+  for (size_t index = 0; index < contents->numHwLayers; ++index) {
+    if (layers[index].acquireFenceFd != -1) {
+      close(layers[index].acquireFenceFd);
+      layers[index].acquireFenceFd = -1;
+      ++closedFds;
+    }
+  }
+  if (closedFds) {
+    ALOGI("Saw %d layers, closed=%d", contents->numHwLayers, closedFds);
+  }
+
+  // TODO(ghartman): This should be set before returning. On the next set it
+  // should be signalled when we load the new frame.
+  contents->retireFenceFd = -1;
+  return 0;
+}
+#endif
+
+static void gce_hwc_register_procs(gce_hwc_device* dev,
+                                   const hwc_procs_t* procs) {
+  struct gce_hwc_composer_device_1_t* pdev =
+      (struct gce_hwc_composer_device_1_t*)dev;
+  pdev->procs = procs;
+}
+
+static int gce_hwc_query(gce_hwc_device* dev, int what, int* value) {
+  struct gce_hwc_composer_device_1_t* pdev =
+      (struct gce_hwc_composer_device_1_t*)dev;
+
+  switch (what) {
+    case HWC_BACKGROUND_LAYER_SUPPORTED:
+      // we support the background layer
+      value[0] = 0;
+      break;
+    case HWC_VSYNC_PERIOD:
+      value[0] = pdev->vsync_period_ns;
+      break;
+    default:
+      // unsupported query
+      ALOGE("%s badness unsupported query what=%d", __FUNCTION__, what);
+      return -EINVAL;
+  }
+  return 0;
+}
+
+static int gce_hwc_event_control(
+#if GCE_PLATFORM_SDK_BEFORE(J_MR1)
+    gce_hwc_device* dev, int event, int /*enabled*/) {
+#else
+    gce_hwc_device* dev, int /*dpy*/, int event, int /*enabled*/) {
+#endif
+  struct gce_hwc_composer_device_1_t* pdev =
+      (struct gce_hwc_composer_device_1_t*)dev;
+
+  if (event == HWC_EVENT_VSYNC) {
+    return 0;
+  }
+  return -EINVAL;
+}
+
+static void* hwc_vsync_thread(void* data) {
+  struct gce_hwc_composer_device_1_t* pdev =
+      (struct gce_hwc_composer_device_1_t*)data;
+  setpriority(PRIO_PROCESS, 0, HAL_PRIORITY_URGENT_DISPLAY);
+
+  int64_t base_timestamp = pdev->vsync_base_timestamp;
+  int64_t last_logged = base_timestamp / 1e9;
+  int sent = 0;
+  int last_sent = 0;
+  static const int log_interval = 60;
+  while (true) {
+    struct timespec rt;
+    if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
+      ALOGE("%s:%d error in vsync thread clock_gettime: %s", __FILE__, __LINE__,
+            strerror(errno));
+    }
+    int64_t timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
+    // Given now's timestamp calculate the time of the next timestamp.
+    timestamp += pdev->vsync_period_ns -
+                 (timestamp - base_timestamp) % pdev->vsync_period_ns;
+
+    rt.tv_sec = timestamp / 1e9;
+    rt.tv_nsec = timestamp % static_cast<int32_t>(1e9);
+    int err = clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &rt, NULL);
+    if ( err == -1) {
+      ALOGE("error in vsync thread: %s", strerror(errno));
+      if (errno == EINTR) {
+        continue;
+      }
+    }
+
+    pdev->procs->vsync(const_cast<hwc_procs_t*>(pdev->procs), 0, timestamp);
+    if (rt.tv_sec - last_logged > log_interval) {
+      ALOGI("Sent %d syncs in %ds", sent - last_sent, log_interval);
+      last_logged = rt.tv_sec;
+      last_sent = sent;
+    }
+    ++sent;
+  }
+
+  return NULL;
+}
+
+static int gce_hwc_blank(gce_hwc_device* dev, int disp, int /*blank*/) {
+  struct gce_hwc_composer_device_1_t* pdev =
+      (struct gce_hwc_composer_device_1_t*)dev;
+  if (!IS_PRIMARY_DISPLAY(disp)) return -EINVAL;
+  return 0;
+}
+
+static void gce_hwc_dump(gce_hwc_device* dev, char* buff, int buff_len) {
+  reinterpret_cast<gce_hwc_composer_device_1_t*>(dev)->composer->Dump(buff,
+                                                                      buff_len);
+}
+
+static int gce_hwc_get_display_configs(gce_hwc_device* dev, int disp,
+                                       uint32_t* configs, size_t* numConfigs) {
+  struct gce_hwc_composer_device_1_t* pdev =
+      (struct gce_hwc_composer_device_1_t*)dev;
+
+  if (*numConfigs == 0) return 0;
+
+  if (IS_PRIMARY_DISPLAY(disp)) {
+    configs[0] = 0;
+    *numConfigs = 1;
+    return 0;
+  }
+
+  return -EINVAL;
+}
+
+#if GCE_PLATFORM_SDK_AFTER(J)
+static int32_t gce_hwc_attribute(struct gce_hwc_composer_device_1_t* pdev,
+                                 const uint32_t attribute) {
+  const GceFrameBuffer& config = GceFrameBuffer::getInstance();
+  switch (attribute) {
+    case HWC_DISPLAY_VSYNC_PERIOD:
+      return pdev->vsync_period_ns;
+    case HWC_DISPLAY_WIDTH:
+      return config.x_res();
+    case HWC_DISPLAY_HEIGHT:
+      return config.y_res();
+    case HWC_DISPLAY_DPI_X:
+      ALOGI("Reporting DPI_X of %d", config.dpi());
+      return config.dpi() * 1000;  // The number of pixels per thousand inches
+    case HWC_DISPLAY_DPI_Y:
+      ALOGI("Reporting DPI_Y of %d", config.dpi());
+      return config.dpi() * 1000;  // The number of pixels per thousand inches
+    default:
+      ALOGE("unknown display attribute %u", attribute);
+      return -EINVAL;
+  }
+}
+
+static int gce_hwc_get_display_attributes(gce_hwc_device* dev, int disp,
+                                          uint32_t config __unused,
+                                          const uint32_t* attributes,
+                                          int32_t* values) {
+  struct gce_hwc_composer_device_1_t* pdev =
+      (struct gce_hwc_composer_device_1_t*)dev;
+
+  if (!IS_PRIMARY_DISPLAY(disp)) {
+    ALOGE("unknown display type %u", disp);
+    return -EINVAL;
+  }
+
+  for (int i = 0; attributes[i] != HWC_DISPLAY_NO_ATTRIBUTE; i++) {
+    values[i] = gce_hwc_attribute(pdev, attributes[i]);
+  }
+
+  return 0;
+}
+#endif
+
+static int gce_hwc_close(hw_device_t* device) {
+  struct gce_hwc_composer_device_1_t* dev =
+      (struct gce_hwc_composer_device_1_t*)device;
+  ALOGE("gce_hwc_close");
+  pthread_kill(dev->vsync_thread, SIGTERM);
+  pthread_join(dev->vsync_thread, NULL);
+  delete dev->composer;
+  delete dev;
+  return 0;
+}
+
+static int gce_hwc_open(const struct hw_module_t* module, const char* name,
+                        struct hw_device_t** device) {
+  ALOGI("%s", __FUNCTION__);
+  if (strcmp(name, HWC_HARDWARE_COMPOSER)) {
+    ALOGE("%s called with bad name %s", __FUNCTION__, name);
+    return -EINVAL;
+  }
+
+  gce_hwc_composer_device_1_t* dev  = new gce_hwc_composer_device_1_t();
+  if (!dev) {
+    ALOGE("%s failed to allocate dev", __FUNCTION__);
+    return -ENOMEM;
+  }
+
+  // TODO(ghartman): Read metadata here
+  int refreshRate = 60;
+  dev->vsync_period_ns = 1000000000 / refreshRate;
+  struct timespec rt;
+  if (clock_gettime(CLOCK_MONOTONIC, &rt) == -1) {
+    ALOGE("%s:%d error in vsync thread clock_gettime: %s", __FILE__, __LINE__,
+          strerror(errno));
+  }
+  dev->vsync_base_timestamp = int64_t(rt.tv_sec) * 1e9 + rt.tv_nsec;
+
+  dev->base.common.tag = HARDWARE_DEVICE_TAG;
+  dev->base.common.version = GCE_HWC_DEVICE_API_VERSION;
+  dev->base.common.module = const_cast<hw_module_t*>(module);
+  dev->base.common.close = gce_hwc_close;
+
+  dev->base.prepare = gce_hwc_prepare;
+  dev->base.set = gce_hwc_set;
+  dev->base.query = gce_hwc_query;
+  dev->base.registerProcs = gce_hwc_register_procs;
+  dev->base.dump = gce_hwc_dump;
+#if GCE_PLATFORM_SDK_BEFORE(J_MR1)
+  static hwc_methods_t hwc_methods = {gce_hwc_event_control};
+  dev->base.methods = &hwc_methods;
+#else
+  dev->base.blank = gce_hwc_blank;
+  dev->base.eventControl = gce_hwc_event_control;
+  dev->base.getDisplayConfigs = gce_hwc_get_display_configs;
+  dev->base.getDisplayAttributes = gce_hwc_get_display_attributes;
+#endif
+  dev->composer =
+      new ComposerType(dev->vsync_base_timestamp, dev->vsync_period_ns);
+
+  int ret = pthread_create(&dev->vsync_thread, NULL, hwc_vsync_thread, dev);
+  if (ret) {
+    ALOGE("failed to start vsync thread: %s", strerror(ret));
+    ret = -ret;
+    delete dev;
+  } else {
+    *device = &dev->base.common;
+  }
+
+  return ret;
+}
+
+static struct hw_module_methods_t gce_hwc_module_methods = {
+    gce_hwc_open,
+};
+
+hwc_module_t HAL_MODULE_INFO_SYM = {{HARDWARE_MODULE_TAG,
+                                     HWC_MODULE_API_VERSION_0_1,
+                                     HARDWARE_HAL_API_VERSION,
+                                     HWC_HARDWARE_MODULE_ID,
+                                     "GCE hwcomposer module",
+                                     "Google",
+                                     &gce_hwc_module_methods,
+                                     NULL,
+                                     {0}}};
diff --git a/guest/hals/hwcomposer/legacy/hwcomposer.mk b/guest/hals/hwcomposer/legacy/hwcomposer.mk
new file mode 100644
index 0000000..eb04e2b
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/hwcomposer.mk
@@ -0,0 +1,54 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_MODULE_PATH := $(TARGET_OUT_SHARED_LIBRARIES)/hw
+LOCAL_MULTILIB := first
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SHARED_LIBRARIES := \
+    libgceframebuffer \
+    liblog \
+    libcutils \
+    libutils \
+    libsync \
+    libhardware \
+    libjpeg \
+    $(GCE_STLPORT_LIBS)
+
+LOCAL_STATIC_LIBRARIES := \
+    libgcemetadata \
+    libyuv_static
+
+LOCAL_SRC_FILES := \
+    geometry_utils.cpp \
+    hwcomposer.cpp \
+    gce_composer.cpp \
+    stats_keeper.cpp \
+    base_composer.cpp
+
+LOCAL_CFLAGS += \
+    -DLOG_TAG=\"hwcomposer\" \
+    -DGATHER_STATS \
+    $(GCE_VERSION_CFLAGS)
+
+LOCAL_C_INCLUDES := \
+    device/google/gce/hwcomposer \
+    device/google/gce/include \
+    external/libyuv/files/include \
+    bionic \
+    $(GCE_STLPORT_INCLUDES)
+
+include device/google/gce/libs/base/libbase.mk
+LOCAL_SHARED_LIBRARIES += $(GCE_LIBBASE_LIB_NAME)
+LOCAL_C_INCLUDES += $(GCE_LIBBASE_INCLUDE_DIR)
diff --git a/guest/hals/hwcomposer/legacy/hwcomposer_common.h b/guest/hals/hwcomposer/legacy/hwcomposer_common.h
new file mode 100644
index 0000000..857337f
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/hwcomposer_common.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GCE_HWCOMPOSER_HWCOMPOSER_COMMON_H
+#define GCE_HWCOMPOSER_HWCOMPOSER_COMMON_H
+
+#include <api_level_fixes.h>
+
+#include <hardware/hwcomposer.h>
+#include <hardware/hwcomposer_defs.h>
+
+#if GCE_PLATFORM_SDK_BEFORE(J_MR1)
+typedef hwc_composer_device_t gce_hwc_device;
+typedef hwc_layer_t gce_hwc_layer;
+#define IS_TARGET_FRAMEBUFFER(x) false
+#define IS_PRIMARY_DISPLAY(x) true
+#define GCE_HWC_DEVICE_API_VERSION HWC_DEVICE_API_VERSION_0_3
+#else
+typedef hwc_composer_device_1_t gce_hwc_device;
+typedef hwc_layer_1_t gce_hwc_layer;
+#define IS_TARGET_FRAMEBUFFER(x) ((x) == HWC_FRAMEBUFFER_TARGET)
+#define IS_PRIMARY_DISPLAY(x) ((x) == HWC_DISPLAY_PRIMARY)
+#define GCE_HWC_DEVICE_API_VERSION HWC_DEVICE_API_VERSION_1_1
+#endif
+
+#endif
diff --git a/guest/hals/hwcomposer/legacy/stats_keeper.cpp b/guest/hals/hwcomposer/legacy/stats_keeper.cpp
new file mode 100644
index 0000000..9c9487f
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/stats_keeper.cpp
@@ -0,0 +1,227 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include <cutils/log.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <algorithm>
+#include <utility>
+#include <vector>
+
+#include "geometry_utils.h"
+#include "stats_keeper.h"
+
+using avd::time::TimeDifference;
+using avd::time::Nanoseconds;
+using avd::time::Microseconds;
+using avd::time::Seconds;
+using avd::time::MonotonicTimePoint;
+using avd::Mutex;
+using avd::LockGuard;
+
+namespace avd {
+
+namespace {
+
+// These functions assume that there is at least one suitable element inside
+// the multiset.
+template <class T>
+void MultisetDeleteOne(std::multiset<T>* mset, const T& key) {
+  mset->erase(mset->find(key));
+}
+template <class T>
+const T& MultisetMin(const std::multiset<T>& mset) {
+  return *mset.begin();
+}
+template <class T>
+const T& MultisetMax(const std::multiset<T>& mset) {
+  return *mset.rbegin();
+}
+
+}  // namespace
+
+StatsKeeper::StatsKeeper(TimeDifference timespan,
+                         int64_t vsync_base,
+                         int32_t vsync_period)
+    : period_length_(timespan, 1),
+      num_layers_(0),
+      num_hwcomposited_layers_(0),
+      num_prepare_calls_(0),
+      num_set_calls_(0),
+      prepare_call_total_time_(0),
+      set_call_total_time_(0),
+      total_layers_area(0),
+      total_invisible_area(0),
+      vsync_base_(vsync_base),
+      vsync_period_(vsync_period) {
+  last_composition_stats_.num_prepare_calls = 0;
+}
+
+StatsKeeper::~StatsKeeper() {}
+
+void StatsKeeper::RecordPrepareStart(int num_layers) {
+  last_composition_stats_.num_layers = num_layers;
+  last_composition_stats_.num_prepare_calls++;
+  num_prepare_calls_++;
+  last_composition_stats_.prepare_start = MonotonicTimePoint::Now();
+  // Calculate the (expected) time of last VSYNC event. We can only make a guess about it because
+  // the vsync thread could run late or surfaceflinger could run late and call prepare from a
+  // previous vsync cycle.
+  int64_t last_vsync =
+      Nanoseconds(last_composition_stats_.set_start.SinceEpoch()).count();
+  last_vsync -= (last_vsync - vsync_base_) % vsync_period_;
+  last_composition_stats_.last_vsync = MonotonicTimePoint() + Nanoseconds(last_vsync);
+}
+
+void StatsKeeper::RecordPrepareEnd(int num_hwcomposited_layers) {
+  last_composition_stats_.prepare_end = MonotonicTimePoint::Now();
+  last_composition_stats_.num_hwc_layers = num_hwcomposited_layers;
+}
+
+void StatsKeeper::RecordSetStart() {
+  last_composition_stats_.set_start = MonotonicTimePoint::Now();
+}
+
+void StatsKeeper::RecordSetEnd() {
+  last_composition_stats_.set_end = MonotonicTimePoint::Now();
+  LockGuard<Mutex> lock(mutex_);
+  num_set_calls_++;
+  while (!raw_composition_data_.empty() &&
+         period_length_ < last_composition_stats_.set_end -
+                              raw_composition_data_.front().time_point()) {
+    const CompositionData& front = raw_composition_data_.front();
+
+    num_prepare_calls_ -= front.num_prepare_calls();
+    --num_set_calls_;
+    num_layers_ -= front.num_layers();
+    num_hwcomposited_layers_ -= front.num_hwcomposited_layers();
+    prepare_call_total_time_ =
+        Nanoseconds(prepare_call_total_time_ - front.prepare_time());
+    set_call_total_time_ =
+        Nanoseconds(set_call_total_time_ - front.set_calls_time());
+
+    MultisetDeleteOne(&prepare_calls_per_set_calls_, front.num_prepare_calls());
+    MultisetDeleteOne(&layers_per_compositions_, front.num_layers());
+    MultisetDeleteOne(&prepare_call_times_, front.prepare_time());
+    MultisetDeleteOne(&set_call_times_, front.set_calls_time());
+    if (front.num_hwcomposited_layers() != 0) {
+      MultisetDeleteOne(
+          &set_call_times_per_hwcomposited_layer_ns_,
+          front.set_calls_time().count() / front.num_hwcomposited_layers());
+    }
+
+    raw_composition_data_.pop_front();
+  }
+  Nanoseconds last_prepare_call_time_(last_composition_stats_.prepare_end -
+                                      last_composition_stats_.prepare_start);
+  Nanoseconds last_set_call_total_time_(last_composition_stats_.set_end -
+                                        last_composition_stats_.set_start);
+  raw_composition_data_.push_back(CompositionData(
+      last_composition_stats_.set_end, last_composition_stats_.num_prepare_calls,
+      last_composition_stats_.num_layers, last_composition_stats_.num_hwc_layers, last_prepare_call_time_,
+      last_set_call_total_time_));
+
+  const CompositionData& back = raw_composition_data_.back();
+
+  // There may be several calls to prepare before a call to set, but the only
+  // valid call is the last one, so we need to compute these here:
+  num_layers_ += last_composition_stats_.num_layers;
+  num_hwcomposited_layers_ += last_composition_stats_.num_hwc_layers;
+  prepare_call_total_time_ =
+      Nanoseconds(prepare_call_total_time_ + last_prepare_call_time_);
+  set_call_total_time_ =
+      Nanoseconds(set_call_total_time_ + last_set_call_total_time_);
+  prepare_calls_per_set_calls_.insert(last_composition_stats_.num_prepare_calls);
+  layers_per_compositions_.insert(last_composition_stats_.num_layers);
+  prepare_call_times_.insert(last_prepare_call_time_);
+  set_call_times_.insert(last_set_call_total_time_);
+  if (last_composition_stats_.num_hwc_layers != 0) {
+    set_call_times_per_hwcomposited_layer_ns_.insert(
+        last_set_call_total_time_.count() / last_composition_stats_.num_hwc_layers);
+  }
+
+  // Reset the counter
+  last_composition_stats_.num_prepare_calls = 0;
+}
+
+void StatsKeeper::SynchronizedDump(char* buffer,
+                                            int buffer_size) const {
+  LockGuard<Mutex> lock(mutex_);
+  int chars_written = 0;
+// Make sure there is enough space to write the next line
+#define bprintf(...)                                                           \
+  (chars_written += (chars_written < buffer_size)                              \
+                        ? (snprintf(&buffer[chars_written],                    \
+                                    buffer_size - chars_written, __VA_ARGS__)) \
+                        : 0)
+
+  bprintf(
+      "HWComposer stats from the %lld seconds just before the last call to "
+      "set() (which happended %lld seconds ago):\n",
+      Seconds(period_length_).count(),
+      Seconds(MonotonicTimePoint::Now() - last_composition_stats_.set_end).count());
+  bprintf("  Layer count: %d\n", num_layers_);
+
+  if (num_layers_ == 0 || num_prepare_calls_ == 0 || num_set_calls_ == 0) {
+    return;
+  }
+
+  bprintf("  Layers composited by hwcomposer: %d (%d%%)\n",
+          num_hwcomposited_layers_,
+          100 * num_hwcomposited_layers_ / num_layers_);
+  bprintf("  Number of calls to prepare(): %d\n", num_prepare_calls_);
+  bprintf("  Number of calls to set(): %d\n", num_set_calls_);
+  if (num_set_calls_ > 0) {
+    bprintf(
+        "  Maximum number of calls to prepare() before a single call to set(): "
+        "%d\n",
+        MultisetMax(prepare_calls_per_set_calls_));
+  }
+  bprintf(
+      "  Time spent on prepare() (in microseconds):\n    max: %lld\n    "
+      "average: %lld\n    min: %lld\n    total: %lld\n",
+      Microseconds(MultisetMax(prepare_call_times_)).count(),
+      Microseconds(prepare_call_total_time_).count() / num_prepare_calls_,
+      Microseconds(MultisetMin(prepare_call_times_)).count(),
+      Microseconds(prepare_call_total_time_).count());
+  bprintf(
+      "  Time spent on set() (in microseconds):\n    max: %lld\n    average: "
+      "%lld\n    min: %lld\n    total: %lld\n",
+      Microseconds(MultisetMax(set_call_times_)).count(),
+      Microseconds(set_call_total_time_).count() / num_set_calls_,
+      Microseconds(MultisetMin(set_call_times_)).count(),
+      Microseconds(set_call_total_time_).count());
+  if (num_hwcomposited_layers_ > 0) {
+    bprintf(
+        "  Per layer compostition time:\n    max: %lld\n    average: %lld\n    "
+        "min: %lld\n",
+        Microseconds(MultisetMax(set_call_times_per_hwcomposited_layer_ns_))
+            .count(),
+        Microseconds(set_call_total_time_).count() / num_hwcomposited_layers_,
+        Microseconds(MultisetMin(set_call_times_per_hwcomposited_layer_ns_))
+            .count());
+  }
+  bprintf("Statistics from last 100 compositions:\n");
+  bprintf("  Total area: %lld square pixels\n", total_layers_area);
+  if (total_layers_area != 0) {
+    bprintf("  Total invisible area: %lld square pixels, %lld%%\n",
+            total_invisible_area,
+            100 * total_invisible_area / total_layers_area);
+  }
+#undef bprintf
+}
+
+}  // namespace avd
diff --git a/guest/hals/hwcomposer/legacy/stats_keeper.h b/guest/hals/hwcomposer/legacy/stats_keeper.h
new file mode 100644
index 0000000..1a9dd06
--- /dev/null
+++ b/guest/hals/hwcomposer/legacy/stats_keeper.h
@@ -0,0 +1,191 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef GCE_HWCOMPOSER_STATS_H_
+#define GCE_HWCOMPOSER_STATS_H_
+
+#include <GceFrameBufferControl.h>
+#include <MonotonicTime.h>
+#include <Pthread.h>
+#include <android-base/thread_annotations.h>
+#include <deque>
+#include <set>
+
+#include "hwcomposer_common.h"
+
+namespace avd {
+
+class CompositionData {
+ public:
+  CompositionData(avd::time::MonotonicTimePoint time_point,
+                  int num_prepares, int num_layers, int num_hwcomposited_layers,
+                  avd::time::Nanoseconds prepare_time,
+                  avd::time::Nanoseconds set_calls_time)
+      : time_point_(time_point),
+        num_prepare_calls_(num_prepares),
+        num_layers_(num_layers),
+        num_hwcomposited_layers_(num_hwcomposited_layers),
+        prepare_time_(prepare_time),
+        set_calls_time_(set_calls_time) {}
+
+  avd::time::MonotonicTimePoint time_point() const {
+    return time_point_;
+  }
+
+  int num_prepare_calls() const { return num_prepare_calls_; }
+
+  int num_layers() const { return num_layers_; }
+
+  int num_hwcomposited_layers() const { return num_hwcomposited_layers_; }
+
+  avd::time::Nanoseconds prepare_time() const {
+    return prepare_time_;
+  }
+
+  avd::time::Nanoseconds set_calls_time() const {
+    return set_calls_time_;
+  }
+
+ private:
+  avd::time::MonotonicTimePoint time_point_;
+  int num_prepare_calls_;
+  int num_layers_;
+  int num_hwcomposited_layers_;
+  avd::time::Nanoseconds prepare_time_;
+  avd::time::Nanoseconds set_calls_time_;
+};
+
+class StatsKeeper {
+ public:
+  // The timespan parameter indicates for how long we keep stats about the past
+  // compositions.
+  StatsKeeper(avd::time::TimeDifference timespan,
+              int64_t vsync_base,
+              int32_t vsync_period);
+  StatsKeeper();
+  ~StatsKeeper();
+
+  // Record the time at which a call to prepare was made, takes the number of
+  // layers received (excluding the framebuffer) as a parameter.
+  void RecordPrepareStart(int num_layers);
+  // Record the time at which a call to prepare (was about to) returned, takes
+  // the number of layers marked for hardware composition as a parameter.
+  void RecordPrepareEnd(int num_hwcomposited_layers);
+  void RecordSetStart();
+  void RecordSetEnd() EXCLUDES(mutex_);
+
+  const CompositionStats& last_composition_stats() { return last_composition_stats_; }
+
+  // Calls to this function are synchronized with calls to 'RecordSetEnd' with a
+  // mutex. The other Record* functions do not need such synchronization because
+  // they access last_* variables only, which are not read by 'Dump'.
+  void SynchronizedDump(char* buffer, int buffer_size) const EXCLUDES(mutex_);
+
+ private:
+
+  avd::time::TimeDifference period_length_;
+
+  // Base and period of the VSYNC signal, allows to accurately calculate the
+  // time of the last vsync broadcast.
+  int64_t vsync_base_;
+  int32_t vsync_period_;
+  // Data collected about ongoing composition. These variables are not accessed
+  // from Dump(), so they don't need to be guarded by a mutex.
+  CompositionStats last_composition_stats_;
+
+  // Aggregated performance data collected from past compositions. These
+  // variables are modified when a composition is completed and when old
+  // compositions need to be discarded in RecordSetEnd(), and is accessed from
+  // Dump(). Non-aggregated data is kept in the raw_composition_data_ deque to
+  // be able to discard old values from the aggregated data.
+  int num_layers_ GUARDED_BY(mutex_);
+  int num_hwcomposited_layers_ GUARDED_BY(mutex_);
+  int num_prepare_calls_ GUARDED_BY(mutex_);
+  int num_set_calls_ GUARDED_BY(mutex_);
+  avd::time::Nanoseconds prepare_call_total_time_ GUARDED_BY(mutex_);
+  avd::time::Nanoseconds set_call_total_time_ GUARDED_BY(mutex_);
+  // These are kept in multisets to be able to calculate mins and maxs of
+  // changing sets of (not necessarily different) values.
+  std::multiset<int> prepare_calls_per_set_calls_ GUARDED_BY(mutex_);
+  std::multiset<int> layers_per_compositions_ GUARDED_BY(mutex_);
+  std::multiset<avd::time::Nanoseconds> prepare_call_times_
+      GUARDED_BY(mutex_);
+  std::multiset<avd::time::Nanoseconds> set_call_times_
+      GUARDED_BY(mutex_);
+  std::multiset<int64_t> set_call_times_per_hwcomposited_layer_ns_
+      GUARDED_BY(mutex_);
+
+  // Time-ordered list of compositions, used to update the global aggregated
+  // performance data when old compositions fall out of the period of interest.
+  std::deque<CompositionData> raw_composition_data_ GUARDED_BY(mutex_);
+
+  // TODO(jemoreira): Add min/max/average composition times per layer area units
+
+  std::deque<std::pair<int64_t, int64_t> > composition_areas GUARDED_BY(mutex_);
+  int64_t total_layers_area GUARDED_BY(mutex_);
+  int64_t total_invisible_area GUARDED_BY(mutex_);
+
+  // Controls access to data from past compositions.
+  mutable avd::Mutex mutex_;
+};
+
+template <class Composer>
+class StatsKeepingComposer {
+ public:
+  // Keep stats from the last 10 seconds.
+  StatsKeepingComposer(int64_t vsync_base_timestamp, int32_t vsync_period_ns)
+      : composer_(vsync_base_timestamp, vsync_period_ns),
+        stats_keeper_(avd::time::TimeDifference(avd::time::Seconds(10), 1),
+                      vsync_base_timestamp,
+                      vsync_period_ns) {
+    // Don't let the composer broadcast by itself, allow it to return to collect
+    // the timings and broadcast then.
+    composer_.ReplaceFbBroadcaster(NULL);
+  }
+  ~StatsKeepingComposer() {}
+
+  int PrepareLayers(size_t num_layers, gce_hwc_layer* layers) {
+    stats_keeper_.RecordPrepareStart(num_layers);
+    int num_hwc_layers = composer_.PrepareLayers(num_layers, layers);
+    stats_keeper_.RecordPrepareEnd(num_hwc_layers);
+    return num_hwc_layers;
+  }
+
+  int SetLayers(size_t num_layers, gce_hwc_layer* layers) {
+    stats_keeper_.RecordSetStart();
+    int yoffset = composer_.SetLayers(num_layers, layers);
+    stats_keeper_.RecordSetEnd();
+    if (yoffset >= 0) {
+      GceFrameBufferControl::getInstance().BroadcastFrameBufferChanged(
+          yoffset, &stats_keeper_.last_composition_stats());
+    } else {
+      ALOGE("%s: Error on SetLayers(), yoffset: %d", __FUNCTION__, yoffset);
+    }
+    return yoffset;
+  }
+
+  void Dump(char* buff, int buff_len) {
+    stats_keeper_.SynchronizedDump(buff, buff_len);
+  }
+
+ private:
+  StatsKeeper stats_keeper_;
+  Composer composer_;
+};
+
+}  // namespace avd
+
+#endif
diff --git a/guest/libs/legacy_framebuffer/Android.mk b/guest/libs/legacy_framebuffer/Android.mk
new file mode 100644
index 0000000..b150b2b
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/Android.mk
@@ -0,0 +1,92 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+gceframebuffer_common_src_files := \
+    GceFrameBuffer.cpp \
+    GceFrameBufferControl.cpp \
+    RegionRegistry.cpp
+
+gceframebuffer_common_c_flags := -Wall -Werror $(GCE_VERSION_CFLAGS)
+
+gceframebuffer_common_c_includes := \
+    device/google/gce/include
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libgceframebuffer
+LOCAL_VENDOR_MODULE := true
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := ${gceframebuffer_common_src_files}
+LOCAL_CFLAGS += ${gceframebuffer_common_c_flags}
+LOCAL_C_INCLUDES := ${gceframebuffer_common_c_includes} \
+    $(GCE_STLPORT_INCLUDES)
+
+LOCAL_STATIC_LIBRARIES := \
+    libgcemetadata \
+    libgcecutils \
+    libjsoncpp
+
+LOCAL_SHARED_LIBRARIES := \
+    liblog \
+    libutils \
+    libcutils \
+    $(GCE_STLPORT_LIBS)
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE := libgceframebuffer
+LOCAL_MODULE_TAGS := optional
+
+LOCAL_SRC_FILES := ${gceframebuffer_common_src_files}
+LOCAL_CFLAGS += ${gceframebuffer_common_c_flags}
+LOCAL_C_INCLUDES := ${gceframebuffer_common_c_includes}
+
+LOCAL_STATIC_LIBRARIES := \
+    libgcemetadata \
+    libjsoncpp \
+    liblog \
+    libutils \
+    libcutils \
+    $(GCE_STLPORT_LIBS)
+
+include $(BUILD_STATIC_LIBRARY)
+
+# This is needed only before M
+# By M STLport is completely replaces with libcxx
+ifneq (,$(filter $(PLATFORM_SDK_VERSION),16 17 18 19 21 22))
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := ${gceframebuffer_common_src_files}
+LOCAL_CFLAGS += ${gceframebuffer_common_c_flags}
+LOCAL_C_INCLUDES := ${gceframebuffer_common_c_includes}
+
+LOCAL_STATIC_LIBRARIES += \
+    libgcemetadata_cxx \
+    libjsoncpp_cxx \
+    libgcecutils_cxx \
+    libutils \
+    libcutils \
+    liblog
+
+LOCAL_MULTILIB := first
+LOCAL_MODULE := libgceframebuffer_cxx
+LOCAL_MODULE_TAGS := optional
+include external/libcxx/libcxx.mk
+include $(BUILD_STATIC_LIBRARY)
+endif
diff --git a/guest/libs/legacy_framebuffer/DisplayProperties.h b/guest/libs/legacy_framebuffer/DisplayProperties.h
new file mode 100644
index 0000000..a1d4efc
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/DisplayProperties.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+// Google Compute Engine (GCE) Camera HAL - Initial metadata reader.
+#ifndef DEVICE_GOOGLE_GCE_HEADLESS_DISPLAY_PROPERTIES_H_
+#define DEVICE_GOOGLE_GCE_HEADLESS_DISPLAY_PROPERTIES_H_
+
+#include <AutoResources.h>
+
+namespace avd {
+
+class DisplayProperties {
+ public:
+  DisplayProperties() :
+      x_res_(1280),
+      y_res_(720),
+      bits_per_pixel_(32),
+      dpi_(160),
+      default_(true) {
+        config_.SetToString("1280x720x32x160");
+      }
+
+  void Parse(const char* value);
+
+  int GetXRes() const { return x_res_; }
+  int GetYRes() const { return y_res_; }
+  int GetBitsPerPixel() const { return bits_per_pixel_; }
+  int GetDpi() const { return dpi_; }
+  bool IsDefault() const { return default_; }
+  const char* GetConfig() const { return config_.data(); }
+
+ private:
+  // Screen width in pixels
+  int x_res_;
+  // Screen height in pixels
+  int y_res_;
+  // Depth of the screen (obsolete)
+  int bits_per_pixel_;
+  // Pixels per inch
+  int dpi_;
+  // Default
+  bool default_;
+  // Unparsed configuration
+  AutoFreeBuffer config_;
+};
+
+}  // namespace avd
+#endif  // DEVICE_GOOGLE_GCE_HEADLESS_DISPLAY_PROPERTIES_H_
diff --git a/guest/libs/legacy_framebuffer/GceFrameBuffer.cpp b/guest/libs/legacy_framebuffer/GceFrameBuffer.cpp
new file mode 100644
index 0000000..2005419
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/GceFrameBuffer.cpp
@@ -0,0 +1,211 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <api_level_fixes.h>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <utils/String8.h>
+
+#define LOG_TAG "GceFrameBuffer"
+#include <cutils/log.h>
+#include <system/graphics.h>
+#include "GceMetadataAttributes.h"
+#include "InitialMetadataReader.h"
+
+#include "GceFrameBuffer.h"
+
+
+const char* const GceFrameBuffer::kFrameBufferPath =
+    "/dev/userspace_framebuffer";
+
+
+const GceFrameBuffer & GceFrameBuffer::getInstance() {
+  static GceFrameBuffer instance;
+  instance.Configure();
+  return instance;
+}
+
+
+GceFrameBuffer::GceFrameBuffer()
+    : line_length_(-1) { }
+
+
+void GceFrameBuffer::Configure() {
+  const char* metadata_value =
+      avd::InitialMetadataReader::getInstance()->GetValueForKey(
+          GceMetadataAttributes::kDisplayConfigurationKey);
+  display_properties_.Parse(metadata_value);
+  line_length_ = align(
+      display_properties_.GetXRes() * (
+          display_properties_.GetBitsPerPixel() / 8));
+}
+
+
+bool GceFrameBuffer::OpenFrameBuffer(int* frame_buffer_fd) {
+  int fb_fd;
+  if ((fb_fd = open(GceFrameBuffer::kFrameBufferPath, O_RDWR)) < 0) {
+    SLOGE("Failed to open '%s' (%s)",
+          GceFrameBuffer::kFrameBufferPath, strerror(errno));
+    return false;
+  }
+
+  const GceFrameBuffer& config = GceFrameBuffer::getInstance();
+
+  if (ftruncate(fb_fd, config.total_buffer_size()) < 0) {
+    SLOGE("Failed to truncate framebuffer (%s)", strerror(errno));
+    return false;
+  }
+
+  *frame_buffer_fd = fb_fd;
+  return true;
+}
+
+
+bool GceFrameBuffer::OpenAndMapFrameBuffer(void** fb_memory,
+                                                  int* frame_buffer_fd) {
+  int fb_fd;
+  if (!GceFrameBuffer::OpenFrameBuffer(&fb_fd)) { return false; }
+
+  size_t fb_size = GceFrameBuffer::getInstance().total_buffer_size();
+
+  void* mmap_res = mmap(0, fb_size, PROT_READ, MAP_SHARED, fb_fd, 0);
+  if (mmap_res == MAP_FAILED) {
+    SLOGE("Failed to mmap framebuffer (%s)", strerror(errno));
+    close(fb_fd);
+    return false;
+  }
+
+  // Modify the pointers only after mmap succeeds.
+  *fb_memory = mmap_res;
+  *frame_buffer_fd = fb_fd;
+
+  return true;
+}
+
+bool GceFrameBuffer::UnmapAndCloseFrameBuffer(void* fb_memory,
+                                                    int frame_buffer_fd) {
+  size_t fb_size = GceFrameBuffer::getInstance().total_buffer_size();
+  return munmap(fb_memory, fb_size) == 0 && close(frame_buffer_fd) == 0;
+}
+
+
+int GceFrameBuffer::hal_format() const {
+  switch(display_properties_.GetBitsPerPixel()) {
+    case 32:
+      if (kRedShift) {
+        return HAL_PIXEL_FORMAT_BGRA_8888;
+      } else {
+        return HAL_PIXEL_FORMAT_RGBX_8888;
+      }
+    default:
+      return HAL_PIXEL_FORMAT_RGB_565;
+  }
+}
+
+const char* pixel_format_to_string(int format) {
+  switch (format) {
+    // Formats that are universal across versions
+    case HAL_PIXEL_FORMAT_RGBA_8888:
+      return "RGBA_8888";
+    case HAL_PIXEL_FORMAT_RGBX_8888:
+      return "RGBX_8888";
+    case HAL_PIXEL_FORMAT_BGRA_8888:
+      return "BGRA_8888";
+    case HAL_PIXEL_FORMAT_RGB_888:
+      return "RGB_888";
+    case HAL_PIXEL_FORMAT_RGB_565:
+      return "RGB_565";
+    case HAL_PIXEL_FORMAT_YV12:
+      return "YV12";
+    case HAL_PIXEL_FORMAT_YCrCb_420_SP:
+      return "YCrCb_420_SP";
+    case HAL_PIXEL_FORMAT_YCbCr_422_SP:
+      return "YCbCr_422_SP";
+    case HAL_PIXEL_FORMAT_YCbCr_422_I:
+      return "YCbCr_422_I";
+
+#if GCE_PLATFORM_SDK_AFTER(J)
+    // First supported on JBMR1 (API 17)
+    case HAL_PIXEL_FORMAT_IMPLEMENTATION_DEFINED:
+      return "IMPLEMENTATION_DEFINED";
+    case HAL_PIXEL_FORMAT_BLOB:
+      return "BLOB";
+#endif
+#if GCE_PLATFORM_SDK_AFTER(J_MR1)
+    // First supported on JBMR2 (API 18)
+    case HAL_PIXEL_FORMAT_YCbCr_420_888:
+      return "YCbCr_420_888";
+    case HAL_PIXEL_FORMAT_Y8:
+      return "Y8";
+    case HAL_PIXEL_FORMAT_Y16:
+      return "Y16";
+#endif
+#if GCE_PLATFORM_SDK_AFTER(K)
+    // Support was added in L (API 21)
+    case HAL_PIXEL_FORMAT_RAW_OPAQUE:
+      return "RAW_OPAQUE";
+    // This is an alias for RAW_SENSOR in L and replaces it in M.
+    case HAL_PIXEL_FORMAT_RAW16:
+      return "RAW16";
+    case HAL_PIXEL_FORMAT_RAW10:
+      return "RAW10";
+#endif
+#if GCE_PLATFORM_SDK_AFTER(L_MR1)
+    case HAL_PIXEL_FORMAT_YCbCr_444_888:
+      return "YCbCr_444_888";
+    case HAL_PIXEL_FORMAT_YCbCr_422_888:
+      return "YCbCr_422_888";
+    case HAL_PIXEL_FORMAT_RAW12:
+      return "RAW12";
+    case HAL_PIXEL_FORMAT_FLEX_RGBA_8888:
+      return "FLEX_RGBA_8888";
+    case HAL_PIXEL_FORMAT_FLEX_RGB_888:
+      return "FLEX_RGB_888";
+#endif
+
+    // Formats that have been removed
+#if GCE_PLATFORM_SDK_BEFORE(K)
+    // Support was dropped on K (API 19)
+    case HAL_PIXEL_FORMAT_RGBA_5551:
+      return "RGBA_5551";
+    case HAL_PIXEL_FORMAT_RGBA_4444:
+      return "RGBA_4444";
+#endif
+#if GCE_PLATFORM_SDK_BEFORE(L)
+    // Renamed to RAW_16 in L. Both were present for L, but it was completely
+    // removed in M.
+    case HAL_PIXEL_FORMAT_RAW_SENSOR:
+      return "RAW_SENSOR";
+#endif
+#if GCE_PLATFORM_SDK_AFTER(J_MR2) && GCE_PLATFORM_SDK_BEFORE(M)
+    // Supported K, L, and LMR1. Not supported on JBMR0, JBMR1, JBMR2, and M
+    case HAL_PIXEL_FORMAT_sRGB_X_8888:
+      return "sRGB_X_8888";
+    case HAL_PIXEL_FORMAT_sRGB_A_8888:
+      return "sRGB_A_8888";
+#endif
+  }
+  return "UNKNOWN";
+}
diff --git a/guest/libs/legacy_framebuffer/GceFrameBuffer.h b/guest/libs/legacy_framebuffer/GceFrameBuffer.h
new file mode 100644
index 0000000..dea3dfa
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/GceFrameBuffer.h
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef GCE_FRAME_BUFFER_H_
+#define GCE_FRAME_BUFFER_H_
+
+#include <DisplayProperties.h>
+#include <UniquePtr.h>
+#include <pthread.h>
+#include <sys/mman.h>
+#include <climits>
+
+struct private_handle_t;
+struct remoter_request_packet;
+
+inline size_t roundUpToPageSize(size_t x) {
+  return (x + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1);
+}
+
+class GceFrameBuffer {
+public:
+  static const GceFrameBuffer& getInstance();
+
+  static int align(int input, int alignment = kAlignment) {
+    return (input + alignment - 1) & -alignment;
+  }
+
+  int bits_per_pixel() const {
+    return display_properties_.GetBitsPerPixel();
+  }
+
+  size_t bufferSize() const {
+    return line_length_ * display_properties_.GetYRes();
+  }
+
+  int dpi() const {
+    return display_properties_.GetDpi();
+  }
+
+  int hal_format() const;
+
+  int line_length() const { return line_length_; }
+
+  int total_buffer_size() const {
+    return roundUpToPageSize(line_length_ * y_res_virtual() +
+                             GceFrameBuffer::kSwiftShaderPadding);
+  }
+
+  int x_res() const {
+    return display_properties_.GetXRes();
+  }
+
+  int y_res() const {
+    return display_properties_.GetYRes();
+  }
+
+  int y_res_virtual() const {
+    return display_properties_.GetYRes() * kNumBuffers;
+  }
+
+  static const int kAlignment = 8;
+  static const int kNumHwcBuffers = 3;
+  // Without sync fences enabled surfaceflinger uses only 2 framebuffers,
+  // regardless of how many are available
+  static const int kNumSfBuffers = 3;
+  static const int kNumBuffers = kNumHwcBuffers + kNumSfBuffers;
+  static const char* const kFrameBufferPath;
+
+  static const int kRedShift = 0;
+  static const int kRedBits = 8;
+  static const int kGreenShift = 8;
+  static const int kGreenBits = 8;
+  static const int kBlueShift = 16;
+  static const int kBlueBits = 8;
+  static const int kAlphaShift = 24;
+  static const int kAlphaBits = 8;
+  typedef uint32_t Pixel;
+  static const int kSwiftShaderPadding = 4;
+
+  // Opens the framebuffer file. Ensures the file has the appropriate size by
+  // calling ftruncate.
+  static bool OpenFrameBuffer(int* frame_buffer_fd);
+
+  // Maps the framebuffer into memory. It's the caller's responsibility to
+  // unmap the memory and close the file when done.
+  static bool OpenAndMapFrameBuffer(void** fb_memory, int* frame_buffer_fd);
+  static bool UnmapAndCloseFrameBuffer(void* fb_memory, int frame_buffer_fd);
+
+private:
+  GceFrameBuffer();
+  void Configure();
+
+  avd::DisplayProperties display_properties_;
+  static const int kBitsPerPixel = sizeof(Pixel) * CHAR_BIT;
+  // Length of a scan-line in bytes.
+  int line_length_;
+  DISALLOW_COPY_AND_ASSIGN(GceFrameBuffer);
+};
+
+const char* pixel_format_to_string(int format);
+
+#endif
diff --git a/guest/libs/legacy_framebuffer/GceFrameBufferControl.cpp b/guest/libs/legacy_framebuffer/GceFrameBufferControl.cpp
new file mode 100644
index 0000000..c0fa84d
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/GceFrameBufferControl.cpp
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <errno.h>
+#include <fcntl.h>
+#include <pwd.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/time.h>
+#include <unistd.h>
+
+#include <utils/String8.h>
+
+#define LOG_TAG "GceFrameBufferControl"
+#include <cutils/log.h>
+#include <system/graphics.h>
+
+#include "GceFrameBufferControl.h"
+#include "gralloc_gce_priv.h"
+
+enum { NOT_YET = 0, IN_PROGRESS, DONE };
+
+struct FrameBufferControl {
+  pthread_mutex_t mutex;
+  pthread_cond_t cond_var;
+  uint32_t seq_num;
+  volatile int yoffset;
+  volatile int initialized;
+  volatile uint32_t buffer_bits;
+  CompositionStats stats;
+};
+
+// __sync_lock_test_and_set is described to work on intel, but not on many other
+// targets.
+#define ATOMICALLY_SET(x, val) __sync_lock_test_and_set(&(x), (val))
+#define ATOMICALLY_COMPARE_AND_SWAP(x, old, val) \
+  __sync_val_compare_and_swap(&(x), (old), (val))
+// fetch the value, don't modify it (or with 0)
+#define ATOMICALLY_GET(x) __sync_or_and_fetch(&(x), 0)
+
+const char* const GceFrameBufferControl::kFrameBufferControlPath =
+    "/dev/framebuffer_control";
+
+GceFrameBufferControl& GceFrameBufferControl::getInstance() {
+  static GceFrameBufferControl instance;
+  // If not initialized before and fails to initialize now
+  if (!instance.Initialize()) {
+    LOG_ALWAYS_FATAL(
+        "Unable to initialize the framebuffer control structure (%s)... "
+        "aborting!",
+        strerror(errno));
+  }
+  return instance;
+}
+
+uint32_t GceFrameBufferControl::GetAndSetNextAvailableBufferBit(uint32_t filter) {
+  if (pthread_mutex_lock(&control_memory_->mutex)) {
+    ALOGE("Failed to acquire lock on framebuffer control mutex (%s) - %s",
+          strerror(errno), __FUNCTION__);
+    return 0;
+  }
+  uint32_t bit = control_memory_->buffer_bits;
+  bit &= filter;
+  if (bit == filter) {
+    // All bits in the filter are already set in the set
+    bit = 0LU;
+  } else {
+    // Set available bits to 1
+    bit = (bit^filter);
+    // isolate first available bit
+    bit &= ~bit + 1LU;
+    // set it on bit set on shared memory
+    control_memory_->buffer_bits |= bit;
+  }
+
+  pthread_mutex_unlock(&control_memory_->mutex);
+  return bit;
+}
+
+int GceFrameBufferControl::UnsetBufferBits(uint32_t bits) {
+  if (pthread_mutex_lock(&control_memory_->mutex)) {
+    ALOGE("Failed to acquire lock on framebuffer control mutex (%s) - %s",
+          strerror(errno), __FUNCTION__);
+    return -1;
+  }
+
+  control_memory_->buffer_bits &= ~bits;
+
+  pthread_mutex_unlock(&control_memory_->mutex);
+  return 0;
+}
+
+GceFrameBufferControl::GceFrameBufferControl()
+    : control_fd_(-1), control_memory_(NULL) {}
+
+namespace {
+
+bool MapFrameBufferControl(FrameBufferControl** control_memory_ptr,
+                           int* fbc_fd) {
+  size_t control_size = sizeof(FrameBufferControl);
+  mode_t fb_mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH;
+  int control_fd;
+  if ((control_fd = open(GceFrameBufferControl::kFrameBufferControlPath, O_RDWR,
+                         fb_mode)) < 0) {
+    ALOGE("Failed to open framebuffer control at %s (%s)",
+          GceFrameBufferControl::kFrameBufferControlPath, strerror(errno));
+    return false;
+  }
+
+  if (ftruncate(control_fd, sizeof(FrameBufferControl)) < 0) {
+    ALOGE("Failed to truncate framebuffer control at %s (%s)",
+          GceFrameBufferControl::kFrameBufferControlPath, strerror(errno));
+    return false;
+  }
+
+  void* control_memory;
+  control_memory =
+      mmap(0, control_size, PROT_READ | PROT_WRITE, MAP_SHARED, control_fd, 0);
+  if (control_memory == MAP_FAILED) {
+    ALOGE("Failed to mmap framebuffer control (%s)", strerror(errno));
+    close(control_fd);
+    return false;
+  }
+
+  *control_memory_ptr = reinterpret_cast<FrameBufferControl*>(control_memory);
+  *fbc_fd = control_fd;
+
+  return true;
+}
+
+void UnmapFrameBufferControl(FrameBufferControl** control_memory_ptr,
+                             int* fbc_fd) {
+  munmap(*control_memory_ptr, sizeof(FrameBufferControl));
+  *control_memory_ptr = NULL;
+  close(*fbc_fd);
+  *fbc_fd = -1;
+}
+}
+
+bool GceFrameBufferControl::Initialize() {
+  if (control_fd_ >= 0) {
+    return true;
+  }
+
+  if (!MapFrameBufferControl(&control_memory_, &control_fd_)) {
+    return false;
+  }
+
+  int initializing_state = ATOMICALLY_COMPARE_AND_SWAP(
+      control_memory_->initialized, NOT_YET, IN_PROGRESS);
+  switch (initializing_state) {
+    case DONE:
+      return true;
+
+    case IN_PROGRESS: {  // wait 1 sec and try again
+      do {
+        sleep(1);
+        initializing_state = ATOMICALLY_GET(control_memory_->initialized);
+        if (initializing_state != DONE) {
+          ALOGW(
+              "Framebuffer control structure has not yet been initialized "
+              "after one second. Value of initialized flag: %d",
+              initializing_state);
+        }
+      } while (initializing_state != DONE);
+      return true;
+    }
+
+    case NOT_YET: {  // flag set to IN_PROGRESS, proceed to initialize
+      pthread_mutexattr_t mutex_attr;
+      pthread_mutexattr_init(&mutex_attr);
+      pthread_mutexattr_setpshared(&mutex_attr, PTHREAD_PROCESS_SHARED);
+      int retval = pthread_mutex_init(&(control_memory_->mutex), &mutex_attr);
+      if (retval) {
+        ALOGE("Failed to acquire lock on framebuffer control mutex (%s) - %s",
+              strerror(errno), __FUNCTION__);
+        UnmapFrameBufferControl(&control_memory_, &control_fd_);
+        return false;
+      }
+      pthread_mutexattr_destroy(&mutex_attr);
+
+      pthread_condattr_t cond_attr;
+      pthread_condattr_init(&cond_attr);
+      pthread_condattr_setpshared(&cond_attr, PTHREAD_PROCESS_SHARED);
+      retval = pthread_cond_init(&(control_memory_->cond_var), &cond_attr);
+      if (retval) {
+        ALOGE("Failed to initialize cond var for framebuffer control (%s)",
+              strerror(errno));
+        pthread_mutex_destroy(&(control_memory_->mutex));
+        UnmapFrameBufferControl(&control_memory_, &control_fd_);
+        return false;
+      }
+      pthread_condattr_destroy(&cond_attr);
+
+      ATOMICALLY_SET(control_memory_->buffer_bits, 0LU);
+      ATOMICALLY_SET(control_memory_->seq_num, 0);
+      ATOMICALLY_SET(control_memory_->initialized, DONE);
+
+      return true;
+    }
+
+    default: {  // unrecognized value
+      ALOGE("Framebuffer control memory is corrupt, initialized = %d",
+            initializing_state);
+      UnmapFrameBufferControl(&control_memory_, &control_fd_);
+      return false;
+    }
+  }
+
+  return true;
+}
+
+int GceFrameBufferControl::GetCurrentYOffset() const {
+  if (!control_memory_) return -1;
+  return control_memory_->yoffset;
+}
+
+int GceFrameBufferControl::WaitForFrameBufferChangeSince(
+    uint32_t previous_fb_seq,
+    int* yoffset_p,
+    uint32_t* fb_seq_p,
+    CompositionStats* stats_p) {
+  if (pthread_mutex_lock(&control_memory_->mutex)) {
+    ALOGE("Failed to acquire lock on framebuffer control mutex (%s) - %s",
+          strerror(errno), __FUNCTION__);
+    return -1;
+  }
+  int retval = 0;
+
+  while (control_memory_->seq_num == previous_fb_seq) {
+    retval =
+        pthread_cond_wait(&control_memory_->cond_var, &control_memory_->mutex);
+  }
+
+  if (fb_seq_p) {
+    *fb_seq_p = control_memory_->seq_num;
+  }
+  if (yoffset_p) {
+    *yoffset_p = control_memory_->yoffset;
+  }
+  if (stats_p) {
+    *stats_p = control_memory_->stats;
+  }
+
+  pthread_mutex_unlock(&control_memory_->mutex);
+  return retval;
+}
+
+int GceFrameBufferControl::WaitForFrameBufferChange(int* yoffset_p) {
+  return WaitForFrameBufferChangeSince(
+      control_memory_->seq_num, yoffset_p, NULL, NULL);
+}
+
+int GceFrameBufferControl::BroadcastFrameBufferChanged(int yoffset) {
+  return BroadcastFrameBufferChanged(yoffset, NULL);
+}
+
+// increments the framebuffer sequential number, ensuring it's never zero
+static inline uint32_t seq_inc(uint32_t num) {
+  ++num;
+  return num? num: 1;
+}
+
+int GceFrameBufferControl::BroadcastFrameBufferChanged(
+    int yoffset, const CompositionStats* stats) {
+  if (pthread_mutex_lock(&control_memory_->mutex)) {
+    ALOGE("Failed to acquire lock on framebuffer control mutex (%s)",
+          strerror(errno));
+    return -1;
+  }
+  control_memory_->yoffset = yoffset;
+  control_memory_->seq_num = seq_inc(control_memory_->seq_num);
+  if (stats) { control_memory_->stats = *stats; }
+  pthread_cond_broadcast(&control_memory_->cond_var);
+  pthread_mutex_unlock(&control_memory_->mutex);
+
+  return 0;
+}
diff --git a/guest/libs/legacy_framebuffer/GceFrameBufferControl.h b/guest/libs/legacy_framebuffer/GceFrameBufferControl.h
new file mode 100644
index 0000000..0d6eff7
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/GceFrameBufferControl.h
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef GCE_FRAME_BUFFER_CONTROL_H_
+#define GCE_FRAME_BUFFER_CONTROL_H_
+
+#include <MonotonicTime.h>
+// For DISALLOW_COPY_AND_ASSIGN
+#include <UniquePtr.h>
+
+struct FrameBufferControl;
+
+struct CompositionStats {
+  avd::time::MonotonicTimePoint prepare_start;
+  avd::time::MonotonicTimePoint prepare_end;
+  avd::time::MonotonicTimePoint set_start;
+  avd::time::MonotonicTimePoint set_end;
+  avd::time::MonotonicTimePoint last_vsync;
+  // There may be more than one call to prepare, the timestamps are with regards to the last one (the one that precedes the set call)
+  int num_prepare_calls;
+  int num_layers;
+  // The number of layers composed by the hwcomposer
+  int num_hwc_layers;
+};
+
+class GceFrameBufferControl {
+ public:
+  static GceFrameBufferControl& getInstance();
+
+  static const char* const kFrameBufferControlPath;
+
+  // The framebuffer control structure mantains a bit set to keep track of the
+  // buffers that have been allocated already. This function atomically finds an
+  // unset (0) bit in the set, sets it to 1 and returns it. It will only
+  // consider bits already set in the filter parameter.
+  uint32_t GetAndSetNextAvailableBufferBit(uint32_t filter);
+  // Returns 0 on success
+  int UnsetBufferBits(uint32_t bits);
+
+  // Returns the yoffset of the last framebuffer update or a negative number on
+  // error.
+  int GetCurrentYOffset() const;
+  // Returns the value returned by the pthread_cond_wait, or -1 if the control
+  // structure has not been initialized by the hwcomposer yet.
+  int WaitForFrameBufferChange(int* yoffset_p);
+  // Uses a sequential number to determine whether the client was notified of
+  // the last framebuffer change and therefore needs to wait for a new one or if
+  // it can just return with the last one. It also provides the timings of the
+  // composition. Any NULL input parameters will be ignored. The sequential
+  // numbers are guaranteed to never be zero, so a value of zero can be used to
+  // get the last frame without waiting (useful when we want to get a frame for
+  // the first time).
+  int WaitForFrameBufferChangeSince(uint32_t previous_fb_seq,
+                                    int* yoffset_p,
+                                    uint32_t* fb_seq_p,
+                                    CompositionStats* stats_p);
+
+  // Returns 0 on success, a negative number on error.
+  int BroadcastFrameBufferChanged(int yoffset);
+
+  // Returns 0 on success, a negative number on error.
+  int BroadcastFrameBufferChanged(int yoffset, const CompositionStats* stats);
+
+ private:
+  GceFrameBufferControl();
+
+  // Map the control structure to memory and initialize its contents.
+  bool Initialize();
+
+  // FD for the frame buffer control.
+  int control_fd_;
+  // Pointer to the mapped frame buffer control.
+  FrameBufferControl* control_memory_;
+
+  DISALLOW_COPY_AND_ASSIGN(GceFrameBufferControl);
+};
+
+#endif
diff --git a/guest/libs/legacy_framebuffer/RegionRegistry.cpp b/guest/libs/legacy_framebuffer/RegionRegistry.cpp
new file mode 100644
index 0000000..4fea58b
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/RegionRegistry.cpp
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include <limits.h>
+#include <errno.h>
+#include <pthread.h>
+#include <unistd.h>
+#include <string.h>
+
+#include <sys/mman.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include <cutils/hashmap.h>
+#define LOG_TAG "GceGrallocRegionRegistry"
+#include <cutils/log.h>
+#include <cutils/atomic.h>
+
+#include <linux/ashmem.h>
+
+#include <hardware/hardware.h>
+#include <hardware/gralloc.h>
+#include <system/graphics.h>
+
+#include "gralloc_gce_priv.h"
+
+// TODO(ghartman): Make the configurable through a property
+static const bool g_log_refs = false;
+
+struct GrallocRegion {
+  void* base_;
+  int   num_references_;
+
+  GrallocRegion() : base_(0), num_references_(0) { }
+  // Copy constructors are ok.
+};
+
+
+static const char* get_buffer_name(
+    const private_handle_t* hnd, char output[ASHMEM_NAME_LEN]) {
+  output[0] = '\0';
+  if (!hnd) {
+    ALOGE("Attempted to log gralloc name hnd=NULL");
+    return output;
+  }
+  if (hnd->fd == -1) {
+    ALOGE("Attempted to log gralloc name hnd=%p with fd == -1", hnd);
+    return output;
+  }
+  int rval = ioctl(hnd->fd, ASHMEM_GET_NAME, output);
+  if (rval == -1) {
+    output[0] = '\0';
+  }
+  return output;
+}
+
+
+static int str_hash(void* str) {
+  return hashmapHash(str, strlen(reinterpret_cast<const char*>(str)));
+}
+
+
+static bool str_equal(void* a, void* b) {
+  return strcmp(
+      reinterpret_cast<const char*>(a),
+      reinterpret_cast<const char*>(b)) == 0;
+}
+
+
+static Hashmap* get_regions() {
+  static Hashmap* regionMap = hashmapCreate(19, str_hash, str_equal);
+  return regionMap;
+}
+
+
+static GrallocRegion* lock_region_for_handle(
+    const private_handle_t* hnd, char region_name[ASHMEM_NAME_LEN]) {
+  region_name[0] = '\0';
+  get_buffer_name(hnd, region_name);
+  Hashmap* hash = get_regions();
+  hashmapLock(hash);
+  GrallocRegion* region = reinterpret_cast<GrallocRegion*>(
+      hashmapGet(hash, region_name));
+  if (!region) {
+    region = new GrallocRegion;
+    hashmapPut(hash, strdup(region_name), region);
+  }
+  return region;
+}
+
+
+/* The current implementation uses only a single lock for all regions.
+ * This method takes a region to simplfy the refactoring if we go to
+ * finer-grained locks.
+ */
+static inline void unlock_region(GrallocRegion* ) {
+  hashmapUnlock(get_regions());
+}
+
+
+void* reference_region(const char* op, const private_handle_t* hnd) {
+  char name_buf[ASHMEM_NAME_LEN];
+  GrallocRegion* region = lock_region_for_handle(hnd, name_buf);
+  if (!region->base_) {
+    void* mappedAddress = mmap(
+        0, hnd->total_size, PROT_READ|PROT_WRITE, MAP_SHARED, hnd->fd, 0);
+    if (mappedAddress == MAP_FAILED) {
+      ALOGE("Could not mmap %s", strerror(errno));
+      unlock_region(region);
+      return NULL;
+    }
+    if (!(hnd->flags & private_handle_t::PRIV_FLAGS_FRAMEBUFFER)) {
+      // Set up the guard pages. The last page is always a guard
+      uintptr_t base = uintptr_t(mappedAddress);
+      uintptr_t addr = base + hnd->total_size - PAGE_SIZE;
+      if (mprotect((void*)addr, PAGE_SIZE, PROT_NONE) == -1) {
+        ALOGE("mprotect base=%p, pg=%p failed (%s)",
+              (void*)base, (void*)addr, strerror(errno));
+      }
+    }
+    region->base_ = mappedAddress;
+    ALOGI("Mapped %s hnd=%p fd=%d base=%p format=%s(0x%x) width=%d height=%d",
+          name_buf, hnd, hnd->fd, region->base_,
+          pixel_format_to_string(hnd->format), hnd->format,
+          hnd->x_res, hnd->y_res);
+  }
+
+  void* rval = region->base_;
+  ++region->num_references_;
+  ALOGI_IF(g_log_refs, "Referencing name=%s op=%s addr=%p new numRefs=%d",
+        name_buf, op, region->base_, region->num_references_);
+  unlock_region(region);
+  return rval;
+}
+
+
+int unreference_region(const char* op, const private_handle_t* hnd) {
+  char name_buf[ASHMEM_NAME_LEN];
+
+  GrallocRegion* region = lock_region_for_handle(hnd, name_buf);
+  if (!region->base_) {
+    ALOGE("Unmapping region with no map hnd=%p", hnd);
+    unlock_region(region);
+    return -1;
+  }
+  if (region->num_references_ < 1) {
+    ALOGE(
+        "unmap with hnd=%p, numReferences=%d", hnd, region->num_references_);
+    unlock_region(region);
+    return -1;
+  }
+  --region->num_references_;
+  if (!region->num_references_) {
+    ALOGI("Unmapped %s hnd=%p fd=%d base=%p", name_buf, hnd,
+          hnd->fd, region->base_);
+    if (munmap(region->base_, hnd->total_size) < 0) {
+      ALOGE("Could not unmap %s", strerror(errno));
+    }
+    region->base_ = 0;
+  }
+  ALOGI_IF(g_log_refs, "Unreferencing name=%s op=%s addr=%p new numRefs=%d",
+        name_buf, op, region->base_, region->num_references_);
+  unlock_region(region);
+  return 0;
+}
diff --git a/guest/libs/legacy_framebuffer/RegionRegistry.h b/guest/libs/legacy_framebuffer/RegionRegistry.h
new file mode 100644
index 0000000..1aded12
--- /dev/null
+++ b/guest/libs/legacy_framebuffer/RegionRegistry.h
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef GCE_REGION_REGISTRY_H_
+#define GCE_REGION_REGISTRY_H_
+
+struct private_handle_t;
+
+/**
+ * Map the memory associated with hnd->fd or, if already mapped, increment
+ * its reference count.
+ */
+void* reference_region(
+    const char* op, const private_handle_t* hnd);
+
+/**
+ * Decrement the reference count associated with hnd->fd, unmapping its
+ * memory iff the reference count reaches 0.
+ */
+int unreference_region(const char* op, const private_handle_t* hnd);
+
+#endif  // GCE_REGION_REGISTRY_H_
diff --git a/guest/libs/wpa_supplicant_8_lib/Android.mk b/guest/libs/wpa_supplicant_8_lib/Android.mk
new file mode 100644
index 0000000..7776080
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/Android.mk
@@ -0,0 +1,79 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+LOCAL_PATH := $(call my-dir)
+
+ifeq ($(WPA_SUPPLICANT_VERSION),VER_0_8_X)
+
+ifneq ($(BOARD_WPA_SUPPLICANT_DRIVER),)
+  CONFIG_DRIVER_$(BOARD_WPA_SUPPLICANT_DRIVER) := y
+endif
+
+# Use a custom libnl on releases before N
+ifeq (0, $(shell test $(PLATFORM_SDK_VERSION) -lt 24; echo $$?))
+EXTERNAL_GCE_LIBNL_INCLUDE := external/gce/libnl/include
+else
+EXTERNAL_GCE_LIBNL_INCLUDE :=
+endif
+
+
+WPA_SUPPL_DIR = external/wpa_supplicant_8
+WPA_SRC_FILE :=
+
+include $(WPA_SUPPL_DIR)/wpa_supplicant/android.config
+
+WPA_SUPPL_DIR_INCLUDE = $(WPA_SUPPL_DIR)/src \
+	$(WPA_SUPPL_DIR)/src/common \
+	$(WPA_SUPPL_DIR)/src/drivers \
+	$(WPA_SUPPL_DIR)/src/l2_packet \
+	$(WPA_SUPPL_DIR)/src/utils \
+	$(WPA_SUPPL_DIR)/src/wps \
+	$(WPA_SUPPL_DIR)/wpa_supplicant \
+	$(EXTERNAL_GCE_LIBNL_INCLUDE)
+
+WPA_SUPPL_DIR_INCLUDE += external/libnl/include
+
+ifdef CONFIG_DRIVER_NL80211
+WPA_SRC_FILE += driver_cmd_nl80211.c
+endif
+
+ifeq ($(TARGET_ARCH),arm)
+# To force sizeof(enum) = 4
+L_CFLAGS += -mabi=aapcs-linux
+endif
+
+ifdef CONFIG_ANDROID_LOG
+L_CFLAGS += -DCONFIG_ANDROID_LOG
+endif
+
+########################
+
+include $(CLEAR_VARS)
+LOCAL_MODULE := lib_driver_cmd_simulated
+LOCAL_SHARED_LIBRARIES := libc libcutils
+
+LOCAL_CFLAGS := $(L_CFLAGS) \
+    $(GCE_VERSION_CFLAGS)
+
+LOCAL_SRC_FILES := $(WPA_SRC_FILE)
+
+LOCAL_C_INCLUDES := \
+  device/google/gce/include \
+  $(WPA_SUPPL_DIR_INCLUDE)\
+
+include $(BUILD_STATIC_LIBRARY)
+
+########################
+
+endif
diff --git a/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.c b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.c
new file mode 100644
index 0000000..223e7f8
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.c
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/*
+ * Driver interaction with extended Linux CFG8021
+ */
+
+#include "driver_cmd_nl80211.h"
+
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+
+#if GCE_PLATFORM_SDK_AFTER(L_MR1)
+// Android M exposes headers more directly.
+#include <netinet/in.h>
+#include <linux/if.h>
+#include "driver_nl80211.h"
+#elif GCE_PLATFORM_SDK_AFTER(J_MR2)
+// Android versions K and L put structures in hardware_legacy
+#include "hardware_legacy/driver_nl80211.h"
+#else
+// Android version J does not expose structures directly. These structures are
+// manually defined later.
+#include <netinet/in.h>
+#include <linux/if.h>
+#endif
+
+#include "common.h"
+#include "wpa_supplicant_i.h"
+#include "config.h"
+#include "android_drv.h"
+
+
+int wpa_driver_nl80211_driver_cmd(
+    void* priv, char* cmd, char* buf, size_t buf_len) {
+  struct i802_bss* bss = priv;
+  struct wpa_driver_nl80211_data* drv = bss->drv;
+  struct ifreq ifr;
+  android_wifi_priv_cmd priv_cmd;
+  int ret = 0;
+
+  D("%s: called", __FUNCTION__);
+  if (os_strcasecmp(cmd, "STOP") == 0) {
+    linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 0);
+    wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STOPPED");
+  } else if (os_strcasecmp(cmd, "START") == 0) {
+    linux_set_iface_flags(drv->global->ioctl_sock, bss->ifname, 1);
+    wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "STARTED");
+  } else if (os_strcasecmp(cmd, "MACADDR") == 0) {
+    u8 macaddr[ETH_ALEN] = {};
+
+    ret = linux_get_ifhwaddr(drv->global->ioctl_sock, bss->ifname, macaddr);
+    if (!ret)
+      ret = os_snprintf(
+          buf, buf_len, "Macaddr = " MACSTR "\n", MAC2STR(macaddr));
+  } else if (os_strcasecmp(cmd, "RELOAD") == 0) {
+    wpa_msg(drv->ctx, MSG_INFO, WPA_EVENT_DRIVER_STATE "HANGED");
+  } else {  // Use private command
+    return 0;
+  }
+  return ret;
+}
+
+
+int wpa_driver_set_p2p_noa(void* priv, u8 count, int start, int duration) {
+  D("%s: called", __FUNCTION__);
+  return 0;
+}
+
+
+int wpa_driver_get_p2p_noa(void* priv, u8* buf, size_t len) {
+  D("%s: called", __FUNCTION__);
+  return 0;
+}
+
+
+int wpa_driver_set_p2p_ps(void* priv, int legacy_ps, int opp_ps, int ctwindow) {
+  D("%s: called", __FUNCTION__);
+  return -1;
+}
+
+
+int wpa_driver_set_ap_wps_p2p_ie(
+    void* priv, const struct wpabuf* beacon,
+    const struct wpabuf* proberesp, const struct wpabuf* assocresp) {
+  D("%s: called", __FUNCTION__);
+  return 0;
+}
diff --git a/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.h b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.h
new file mode 100644
index 0000000..dfd982f
--- /dev/null
+++ b/guest/libs/wpa_supplicant_8_lib/driver_cmd_nl80211.h
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#ifndef DEVICE_GOOGLE_GCE_WPA_SUPPLICANT_8_H_
+#define DEVICE_GOOGLE_GCE_WPA_SUPPLICANT_8_H_
+
+#include <api_level_fixes.h>
+
+#include <memory.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include "common.h"
+#include "linux_ioctl.h"
+#include "wpa_supplicant_i.h"
+
+#define LOG_TAG "GceWpaSupplicant8Driver"
+
+#include "cutils/log.h"
+
+#define GCE_WPA_SUPPLICANT_DEBUG 0
+
+#if GCE_WPA_SUPPLICANT_DEBUG
+#  define D(...) ALOGD(__VA_ARGS__)
+#else
+#  define D(...) ((void)0)
+#endif
+
+
+typedef struct android_wifi_priv_cmd {
+  char* buf;
+  int used_len;
+  int total_len;
+} android_wifi_priv_cmd;
+
+#if GCE_PLATFORM_SDK_BEFORE(K)
+
+#include "driver.h"
+
+struct i802_bss {
+  struct wpa_driver_nl80211_data* drv;
+  struct i802_bss* next;
+  int ifindex;
+  char ifname[IFNAMSIZ + 1];
+  char brname[IFNAMSIZ];
+
+  unsigned int beacon_set:1;
+  unsigned int added_if_into_bridge:1;
+  unsigned int added_bridge:1;
+  unsigned int in_deinit:1;
+
+  u8 addr[ETH_ALEN];
+
+  int freq;
+
+  void* ctx;
+  struct nl_handle* nl_preq;
+  struct nl_handle* nl_mgmt;
+  struct nl_cb* nl_cb;
+
+  struct nl80211_wiphy_data *wiphy_data;
+  struct dl_list wiphy_list;
+};
+
+struct nl80211_global {
+  struct dl_list interfaces;
+  int if_add_ifindex;
+  struct netlink_data *netlink;
+  struct nl_cb* nl_cb;
+  struct nl_handle* nl;
+  int nl80211_id;
+  int ioctl_sock;  // socket for ioctl() use
+
+  struct nl_handle* nl_event;
+};
+
+struct wpa_driver_nl80211_data {
+  struct nl80211_global* global;
+  struct dl_list list;
+  struct dl_list wiphy_list;
+  char phyname[32];
+  void* ctx;
+  int ifindex;
+  int if_removed;
+  int if_disabled;
+  int ignore_if_down_event;
+  struct rfkill_data* rfkill;
+  struct wpa_driver_capa capa;
+  u8* extended_capa;
+  u8* extended_capa_mask;
+  unsigned int extended_capa_len;
+  int has_capability;
+  // More internal data follows.
+};
+
+#endif  // GCE_PLATFORM_SDK_AFTER(J)
+
+#endif  // DEVICE_GOOGLE_GCE_WPA_SUPPLICANT_8_H_