Merge pull request #163 from marcbonnici/Derived_Measurements

Add support for AcmeCape and Derived Measurements
diff --git a/devlib/target.py b/devlib/target.py
index 27b1c4b..b96bbdc 100644
--- a/devlib/target.py
+++ b/devlib/target.py
@@ -1061,20 +1061,25 @@
 
     # Android-specific
 
-    def swipe_to_unlock(self, direction="horizontal"):
+    def swipe_to_unlock(self, direction="diagonal"):
         width, height = self.screen_resolution
         command = 'input swipe {} {} {} {}'
-        if direction == "horizontal":
-            swipe_heigh = height * 2 // 3
+        if direction == "diagonal":
             start = 100
             stop = width - start
-            self.execute(command.format(start, swipe_heigh, stop, swipe_heigh))
-        if direction == "vertical":
-            swipe_middle = height / 2
-            swipe_heigh = height * 2 // 3
-            self.execute(command.format(swipe_middle, swipe_heigh, swipe_middle, 0))
+            swipe_height = height * 2 // 3
+            self.execute(command.format(start, swipe_height, stop, 0))
+        elif direction == "horizontal":
+            swipe_height = height * 2 // 3
+            start = 100
+            stop = width - start
+            self.execute(command.format(start, swipe_height, stop, swipe_height))
+        elif direction == "vertical":
+            swipe_middle = width / 2
+            swipe_height = height * 2 // 3
+            self.execute(command.format(swipe_middle, swipe_height, swipe_middle, 0))
         else:
-            raise DeviceError("Invalid swipe direction: {}".format(self.swipe_to_unlock))
+            raise TargetError("Invalid swipe direction: {}".format(direction))
 
     def getprop(self, prop=None):
         props = AndroidProperties(self.execute('getprop'))
@@ -1186,6 +1191,69 @@
         if self.is_screen_on():
             self.execute('input keyevent 26')
 
+    def set_auto_brightness(self, auto_brightness):
+        cmd = 'settings put system screen_brightness_mode {}'
+        self.execute(cmd.format(int(boolean(auto_brightness))))
+
+    def get_auto_brightness(self):
+        cmd = 'settings get system screen_brightness_mode'
+        return boolean(self.execute(cmd).strip())
+
+    def set_brightness(self, value):
+        if not 0 <= value <= 255:
+            msg = 'Invalid brightness "{}"; Must be between 0 and 255'
+            raise ValueError(msg.format(value))
+        self.set_auto_brightness(False)
+        cmd = 'settings put system screen_brightness {}'
+        self.execute(cmd.format(int(value)))
+
+    def get_brightness(self):
+        cmd = 'settings get system screen_brightness'
+        return integer(self.execute(cmd).strip())
+
+    def get_airplane_mode(self):
+        cmd = 'settings get global airplane_mode_on'
+        return boolean(self.execute(cmd).strip())
+
+    def set_airplane_mode(self, mode):
+        root_required = self.get_sdk_version() > 23
+        if root_required and not self.is_rooted:
+            raise TargetError('Root is required to toggle airplane mode on Android 7+')
+        cmd = 'settings put global airplane_mode_on {}'
+        self.execute(cmd.format(int(boolean(mode))))
+        self.execute('am broadcast -a android.intent.action.AIRPLANE_MODE', as_root=root_required)
+
+    def get_auto_rotation(self):
+        cmd = 'settings get system accelerometer_rotation'
+        return boolean(self.execute(cmd).strip())
+
+    def set_auto_rotation(self, autorotate):
+        cmd = 'settings put system accelerometer_rotation {}'
+        self.execute(cmd.format(int(boolean(autorotate))))
+
+    def set_natural_rotation(self):
+        self.set_rotation(0)
+
+    def set_left_rotation(self):
+        self.set_rotation(1)
+
+    def set_inverted_rotation(self):
+        self.set_rotation(2)
+
+    def set_right_rotation(self):
+        self.set_rotation(3)
+
+    def get_rotation(self):
+        cmd = 'settings get system user_rotation'
+        return self.execute(cmd).strip()
+
+    def set_rotation(self, rotation):
+        if not 0 <= rotation <= 3:
+            raise ValueError('Rotation value must be between 0 and 3')
+        self.set_auto_rotation(False)
+        cmd = 'settings put system user_rotation {}'
+        self.execute(cmd.format(rotation))
+
     def homescreen(self):
         self.execute('am start -a android.intent.action.MAIN -c android.intent.category.HOME')
 
