Merge "Initial commit for AT-Factory-Tool"
diff --git a/at-factory-tool/atft.py b/at-factory-tool/atft.py
new file mode 100755
index 0000000..355ba88
--- /dev/null
+++ b/at-factory-tool/atft.py
@@ -0,0 +1,260 @@
+#!/usr/bin/python
+"""Graphical tool for managing the ATFA and AT communication.
+
+This tool allows for easy graphical access to common ATFA commands.  It also
+locates Fastboot devices and can initiate communication between the ATFA and
+an Android Things device.
+"""
+import atftman
+import fastboot_exceptions
+import fastbootsh
+import wx
+
+
+class Atft(wx.Frame):
+  """wxpython class to handle all GUI commands for the ATFA.
+
+  Creates the GUI and provides various functions for interacting with an
+  ATFA and an Android Things device.
+
+  """
+
+  def __init__(self, *args, **kwargs):
+    super(Atft, self).__init__(*args, **kwargs)
+
+    self.atft_manager = atftman.AtftManager(fastbootsh.FastbootDevice)
+
+    self.panel = wx.Panel(self)
+    self.menubar = wx.MenuBar()
+    self.app_menu = wx.Menu()
+    self.atfa_menu = wx.Menu()
+    self.space_menu = wx.Menu()
+    self.toolbar = self.CreateToolBar()
+
+    # App Menu Options
+    self.shst = self.app_menu.Append(
+        wx.ID_ANY, 'Show Statusbar', kind=wx.ITEM_CHECK)
+    self.app_menu.Check(self.shst.GetId(), True)
+    self.Bind(wx.EVT_MENU, self.ToggleStatusBar, self.shst)
+
+    self.shtl = self.app_menu.Append(
+        wx.ID_ANY, 'Show Toolbar', kind=wx.ITEM_CHECK)
+    self.app_menu.Check(self.shtl.GetId(), True)
+    self.Bind(wx.EVT_MENU, self.ToggleToolBar, self.shtl)
+
+    app_menu_quit = self.app_menu.Append(wx.ID_EXIT, 'Quit')
+    self.Bind(wx.EVT_MENU, self.OnQuit, app_menu_quit)
+
+    # ATFA Menu Options
+    atfa_menu_listdev = self.atfa_menu.Append(wx.ID_ANY, 'List Devices')
+    self.Bind(wx.EVT_MENU, self.OnListDevices, atfa_menu_listdev)
+
+    atfa_menu_storage = self.atfa_menu.Append(wx.ID_ANY, 'Storage Mode')
+    self.Bind(wx.EVT_MENU, self.OnStorageMode, atfa_menu_storage)
+
+    atfa_menu_reboot = self.atfa_menu.Append(wx.ID_ANY, 'Reboot')
+    self.Bind(wx.EVT_MENU, self.OnReboot, atfa_menu_reboot)
+
+    atfa_menu_shutdown = self.atfa_menu.Append(wx.ID_ANY, 'Shutdown')
+    self.Bind(wx.EVT_MENU, self.OnShutdown, atfa_menu_shutdown)
+
+    # Add Menu items to Menubar
+    self.menubar.Append(self.app_menu, 'Application')
+    self.menubar.Append(self.space_menu, ' ')
+    self.menubar.Append(self.atfa_menu, 'ATFA Device')
+    self.SetMenuBar(self.menubar)
+
+    # Toolbar buttons
+    toolbar_devices = self.toolbar.AddLabelTool(
+        wx.ID_ANY, 'List Devices', wx.Bitmap('toolbar_devices.png'))
+    self.Bind(wx.EVT_TOOL, self.OnListDevices, toolbar_devices)
+    toolbar_storage = self.toolbar.AddLabelTool(
+        wx.ID_ANY, 'Storage Mode', wx.Bitmap('toolbar_storage.png'))
+    self.Bind(wx.EVT_TOOL, self.OnStorageMode, toolbar_storage)
+    toolbar_quit = self.toolbar.AddLabelTool(wx.ID_ANY, 'Quit App',
+                                             wx.Bitmap('toolbar_exit.png'))
+    self.Bind(wx.EVT_TOOL, self.OnQuit, toolbar_quit)
+    toolbar_reboot = self.toolbar.AddLabelTool(wx.ID_ANY, 'Reboot ATFA',
+                                               wx.Bitmap('toolbar_reboot.png'))
+    self.Bind(wx.EVT_TOOL, self.OnReboot, toolbar_reboot)
+    toolbar_shutdown = self.toolbar.AddLabelTool(
+        wx.ID_ANY, 'Shutdown ATFA', wx.Bitmap('toolbar_shutdown.png'))
+    self.Bind(wx.EVT_TOOL, self.OnShutdown, toolbar_shutdown)
+
+    self.vbox = wx.BoxSizer(wx.VERTICAL)
+    self.st = wx.StaticLine(self.panel, wx.ID_ANY, style=wx.LI_HORIZONTAL)
+    self.vbox.Add(self.st, 0, wx.ALL | wx.EXPAND, 5)
+
+    # Device Output Title
+    self.dev_title = wx.StaticText(self.panel, wx.ID_ANY, 'Detected Devices')
+    self.dev_title_sizer = wx.BoxSizer(wx.HORIZONTAL)
+    self.dev_title_sizer.Add(self.dev_title, 0, wx.ALL, 5)
+    self.vbox.Add(self.dev_title_sizer, 0, wx.LEFT)
+
+    # Device Output Window
+    self.devices_output = wx.TextCtrl(
+        self.panel,
+        wx.ID_ANY,
+        size=(500, 100),
+        style=wx.TE_MULTILINE | wx.TE_READONLY)
+    self.vbox.Add(self.devices_output, 0, wx.ALL | wx.EXPAND, 5)
+
+    # Command Output Title
+    self.comm_title = wx.StaticText(self.panel, wx.ID_ANY, 'Command Output')
+    self.comm_title_sizer = wx.BoxSizer(wx.HORIZONTAL)
+    self.comm_title_sizer.Add(self.comm_title, 0, wx.ALL, 5)
+    self.vbox.Add(self.comm_title_sizer, 0, wx.LEFT)
+
+    # Command Output Window
+    self.cmd_output = wx.TextCtrl(
+        self.panel,
+        wx.ID_ANY,
+        size=(500, 500),
+        style=wx.TE_MULTILINE | wx.TE_READONLY | wx.HSCROLL)
+    self.vbox.Add(self.cmd_output, 0, wx.ALL | wx.EXPAND, 5)
+
+    self.panel.SetSizer(self.vbox)
+    self.SetMenuBar(self.menubar)
+    self.toolbar.Realize()
+    self.statusbar = self.CreateStatusBar()
+    self.statusbar.SetStatusText('Ready')
+    self.SetSize((800, 800))
+    self.SetTitle('Google AT-Factory-Tool')
+    self.Center()
+    self.Show(True)
+
+    self.OnListDevices(None)
+
+  def PrintToDeviceWindow(self, text):
+    self.devices_output.WriteText(text)
+    self.devices_output.WriteText('\n')
+
+  def PrintToCmdWindow(self, text):
+    self.ClearCommandWindow()
+    self.cmd_output.WriteText(text)
+    self.cmd_output.WriteText('\n')
+
+  def OnListDevices(self, event=None):
+    if event is not None:
+      event.Skip()
+
+    self.ClearDeviceWindow()
+    try:
+      devices = self.atft_manager.ListDevices()
+
+      if devices['atfa_dev'] is not None:
+        self.PrintToDeviceWindow('[ATFA] ' + devices['atfa_dev'])
+      if devices['target_dev'] is not None:
+        self.PrintToDeviceWindow('[Target] ' + devices['target_dev'])
+    except fastboot_exceptions.DeviceNotFoundException:
+      self.PrintToDeviceWindow('No devices found!')
+    except fastboot_exceptions.FastbootFailure:
+      self.PrintToCmdWindow('Fastboot command failed!')
+    except Exception as e:  # pylint: disable=broad-except
+      self.PrintException(e)
+
+  def OnGetSerial(self, event):
+    self.OnListDevices()
+    self.PrintToCmdWindow('Getting ATFA Serial number')
+    try:
+      self.atft_manager.atfa_dev_manager.GetSerial()
+    except fastboot_exceptions.DeviceNotFoundException:
+      self.PrintToCmdWindow("Can't get serial number!  No Available ATFA!")
+    except fastboot_exceptions.FastbootFailure:
+      self.PrintToCmdWindow('Fastboot command failed!')
+    except Exception as e:  # pylint: disable=broad-except
+      self.PrintException(e)
+
+  def OnNormalMode(self, event):
+    self.OnListDevices()
+    self.PrintToCmdWindow('Switching to Normal Mode')
+    try:
+      self.atft_manager.SwitchNormal()
+    except fastboot_exceptions.DeviceNotFoundException:
+      self.PrintToCmdWindow("Can't switch to Normal Mode!  No Available ATFA!")
+    except fastboot_exceptions.FastbootFailure:
+      self.PrintToCmdWindow('Fastboot command failed!')
+    except Exception as e:  # pylint: disable=broad-except
+      self.PrintException(e)
+
+  def OnStorageMode(self, event):
+    self.OnListDevices()
+    try:
+      self.PrintToCmdWindow('Switching to Storage Mode for' +
+                            self.atft_manager.GetAtfaSerial())
+      self.PrintToCmdWindow(self.atft_manager.
+                            atfa_dev_manager.SwitchStorage())
+    except fastboot_exceptions.DeviceNotFoundException:
+      self.PrintToCmdWindow("Can't switch to Storage Mode!  No Available ATFA!")
+    except fastboot_exceptions.FastbootFailure:
+      self.PrintToCmdWindow('Fastboot command failed!')
+    except Exception as e:  # pylint: disable=broad-except
+      self.PrintException(e)
+
+  def OnReboot(self, event):
+    self.OnListDevices()
+    try:
+      self.PrintToCmdWindow('Rebooting' + self.atft_manager.GetAtfaSerial())
+      self.atft_manager.ataf_dev_manager.Reboot()
+    except fastboot_exceptions.DeviceNotFoundException:
+      self.PrintToCmdWindow("Can't reboot!  No Available ATFA!")
+    except fastboot_exceptions.FastbootFailure:
+      self.PrintToCmdWindow('Fastboot command failed!')
+    except Exception as e:  # pylint: disable=broad-except
+      self.PrintException(e)
+
+  def OnShutdown(self, event):
+    self.OnListDevices()
+    try:
+      self.PrintToCmdWindow('Shutting down' +
+                            self.atft_manager.GetAtfaSerial())
+      self.atft_manager.atfa_dev_manager.Shutdown()
+    except fastboot_exceptions.DeviceNotFoundException:
+      self.PrintToCmdWindow("Can't shutdown!  No Available ATFA!")
+    except fastboot_exceptions.FastbootFailure:
+      self.PrintToCmdWindow('Fastboot command failed!')
+    except Exception as e:  # pylint: disable=broad-except
+      self.PrintException(e)
+
+  def get_logs(self, event):
+    self.OnListDevices()
+    self.atft_manager.atfa_dev_manager.GetLogs()
+
+  def OnQuit(self, event):
+    self.Close()
+
+  def ToggleStatusBar(self, event):
+    if self.shst.IsChecked():
+      self.statusbar.Show()
+    else:
+      self.statusbar.Hide()
+
+  def ToggleToolBar(self, event):
+    if self.shtl.IsChecked():
+      self.toolbar.Show()
+    else:
+      self.toolbar.Hide()
+
+  def ClearCommandWindow(self):
+    # Clear output.
+    self.cmd_output.SetValue('')
+
+  def ClearDeviceWindow(self):
+    # Clear device list.
+    self.devices_output.SetValue('')
+
+  def PrintException(self, e):
+    self.PrintToCmdWindow(self.atft_manager.FormatException(e))
+
+
+def main():
+  # TODO(matta): Check if there's a atft.py already running and not run again
+  # TODO(matta): Inject current host time into ATFA at startup
+  # TODO(matta): Periodically poll for new fastboot devices?
+  app = wx.App()
+  Atft(None)
+  app.MainLoop()
+
+
+if __name__ == '__main__':
+  main()
diff --git a/at-factory-tool/atftman.py b/at-factory-tool/atftman.py
new file mode 100644
index 0000000..edd4f63
--- /dev/null
+++ b/at-factory-tool/atftman.py
@@ -0,0 +1,200 @@
+#!/usr/bin/python
+"""At-Factory-Tool manager module.
+
+This module provides the logical implementation of the graphical tool
+for managing the ATFA and AT communication.
+"""
+import os
+import tempfile
+import uuid
+
+import fastboot_exceptions
+
+
+class EncryptionAlgorithm(object):
+  ALGORITHM_P256 = 1
+  ALGORITHM_CURVE25519 = 2
+
+
+class AtftManager(object):
+  """The manager to implement ATFA tasks.
+
+  TODO(shan): Support multiple target devices.
+  The target_dev attribute can be extended to a list
+
+  Attributes:
+    atfa_dev: A FastbootDevice object identifying the detected ATFA device.
+    target_dev: A FastbootDevice object identifying the AT device
+      to be provisioned.
+    atfa_dev_manager: An interface to do operation on ATFA device.
+  """
+
+  def __init__(self, fastboot_device_controller):
+    """Initialize attributes and store the supplied fastboot_device_controller.
+
+    Args:
+      fastboot_device_controller:
+        The interface to interact with a fastboot device.
+    """
+    self.atfa_dev = None
+    self.target_dev = None
+    self.atfa_dev_manager = None
+    self._fastboot_device_controller = fastboot_device_controller
+
+  def FormatException(self, e):
+    """Format the exception. Concatenate the exception type with the message.
+
+    Args:
+      e: The exception to be printed.
+    Returns:
+      The exception message.
+    """
+    return '{0}: {1}'.format(e.__class__.__name__, e)
+
+  def ListDevices(self):
+    """Get device list.
+
+    Get the serial number of the ATFA device and the target device.
+    If the device does not exist, the returned serial number would be None.
+
+    Returns:
+      A dictionary of
+      {'atfa_dev': ATFA device serial number,
+      'target_dev': target device serial number}
+    Raises:
+      DeviceNotFoundException: When no device is found.
+    """
+    # ListDevices returns a list of USBHandles
+    device_serials = self._fastboot_device_controller.ListDevices()
+
+    if not device_serials:
+      self.atfa_dev = None
+      self.target_dev = None
+      raise fastboot_exceptions.DeviceNotFoundException()
+    else:
+      for found_dev_serial in device_serials:
+        if found_dev_serial.startswith('ATFA'):
+          self.atfa_dev = self._fastboot_device_controller(found_dev_serial)
+          self.atfa_dev_manager = AtfaDeviceManager(self.atfa_dev)
+        elif found_dev_serial:
+          self.target_dev = self._fastboot_device_controller(found_dev_serial)
+    if self.atfa_dev is None:
+      atfa_dev_serial = None
+    else:
+      atfa_dev_serial = self.atfa_dev.serial_number
+    if self.target_dev is None:
+      target_dev_serial = None
+    else:
+      target_dev_serial = self.target_dev.serial_number
+    return {'atfa_dev': atfa_dev_serial, 'target_dev': target_dev_serial}
+
+  def GetAtfaSerial(self):
+    """Get the serial number for the ATFA device.
+
+    Returns:
+      The serial number for the ATFA device
+    Raises:
+      DeviceNotFoundException: When the device is not found
+    """
+    self._CheckDevice(self.atfa_dev)
+    return self.atfa_dev.serial_number
+
+  def TransferContent(self, src, dst):
+    """Transfer content from a device to another device.
+
+    Download file from one device and store it into a tmp file.
+    Upload file from the tmp file onto another device.
+
+    Args:
+      src: The source device to be copied from.
+      dst: The destination device to be copied to.
+    """
+    # create a tmp folder
+    tmp_folder = tempfile.mkdtemp()
+    # temperate file name is a UUID based on host ID and current time.
+    tmp_file_name = str(uuid.uuid1())
+    file_path = os.path.join(tmp_folder, tmp_file_name)
+    # pull file to local fs
+    src.Upload(file_path)
+    # push file to fastboot device
+    dst.Download(file_path)
+    # delete the temperate file afterwards
+    if os.path.exists(file_path):
+      os.remove(file_path)
+    # delete the temperate folder afterwards
+    if os.path.exists(tmp_folder):
+      os.rmdir(tmp_folder)
+
+  def _CheckDevice(self, device):
+    """Check if the device is a connected fastboot device.
+
+    Args:
+      device: The device to be checked.
+    Raises:
+      DeviceNotFoundException: When the device is not found
+    """
+    if device is None:
+      raise fastboot_exceptions.DeviceNotFoundException()
+
+
+class AtfaDeviceManager(object):
+  """The class to manager ATFA device related operations.
+
+  """
+
+  def __init__(self, atfa_dev):
+    self.atfa_dev = atfa_dev
+
+  def GetSerial(self):
+    """Issue fastboot command to get serial number for the ATFA device.
+
+    Raises:
+      DeviceNotFoundException: When the device is not found.
+    """
+    self._CheckDevice(self.atfa_dev)
+    self.atfa_dev.Oem('serial')
+
+  def SwitchNormal(self):
+    """Switch the ATFA device to normal mode.
+
+    TODO(matta): Find a way to find and nicely unmount drive from Windows.
+    TODO(matta): Have ATFA detect unmount and switch back to g_ser mode.
+
+    Raises:
+      DeviceNotFoundException: When the device is not found
+    """
+    self._CheckDevice(self.atfa_dev)
+
+  def SwitchStorage(self):
+    """Switch the ATFA device to storage mode.
+
+    Returns:
+      The result for the fastboot command.
+    Raises:
+      DeviceNotFoundException: When the device is not found
+    """
+    self._CheckDevice(self.atfa_dev)
+    return self.atfa_dev.Oem('storage')
+
+  def Reboot(self):
+    """Reboot the ATFA device.
+
+    Raises:
+      DeviceNotFoundException: When the device is not found
+    """
+    self._CheckDevice(self.atfa_dev)
+    self.atfa_dev.Oem('reboot')
+
+  def Shutdown(self):
+    """Shutdown the ATFA device.
+
+    Raises:
+      DeviceNotFoundException: When the device is not found
+    """
+    self._CheckDevice(self.atfa_dev)
+    self.atfa_dev.Oem('shutdown')
+
+  def GetLogs(self):
+    # TODO(matta): Add Fastboot command to copy logs to storage device and
+    # switch to mass storage mode
+    self._CheckDevice(self.atfa_dev)
diff --git a/at-factory-tool/atftman_unittest.py b/at-factory-tool/atftman_unittest.py
new file mode 100644
index 0000000..f68bcfc
--- /dev/null
+++ b/at-factory-tool/atftman_unittest.py
@@ -0,0 +1,123 @@
+"""Unit test for atftman."""
+import unittest
+
+import atftman
+import fastboot_exceptions
+from mock import patch
+
+
+files = []
+
+
+class AtftManTest(unittest.TestCase):
+  ATFA_TEST_SERIAL = 'ATFA_TEST_SERIAL'
+  TEST_TMP_FOLDER = '/tmp/TMPTEST/'
+  TEST_SERIAL = 'TEST_SERIAL'
+  TEST_UUID = 'TEST-UUID'
+
+  class FastbootDeviceTemplate(object):
+
+    @staticmethod
+    def ListDevices():
+      pass
+
+    def __init__(self, serial_number):
+      self.serial_number = serial_number
+
+    def Oem(self, oem_command):
+      pass
+
+    def Upload(self, file_path):
+      pass
+
+    def Download(self, file_path):
+      pass
+
+    def Disconnect(self):
+      pass
+
+    def __del__(self):
+      pass
+
+  def setUp(self):
+    self.atft_manager = atftman.AtftManager(self.FastbootDeviceTemplate)
+
+  # Test AtftManager.ListDevices
+  @patch('atftman.AtfaDeviceManager')
+  @patch('__main__.AtftManTest.FastbootDeviceTemplate.ListDevices')
+  def testListDevicesNormal(self, mock_list_devices, mock_atfa_device_manager):
+    mock_list_devices.return_value = [self.TEST_SERIAL,
+                                      self.ATFA_TEST_SERIAL]
+    devices = self.atft_manager.ListDevices()
+    self.assertEqual(devices['atfa_dev'], self.ATFA_TEST_SERIAL)
+    self.assertEqual(devices['target_dev'], self.TEST_SERIAL)
+    mock_atfa_device_manager.assert_called()
+
+  @patch('__main__.AtftManTest.FastbootDeviceTemplate.ListDevices')
+  def testListDevicesATFA(self, mock_list_devices):
+    mock_list_devices.return_value = [self.ATFA_TEST_SERIAL]
+    devices = self.atft_manager.ListDevices()
+    self.assertEqual(devices['atfa_dev'], self.ATFA_TEST_SERIAL)
+    self.assertEqual(devices['target_dev'], None)
+
+  @patch('__main__.AtftManTest.FastbootDeviceTemplate.ListDevices')
+  def testListDevicesTarget(self, mock_list_devices):
+    mock_list_devices.return_value = [self.TEST_SERIAL]
+    devices = self.atft_manager.ListDevices()
+    self.assertEqual(devices['atfa_dev'], None)
+    self.assertEqual(devices['target_dev'], self.TEST_SERIAL)
+
+  @patch('__main__.AtftManTest.FastbootDeviceTemplate.ListDevices')
+  def testListDevicesNotFound(self, mock_list_devices):
+    mock_list_devices.return_value = []
+    with self.assertRaises(fastboot_exceptions.DeviceNotFoundException):
+      self.atft_manager.ListDevices()
+
+  # Test AtftManager.TransferContent
+
+  @staticmethod
+  def _AppendFile(file_path):
+    files.append(file_path)
+
+  @staticmethod
+  def _CheckFile(file_path):
+    assert file_path in files
+    return True
+
+  @staticmethod
+  def _RemoveFile(file_path):
+    assert file_path in files
+    files.remove(file_path)
+
+  @patch('os.rmdir')
+  @patch('os.remove')
+  @patch('os.path.exists')
+  @patch('tempfile.mkdtemp')
+  @patch('uuid.uuid1')
+  @patch('__main__.AtftManTest.FastbootDeviceTemplate.Upload')
+  @patch('__main__.AtftManTest.FastbootDeviceTemplate.Download')
+  def testTransferContentNormal(self, mock_download, mock_upload,
+                                mock_uuid, mock_create_folder,
+                                mock_exists, mock_remove, mock_rmdir):
+    # upload (to fs): create a temporary file
+    mock_upload.side_effect = AtftManTest._AppendFile
+    # download (from fs): check if the temporary file exists
+    mock_download.side_effect = AtftManTest._CheckFile
+    mock_exists.side_effect = AtftManTest._CheckFile
+    # remove: remove the file
+    mock_remove.side_effect = AtftManTest._RemoveFile
+    mock_rmdir.side_effect = AtftManTest._RemoveFile
+    mock_create_folder.return_value = self.TEST_TMP_FOLDER
+    files.append(self.TEST_TMP_FOLDER)
+    mock_uuid.return_value = self.TEST_UUID
+    tmp_path = self.TEST_TMP_FOLDER + self.TEST_UUID
+    src = self.FastbootDeviceTemplate(self.TEST_SERIAL)
+    dst = self.FastbootDeviceTemplate(self.TEST_SERIAL)
+    self.atft_manager.TransferContent(src, dst)
+    src.Upload.assert_called_once_with(tmp_path)
+    src.Download.assert_called_once_with(tmp_path)
+    # we should have no temporary file at the end
+    self.assertTrue(not files)
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/at-factory-tool/fastboot_exceptions.py b/at-factory-tool/fastboot_exceptions.py
new file mode 100644
index 0000000..185f43c
--- /dev/null
+++ b/at-factory-tool/fastboot_exceptions.py
@@ -0,0 +1,21 @@
+#!/usr/bin/python
+"""Exceptions for At-Factory-Tool Manager (atftman).
+"""
+
+
+class DeviceNotFoundException(Exception):
+  pass
+
+
+class NoAlgorithmAvailableException(Exception):
+  pass
+
+
+class FastbootFailure(Exception):
+
+  def __init__(self, msg):
+    Exception.__init__(self)
+    self.msg = msg
+
+  def __str__(self):
+    return self.msg
diff --git a/at-factory-tool/fastbootadb.py b/at-factory-tool/fastbootadb.py
new file mode 100644
index 0000000..aa7f850
--- /dev/null
+++ b/at-factory-tool/fastbootadb.py
@@ -0,0 +1,93 @@
+# !/usr/bin/python
+"""Fastboot Interface Implementation using python adb library.
+
+"""
+from adb import fastboot
+from adb import usb_exceptions
+
+import fastboot_exceptions
+
+
+class FastbootDevice(object):
+  """An abstracted fastboot device object.
+
+  Attributes:
+    serial_number: The serial number of the fastboot device.
+  """
+
+  @staticmethod
+  def ListDevices():
+    """List all fastboot devices.
+
+    Returns:
+      A list of serial numbers for all the fastboot devices.
+    """
+    device_serial_numbers = []
+    try:
+      for device in fastboot.FastbootCommands.Devices():
+        device_serial_numbers.append(device.serial_number)
+    except usb_exceptions.FormatMessageWithArgumentsException as e:
+      raise fastboot_exceptions.FastbootFailure(str(e))
+
+    return device_serial_numbers
+
+  def __init__(self, serial_number):
+    """Initiate the fastboot device object.
+
+    Args:
+      serial_number: The serial number of the fastboot device.
+    Raises:
+      FastbootFailure: If failure happens for fastboot commands.
+    """
+    try:
+      self.serial_number = serial_number
+      self._fastboot_commands = (fastboot.FastbootCommands.ConnectDevice(
+          serial=serial_number))
+    except usb_exceptions.FormatMessageWithArgumentsException as e:
+      raise fastboot_exceptions.FastbootFailure(str(e))
+
+  def Oem(self, oem_command):
+    """Run an OEM command.
+
+    Args:
+      oem_command: The OEM command to run.
+    Returns:
+      The result message for the OEM command.
+    Raises:
+      FastbootFailure: If failure happens during the command.
+    """
+    try:
+      return self._fastboot_commands.Oem(oem_command)
+    except usb_exceptions.FormatMessageWithArgumentsException as e:
+      raise fastboot_exceptions.FastbootFailure(str(e))
+
+  def Upload(self, file_path):
+    """Pulls a file from the fastboot device to the local file system.
+
+    TODO: To be implemented. Currently adb library does not support
+    the stage and get_staged command.
+
+    Args:
+      file_path: The file path of the file system
+        that the remote file would be pulled to.
+    """
+    pass
+
+  def Download(self, file_path):
+    """Push a file from the file system to the fastboot device.
+
+    TODO: To be implemented. Currently adb library does not support
+    the stage and get_staged command.
+
+    Args:
+      file_path: The file path of the file on the local file system
+        that would be pushed to fastboot device.
+    """
+    pass
+
+  def Disconnect(self):
+    """Disconnect from the fastboot device."""
+    self._fastboot_commands.Close()
+
+  def __del__(self):
+    self.Disconnect()
diff --git a/at-factory-tool/fastbootadb_unittest.py b/at-factory-tool/fastbootadb_unittest.py
new file mode 100644
index 0000000..01662ad
--- /dev/null
+++ b/at-factory-tool/fastbootadb_unittest.py
@@ -0,0 +1,106 @@
+"""Unit test for fastboot interface using adb library."""
+import unittest
+
+from adb import usb_exceptions
+import fastboot_exceptions
+import fastbootadb
+from mock import MagicMock
+from mock import patch
+
+
+class FastbootAdbTest(unittest.TestCase):
+  ATFA_TEST_SERIAL = 'ATFA_TEST_SERIAL'
+  TEST_COMMAND = 'COMMAND'
+  TEST_MESSAGE_FAILURE = 'FAIL: TEST MESSAGE'
+  TEST_MESSAGE_SUCCESS = 'OKAY: TEST MESSAGE'
+  TEST_SERIAL = 'TEST_SERIAL'
+
+  def setUp(self):
+    pass
+
+  # Test FastbootDevice.__init__
+  @patch('adb.fastboot.FastbootCommands')
+  def testFastbootDeviceInit(self, mock_fastboot_commands):
+    mock_fastboot_instance = MagicMock()
+    mock_fastboot_commands.ConnectDevice.return_value = mock_fastboot_instance
+    fastboot_device = fastbootadb.FastbootDevice(self.TEST_SERIAL)
+    mock_fastboot_commands.ConnectDevice.assert_called_with(
+        serial=self.TEST_SERIAL)
+    self.assertEqual(fastboot_device._fastboot_commands, mock_fastboot_instance)
+    self.assertEqual(fastboot_device.serial_number, self.TEST_SERIAL)
+    return fastboot_device
+
+  # Test FastbootDevice.disconnect
+  @patch('adb.fastboot.FastbootCommands')
+  def testFastbootDeviceDisconnect(self, mock_fastboot_commands):
+    mock_fastboot_instance = MagicMock()
+    mock_fastboot_commands.ConnectDevice.return_value = mock_fastboot_instance
+    fastboot_device = fastbootadb.FastbootDevice(self.TEST_SERIAL)
+    fastboot_device.Disconnect()
+    mock_fastboot_instance.Close.assert_called()
+
+  # Test FastbootDevice.__del__
+  @patch('adb.fastboot.FastbootCommands')
+  def testFastbootDeviceDestroy(self, mock_fastboot_commands):
+    mock_fastboot_instance = MagicMock()
+    mock_fastboot_commands.ConnectDevice.return_value = mock_fastboot_instance
+    fastboot_device = fastbootadb.FastbootDevice(self.TEST_SERIAL)
+    del fastboot_device
+    mock_fastboot_instance.Close.assert_called()
+
+  # Test FastbootDevice.ListDevices
+  @patch('adb.fastboot.FastbootCommands')
+  def testListDevicesNormal(self, mock_fastboot_commands):
+    mock_atfa_usb_handle = MagicMock()
+    mock_target_usb_handle = MagicMock()
+    mock_atfa_usb_handle.serial_number = self.ATFA_TEST_SERIAL
+    mock_target_usb_handle.serial_number = self.TEST_SERIAL
+    # Should return a generator.
+    mock_fastboot_commands.Devices.return_value = (d for d in
+                                                   [mock_atfa_usb_handle,
+                                                    mock_target_usb_handle])
+    devices = fastbootadb.FastbootDevice.ListDevices()
+    mock_fastboot_commands.Devices.assert_called()
+    self.assertEqual(len(devices), 2)
+    self.assertEqual(devices[0], self.ATFA_TEST_SERIAL)
+    self.assertEqual(devices[1], self.TEST_SERIAL)
+
+  @patch('adb.fastboot.FastbootCommands')
+  def testListDevicesFailure(self, mock_fastboot_commands):
+    test_exception = (usb_exceptions.
+                      FormatMessageWithArgumentsException(self.
+                                                          TEST_MESSAGE_FAILURE)
+                     )
+    mock_fastboot_commands.Devices.side_effect = test_exception
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      fastbootadb.FastbootDevice.ListDevices()
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+  # Test FastbootDevice.Oem
+  @patch('adb.fastboot.FastbootCommands')
+  def testOemNormal(self, mock_fastboot_commands):
+    mock_fastboot_device = MagicMock()
+    mock_fastboot_commands.ConnectDevice.return_value = mock_fastboot_device
+    mock_fastboot_device.Oem.return_value = self.TEST_MESSAGE_SUCCESS
+    fastboot_device = fastbootadb.FastbootDevice(self.TEST_SERIAL)
+    out = fastboot_device.Oem(self.TEST_COMMAND)
+    mock_fastboot_device.Oem.assert_called_once_with(self.TEST_COMMAND)
+    self.assertEqual(self.TEST_MESSAGE_SUCCESS, out)
+
+  @patch('adb.fastboot.FastbootCommands')
+  def testOemFailure(self, mock_fastboot_commands):
+    test_exception = (usb_exceptions.
+                      FormatMessageWithArgumentsException(self.
+                                                          TEST_MESSAGE_FAILURE)
+                     )
+    mock_fastboot_device = MagicMock()
+    mock_fastboot_commands.ConnectDevice.return_value = mock_fastboot_device
+    mock_fastboot_device.Oem.side_effect = test_exception
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      fastboot_device = fastbootadb.FastbootDevice(self.TEST_SERIAL)
+      fastboot_device.Oem(self.TEST_COMMAND)
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/at-factory-tool/fastbootsh.py b/at-factory-tool/fastbootsh.py
new file mode 100644
index 0000000..b04b7e8
--- /dev/null
+++ b/at-factory-tool/fastbootsh.py
@@ -0,0 +1,91 @@
+# !/usr/bin/python
+"""Fastboot Interface Implementation using sh library.
+
+"""
+import fastboot_exceptions
+import sh
+
+
+class FastbootDevice(object):
+  """An abstracted fastboot device object.
+
+  Attributes:
+    serial_number: The serial number of the fastboot device.
+  """
+
+  @staticmethod
+  def ListDevices():
+    """List all fastboot devices.
+
+    Returns:
+      A list of serial numbers for all the fastboot devices.
+    """
+    out = sh.fastboot('devices')
+    if out.startswith('FAIL'):
+      raise fastboot_exceptions.FastbootFailure(out)
+
+    device_serial_numbers = out.replace('\tfastboot', '').rstrip().split('\n')
+    # filter out empty string
+    return filter(None, device_serial_numbers)
+
+  def __init__(self, serial_number):
+    """Initiate the fastboot device object.
+
+    Args:
+      serial_number: The serial number of the fastboot device.
+    """
+    self.serial_number = serial_number
+
+  def Oem(self, oem_command):
+    """Run an OEM command.
+
+    Args:
+      oem_command: The OEM command to run.
+    Returns:
+      The result message for the OEM command.
+    Raises:
+      FastbootFailure: If failure happens during the command.
+    """
+    out = sh.fastboot('-s', self.serial_number, 'oem', oem_command)
+    if out.startswith('FAIL'):
+      raise fastboot_exceptions.FastbootFailure(out)
+    return out
+
+  def Upload(self, file_path):
+    """Pulls a file from the fastboot device to the local file system.
+
+    Args:
+      file_path: The file path of the file system
+        that the remote file would be pulled to.
+    Returns:
+      The output for the fastboot command required.
+    Raises:
+      FastbootFailure: If failure happens during the command.
+    """
+    out = sh.fastboot('-s', self.serial_number, 'get_staged', file_path)
+    if out.startswith('FAIL'):
+      raise fastboot_exceptions.FastbootFailure(out)
+    return out
+
+  def Download(self, file_path):
+    """Push a file from the file system to the fastboot device.
+
+    Args:
+      file_path: The file path of the file on the local file system
+        that would be pushed to fastboot device.
+    Returns:
+      The output for the fastboot command required.
+    Raises:
+      FastbootFailure: If failure happens during the command.
+    """
+    out = sh.fastboot('-s', self.serial_number, 'stage', file_path)
+    if out.startswith('FAIL'):
+      raise fastboot_exceptions.FastbootFailure(out)
+    return out
+
+  def Disconnect(self):
+    """Disconnect from the fastboot device."""
+    pass
+
+  def __del__(self):
+    self.Disconnect()
diff --git a/at-factory-tool/fastbootsh_unittest.py b/at-factory-tool/fastbootsh_unittest.py
new file mode 100644
index 0000000..1dec0ee
--- /dev/null
+++ b/at-factory-tool/fastbootsh_unittest.py
@@ -0,0 +1,129 @@
+"""Unit test for fastboot interface using sh library."""
+import unittest
+
+import fastboot_exceptions
+import fastbootsh
+from mock import patch
+
+
+class FastbootShTest(unittest.TestCase):
+  ATFA_TEST_SERIAL = 'ATFA_TEST_SERIAL'
+  TEST_MESSAGE_FAILURE = 'FAIL: TEST MESSAGE'
+  TEST_MESSAGE_SUCCESS = 'OKAY: TEST MESSAGE'
+  TEST_SERIAL = 'TEST_SERIAL'
+
+  def setUp(self):
+    pass
+
+  # Test FastbootDevice.ListDevices
+  @patch('sh.fastboot', create=True)
+  def testListDevicesOneDevice(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_SERIAL + '\tfastboot'
+    device_serial_numbers = fastbootsh.FastbootDevice.ListDevices()
+    mock_fastboot_commands.assert_called_once_with('devices')
+    self.assertEqual(1, len(device_serial_numbers))
+    self.assertEqual(self.TEST_SERIAL, device_serial_numbers[0])
+
+  @patch('sh.fastboot', create=True)
+  def testListDevicesTwoDevices(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = (self.TEST_SERIAL + '\tfastboot\n' +
+                                           self.ATFA_TEST_SERIAL + '\tfastboot')
+    device_serial_numbers = fastbootsh.FastbootDevice.ListDevices()
+    mock_fastboot_commands.assert_called_once_with('devices')
+    self.assertEqual(2, len(device_serial_numbers))
+    self.assertEqual(self.TEST_SERIAL, device_serial_numbers[0])
+    self.assertEqual(self.ATFA_TEST_SERIAL, device_serial_numbers[1])
+
+  @patch('sh.fastboot', create=True)
+  def testListDevicesMultiDevices(self, mock_fastboot_commands):
+    one_device = self.TEST_SERIAL + '\tfastboot'
+    result = one_device
+    for _ in range(0, 9):
+      result += '\n' + one_device
+    mock_fastboot_commands.return_value = result
+    device_serial_numbers = fastbootsh.FastbootDevice.ListDevices()
+    mock_fastboot_commands.assert_called_once_with('devices')
+    self.assertEqual(10, len(device_serial_numbers))
+
+  @patch('sh.fastboot', create=True)
+  def testListDevicesNone(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = ''
+    device_serial_numbers = fastbootsh.FastbootDevice.ListDevices()
+    mock_fastboot_commands.assert_called_once_with('devices')
+    self.assertEqual(0, len(device_serial_numbers))
+
+  @patch('sh.fastboot', create=True)
+  def testListDevicesFailure(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_FAILURE
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      fastbootsh.FastbootDevice.ListDevices()
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+  # Test FastbootDevice.Oem
+  @patch('sh.fastboot', create=True)
+  def testOem(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_SUCCESS
+    command = 'TEST COMMAND'
+    device = fastbootsh.FastbootDevice(self.TEST_SERIAL)
+    message = device.Oem(command)
+    mock_fastboot_commands.assert_called_once_with('-s',
+                                                   self.TEST_SERIAL,
+                                                   'oem',
+                                                   command)
+    self.assertEqual(self.TEST_MESSAGE_SUCCESS, message)
+
+  @patch('sh.fastboot', create=True)
+  def testOemFailure(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_FAILURE
+    command = 'TEST COMMAND'
+    device = fastbootsh.FastbootDevice(self.TEST_SERIAL)
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      device.Oem(command)
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+  # Test FastbootDevice.Upload
+  @patch('sh.fastboot', create=True)
+  def testUpload(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_SUCCESS
+    command = 'TEST COMMAND'
+    device = fastbootsh.FastbootDevice(self.TEST_SERIAL)
+    message = device.Upload(command)
+    mock_fastboot_commands.assert_called_once_with('-s',
+                                                   self.TEST_SERIAL,
+                                                   'get_staged',
+                                                   command)
+    self.assertEqual(self.TEST_MESSAGE_SUCCESS, message)
+
+  @patch('sh.fastboot', create=True)
+  def testUploadFailure(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_FAILURE
+    command = 'TEST COMMAND'
+    device = fastbootsh.FastbootDevice(self.TEST_SERIAL)
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      device.Upload(command)
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+  # Test FastbootDevice.Download
+  @patch('sh.fastboot', create=True)
+  def testDownload(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_SUCCESS
+    command = 'TEST COMMAND'
+    device = fastbootsh.FastbootDevice(self.TEST_SERIAL)
+    message = device.Download(command)
+    mock_fastboot_commands.assert_called_once_with('-s',
+                                                   self.TEST_SERIAL,
+                                                   'stage',
+                                                   command)
+    self.assertEqual(self.TEST_MESSAGE_SUCCESS, message)
+
+  @patch('sh.fastboot', create=True)
+  def testDownloadFailure(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_FAILURE
+    command = 'TEST COMMAND'
+    device = fastbootsh.FastbootDevice(self.TEST_SERIAL)
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      device.Download(command)
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/at-factory-tool/fastbootsubp.py b/at-factory-tool/fastbootsubp.py
new file mode 100644
index 0000000..60dd6cf
--- /dev/null
+++ b/at-factory-tool/fastbootsubp.py
@@ -0,0 +1,94 @@
+# !/usr/bin/python
+"""Fastboot Interface Implementation using sh library.
+
+"""
+import subprocess
+import fastboot_exceptions
+
+
+class FastbootDevice(object):
+  """An abstracted fastboot device object.
+
+  Attributes:
+    serial_number: The serial number of the fastboot device.
+  """
+
+  @staticmethod
+  def ListDevices():
+    """List all fastboot devices.
+
+    Returns:
+      A list of serial numbers for all the fastboot devices.
+    """
+    out = subprocess.check_output(['fastboot', 'devices'])
+    if out.startswith('FAIL'):
+      raise fastboot_exceptions.FastbootFailure(out)
+
+    device_serial_numbers = out.replace('\tfastboot', '').rstrip().split('\n')
+    # filter out empty string
+    return filter(None, device_serial_numbers)
+
+  def __init__(self, serial_number):
+    """Initiate the fastboot device object.
+
+    Args:
+      serial_number: The serial number of the fastboot device.
+    """
+    self.serial_number = serial_number
+
+  def Oem(self, oem_command):
+    """Run an OEM command.
+
+    Args:
+      oem_command: The OEM command to run.
+    Returns:
+      The result message for the OEM command.
+    Raises:
+      FastbootFailure: If failure happens during the command.
+    """
+    out = subprocess.check_output(['fastboot', '-s', self.serial_number,
+                                   'oem', oem_command])
+    if out.startswith('FAIL'):
+      raise fastboot_exceptions.FastbootFailure(out)
+    return out
+
+  def Upload(self, file_path):
+    """Pulls a file from the fastboot device to the local file system.
+
+    Args:
+      file_path: The file path of the file system
+        that the remote file would be pulled to.
+    Returns:
+      The output for the fastboot command required.
+    Raises:
+      FastbootFailure: If failure happens during the command.
+    """
+    out = subprocess.check_output(['fastboot', '-s', self.serial_number,
+                                   'get_staged', file_path])
+    if out.startswith('FAIL'):
+      raise fastboot_exceptions.FastbootFailure(out)
+    return out
+
+  def Download(self, file_path):
+    """Push a file from the file system to the fastboot device.
+
+    Args:
+      file_path: The file path of the file on the local file system
+        that would be pushed to fastboot device.
+    Returns:
+      The output for the fastboot command required.
+    Raises:
+      FastbootFailure: If failure happens during the command.
+    """
+    out = subprocess.check_output(['fastboot', '-s', self.serial_number,
+                                   'stage', file_path])
+    if out.startswith('FAIL'):
+      raise fastboot_exceptions.FastbootFailure(out)
+    return out
+
+  def Disconnect(self):
+    """Disconnect from the fastboot device."""
+    pass
+
+  def __del__(self):
+    self.Disconnect()
diff --git a/at-factory-tool/fastbootsubp_unittest.py b/at-factory-tool/fastbootsubp_unittest.py
new file mode 100644
index 0000000..10ca2cf
--- /dev/null
+++ b/at-factory-tool/fastbootsubp_unittest.py
@@ -0,0 +1,129 @@
+"""Unit test for fastboot interface using sh library."""
+import unittest
+
+import fastboot_exceptions
+import fastbootsubp
+from mock import patch
+
+
+class FastbootSubpTest(unittest.TestCase):
+  ATFA_TEST_SERIAL = 'ATFA_TEST_SERIAL'
+  TEST_MESSAGE_FAILURE = 'FAIL: TEST MESSAGE'
+  TEST_MESSAGE_SUCCESS = 'OKAY: TEST MESSAGE'
+  TEST_SERIAL = 'TEST_SERIAL'
+
+  def setUp(self):
+    pass
+
+  # Test FastbootDevice.ListDevices
+  @patch('subprocess.check_output', create=True)
+  def testListDevicesOneDevice(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_SERIAL + '\tfastboot'
+    device_serial_numbers = fastbootsubp.FastbootDevice.ListDevices()
+    mock_fastboot_commands.assert_called_once_with(['fastboot', 'devices'])
+    self.assertEqual(1, len(device_serial_numbers))
+    self.assertEqual(self.TEST_SERIAL, device_serial_numbers[0])
+
+  @patch('subprocess.check_output', create=True)
+  def testListDevicesTwoDevices(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = (self.TEST_SERIAL + '\tfastboot\n' +
+                                           self.ATFA_TEST_SERIAL + '\tfastboot')
+    device_serial_numbers = fastbootsubp.FastbootDevice.ListDevices()
+    mock_fastboot_commands.assert_called_once_with(['fastboot', 'devices'])
+    self.assertEqual(2, len(device_serial_numbers))
+    self.assertEqual(self.TEST_SERIAL, device_serial_numbers[0])
+    self.assertEqual(self.ATFA_TEST_SERIAL, device_serial_numbers[1])
+
+  @patch('subprocess.check_output', create=True)
+  def testListDevicesMultiDevices(self, mock_fastboot_commands):
+    one_device = self.TEST_SERIAL + '\tfastboot'
+    result = one_device
+    for _ in range(0, 9):
+      result += '\n' + one_device
+    mock_fastboot_commands.return_value = result
+    device_serial_numbers = fastbootsubp.FastbootDevice.ListDevices()
+    mock_fastboot_commands.assert_called_once_with(['fastboot', 'devices'])
+    self.assertEqual(10, len(device_serial_numbers))
+
+  @patch('subprocess.check_output', create=True)
+  def testListDevicesNone(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = ''
+    device_serial_numbers = fastbootsubp.FastbootDevice.ListDevices()
+    mock_fastboot_commands.assert_called_once_with(['fastboot', 'devices'])
+    self.assertEqual(0, len(device_serial_numbers))
+
+  @patch('subprocess.check_output', create=True)
+  def testListDevicesFailure(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_FAILURE
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      fastbootsubp.FastbootDevice.ListDevices()
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+  # Test FastbootDevice.Oem
+  @patch('subprocess.check_output', create=True)
+  def testOem(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_SUCCESS
+    command = 'TEST COMMAND'
+    device = fastbootsubp.FastbootDevice(self.TEST_SERIAL)
+    message = device.Oem(command)
+    mock_fastboot_commands.assert_called_once_with(['fastboot', '-s',
+                                                    self.TEST_SERIAL,
+                                                    'oem',
+                                                    command])
+    self.assertEqual(self.TEST_MESSAGE_SUCCESS, message)
+
+  @patch('subprocess.check_output', create=True)
+  def testOemFailure(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_FAILURE
+    command = 'TEST COMMAND'
+    device = fastbootsubp.FastbootDevice(self.TEST_SERIAL)
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      device.Oem(command)
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+  # Test FastbootDevice.Upload
+  @patch('subprocess.check_output', create=True)
+  def testUpload(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_SUCCESS
+    command = 'TEST COMMAND'
+    device = fastbootsubp.FastbootDevice(self.TEST_SERIAL)
+    message = device.Upload(command)
+    mock_fastboot_commands.assert_called_once_with(['fastboot', '-s',
+                                                    self.TEST_SERIAL,
+                                                    'get_staged',
+                                                    command])
+    self.assertEqual(self.TEST_MESSAGE_SUCCESS, message)
+
+  @patch('subprocess.check_output', create=True)
+  def testUploadFailure(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_FAILURE
+    command = 'TEST COMMAND'
+    device = fastbootsubp.FastbootDevice(self.TEST_SERIAL)
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      device.Upload(command)
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+  # Test FastbootDevice.Download
+  @patch('subprocess.check_output', create=True)
+  def testDownload(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_SUCCESS
+    command = 'TEST COMMAND'
+    device = fastbootsubp.FastbootDevice(self.TEST_SERIAL)
+    message = device.Download(command)
+    mock_fastboot_commands.assert_called_once_with(['fastboot', '-s',
+                                                    self.TEST_SERIAL,
+                                                    'stage',
+                                                    command])
+    self.assertEqual(self.TEST_MESSAGE_SUCCESS, message)
+
+  @patch('subprocess.check_output', create=True)
+  def testDownloadFailure(self, mock_fastboot_commands):
+    mock_fastboot_commands.return_value = self.TEST_MESSAGE_FAILURE
+    command = 'TEST COMMAND'
+    device = fastbootsubp.FastbootDevice(self.TEST_SERIAL)
+    with self.assertRaises(fastboot_exceptions.FastbootFailure) as e:
+      device.Download(command)
+      self.assertEqual(self.TEST_MESSAGE_FAILURE, str(e))
+
+if __name__ == '__main__':
+  unittest.main()
diff --git a/at-factory-tool/toolbar_devices.png b/at-factory-tool/toolbar_devices.png
new file mode 100644
index 0000000..6c2909c
--- /dev/null
+++ b/at-factory-tool/toolbar_devices.png
Binary files differ
diff --git a/at-factory-tool/toolbar_exit.png b/at-factory-tool/toolbar_exit.png
new file mode 100644
index 0000000..949640f
--- /dev/null
+++ b/at-factory-tool/toolbar_exit.png
Binary files differ
diff --git a/at-factory-tool/toolbar_reboot.png b/at-factory-tool/toolbar_reboot.png
new file mode 100644
index 0000000..6ececb2
--- /dev/null
+++ b/at-factory-tool/toolbar_reboot.png
Binary files differ
diff --git a/at-factory-tool/toolbar_shutdown.png b/at-factory-tool/toolbar_shutdown.png
new file mode 100644
index 0000000..468525f
--- /dev/null
+++ b/at-factory-tool/toolbar_shutdown.png
Binary files differ
diff --git a/at-factory-tool/toolbar_storage.png b/at-factory-tool/toolbar_storage.png
new file mode 100644
index 0000000..3ad7f40
--- /dev/null
+++ b/at-factory-tool/toolbar_storage.png
Binary files differ