diff --git a/devlib/utils/types.py b/devlib/utils/types.py
index be30bfc..645328d 100644
--- a/devlib/utils/types.py
+++ b/devlib/utils/types.py
@@ -68,6 +68,15 @@
     """
     if isinstance(value, int):
         return value
+
+    if isinstance(value, basestring):
+        value = value.strip()
+        if value.endswith('%'):
+            try:
+                return float(value.rstrip('%')) / 100
+            except ValueError:
+                raise ValueError('Not numeric: {}'.format(value))
+
     try:
         fvalue = float(value)
     except ValueError:
diff --git a/doc/target.rst b/doc/target.rst
index b64246a..08472e2 100644
--- a/doc/target.rst
+++ b/doc/target.rst
@@ -2,18 +2,18 @@
 ======
 
 
-.. class:: Target(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT)
-   
+.. class:: Target(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=None)
+
     :class:`Target` is the primary interface to the remote device. All interactions
     with the device are performed via a :class:`Target` instance, either
     directly, or via its modules or a wrapper interface (such as an
     :class:`Instrument`).
 
-    :param connection_settings: A ``dict`` that specifies how to connect to the remote 
+    :param connection_settings: A ``dict`` that specifies how to connect to the remote
        device. Its contents depend on the specific :class:`Target` type (used see
        :ref:`connection-types`\ ).
 
-    :param platform: A :class:`Target` defines interactions at Operating System level. A 
+    :param platform: A :class:`Target` defines interactions at Operating System level. A
         :class:`Platform` describes the underlying hardware (such as CPUs
         available). If a :class:`Platform` instance is not specified on
         :class:`Target` creation, one will be created automatically and it will
@@ -22,8 +22,8 @@
 
     :param working_directory: This is primary location for on-target file system
         interactions performed by ``devlib``. This location *must* be readable and
-        writable directly (i.e. without sudo) by the connection's user account. 
-        It may or may not allow execution. This location will be created, 
+        writable directly (i.e. without sudo) by the connection's user account.
+        It may or may not allow execution. This location will be created,
         if necessary, during ``setup()``.
 
         If not explicitly specified, this will be set to a default value
@@ -35,7 +35,7 @@
         (obviously). It should also be possible to write to this location,
         possibly with elevated privileges (i.e. on a rooted Linux target, it
         should be possible to write here with sudo, but not necessarily directly
-        by the connection's account). This location will be created, 
+        by the connection's account). This location will be created,
         if necessary, during ``setup()``.
 
         This location does *not* need to be same as the system's executables
@@ -52,7 +52,7 @@
 
     :param modules: a list of additional modules to be installed. Some modules will
         try to install by default (if supported by the underlying target).
-        Current default modules are ``hotplug``, ``cpufreq``, ``cpuidle``, 
+        Current default modules are ``hotplug``, ``cpufreq``, ``cpuidle``,
         ``cgroups``, and ``hwmon`` (See :ref:`modules`\ ).
 
         See modules documentation for more detail.
@@ -68,6 +68,9 @@
          prompted on the target. This may be used by some modules that establish
          auxiliary connections to a target over UART.
 
+    :param conn_cls: This is the type of connection that will be used to communicate
+        with the device.
+
 .. attribute:: Target.core_names
 
    This is a list containing names of CPU cores on the target, in the order in
@@ -94,7 +97,7 @@
 .. attribute:: Target.is_connected
 
    A boolean value that indicates whether an active connection exists to the
-   target device. 
+   target device.
 
 .. attribute:: Target.connected_as_root
 
@@ -146,7 +149,7 @@
              thread.
 
 .. method:: Target.connect([timeout])
-   
+
    Establish a connection to the target. It is usually not necessary to call
    this explicitly, as a connection gets automatically established on
    instantiation.
@@ -225,7 +228,7 @@
    :param timeout: Timeout (in seconds) for the execution of the command. If
        specified, an exception will be raised if execution does not complete
        with the specified period.
-   :param check_exit_code: If ``True`` (the default) the exit code (on target) 
+   :param check_exit_code: If ``True`` (the default) the exit code (on target)
        from execution of the command will be checked, and an exception will be
        raised if it is not ``0``.
    :param as_root: The command will be executed as root. This will fail on
@@ -262,7 +265,7 @@
           will be interpreted as a comma-separated list of cpu ranges, e.g.
           ``"0,4-7"``.
    :param as_root: Specify whether the command should be run as root
-   :param timeout: If this is specified and invocation does not terminate within this number 
+   :param timeout: If this is specified and invocation does not terminate within this number
            of seconds, an exception will be raised.
 
 .. method:: Target.background_invoke(binary [, args [, in_directory [, on_cpus [, as_root ]]]])
@@ -314,13 +317,13 @@
 
 .. method:: Target.write_value(path, value [, verify])
 
-   Write the value to the specified path on the target. This is primarily 
+   Write the value to the specified path on the target. This is primarily
    intended for sysfs/procfs/debugfs etc.
 
    :param path: file to write into
    :param value: value to be written
    :param verify: If ``True`` (the default) the value will be read back after
-       it is written to make sure it has been written successfully. This due to 
+       it is written to make sure it has been written successfully. This due to
        some sysfs entries silently failing to set the written value without
        returning an error code.
 
@@ -450,3 +453,110 @@
     Returns the path to the extracted contents. In case of files (gzip and
     bzip2), the path to the decompressed file is returned; for archives, the
     path to the directory with the archive's contents is returned.
+
+
+Android Target
+---------------
+
+.. class:: AndroidTarget(connection_settings=None, platform=None, working_directory=None, executables_directory=None, connect=True, modules=None, load_default_modules=True, shell_prompt=DEFAULT_SHELL_PROMPT, conn_cls=AdbConnection, package_data_directory="/data/data")
+
+    :class:`AndroidTarget` is a subclass of :class:`Target` with additional features specific to a device running Android.
+
+    :param package_data_directory: This is the location of the data stored
+        for installed Android packages on the device.
+
+.. method:: AndroidTarget.set_rotation(rotation)
+
+   Specify an integer representing the desired screen rotation with the
+   following mappings: Natural: ``0``, Rotated Left: ``1``, Inverted : ``2``
+   and Rotated Right : ``3``.
+
+.. method:: AndroidTarget.get_rotation(rotation)
+
+   Returns an integer value representing the orientation of the devices
+   screen. ``0`` : Natural, ``1`` : Rotated Left, ``2`` : Inverted
+   and ``3`` : Rotated Right.
+
+.. method:: AndroidTarget.set_natural_rotation()
+
+   Sets the screen orientation of the device to its natural (0 degrees)
+   orientation.
+
+.. method:: AndroidTarget.set_left_rotation()
+
+   Sets the screen orientation of the device to 90 degrees.
+
+.. method:: AndroidTarget.set_inverted_rotation()
+
+   Sets the screen orientation of the device to its inverted (180 degrees)
+   orientation.
+
+.. method:: AndroidTarget.set_right_rotation()
+
+   Sets the screen orientation of the device to 270 degrees.
+
+.. method:: AndroidTarget.set_auto_rotation(autorotate)
+
+   Specify a boolean value for whether the devices auto-rotation should
+   be enabled.
+
+.. method:: AndroidTarget.get_auto_rotation()
+
+   Returns ``True`` if the targets auto rotation is currently enabled and
+   ``False`` otherwise.
+
+.. method:: AndroidTarget.set_airplane_mode(mode)
+
+   Specify a boolean value for whether the device should be in airplane mode.
+
+   .. note:: Requires the device to be rooted if the device is running Android 7+.
+
+.. method:: AndroidTarget.get_airplane_mode()
+
+   Returns ``True`` if the target is currently in airplane mode and
+   ``False`` otherwise.
+
+.. method:: AndroidTarget.set_brightness(value)
+
+   Sets the devices screen brightness to a specified integer between ``0`` and
+   ``255``.
+
+.. method:: AndroidTarget.get_brightness()
+
+   Returns an integer between ``0`` and ``255`` representing the devices
+   current screen brightness.
+
+.. method:: AndroidTarget.set_auto_brightness(auto_brightness)
+
+   Specify a boolean value for whether the devices auto brightness
+   should be enabled.
+
+.. method:: AndroidTarget.get_auto_brightness()
+
+   Returns ``True`` if the targets auto brightness is currently
+   enabled and ``False`` otherwise.
+
+.. method:: AndroidTarget.ensure_screen_is_off()
+
+   Checks if the devices screen is on and if so turns it off.
+
+.. method:: AndroidTarget.ensure_screen_is_on()
+
+   Checks if the devices screen is off and if so turns it on.
+
+.. method:: AndroidTarget.is_screen_on()
+
+   Returns ``True`` if the targets screen is currently on and ``False``
+   otherwise.
+
+.. method:: AndroidTarget.homescreen()
+
+   Returns the device to its home screen.
+
+.. method:: AndroidTarget.swipe_to_unlock(direction="diagonal")
+
+   Performs a swipe input on the device to try and unlock the device.
+   A direction of ``"horizontal"``, ``"vertical"`` or ``"diagonal"``
+   can be supplied to specify in which direction the swipe should be
+   performed. By default ``"diagonal"`` will be used to try and
+   support the majority of newer devices.