diff --git a/wlauto/workloads/googlephotos/__init__.py b/wlauto/workloads/googlephotos/__init__.py
new file mode 100644
index 00000000..db0107b3
--- /dev/null
+++ b/wlauto/workloads/googlephotos/__init__.py
@@ -0,0 +1,99 @@
+import os
+import re
+
+from wlauto import AndroidUiAutoBenchmark, Parameter
+
+
+class Googlephotos(AndroidUiAutoBenchmark):
+
+    name = 'googlephotos'
+    package = 'com.google.android.apps.photos'
+    activity = 'com.google.android.apps.photos.home.HomeActivity'
+
+    description = """
+    A workload to perform standard productivity tasks with googlephotos.
+
+    The workload carries out various tasks, such as browsing images, performing zooms,
+    postprocessing and saving a selected image to file.
+
+    NOTE: This workload requires four jpeg files to be placed in the
+    dependencies directory to run.
+
+    Although this workload attempts to be network independent it requires a
+    network connection (ideally, wifi) to run. This is because the welcome
+    screen UI is dependent on an existing connection.
+    """
+
+    parameters = [
+        Parameter('dumpsys_enabled', kind=bool, default=True,
+                  description="""
+                  If ``True``, dumpsys captures will be carried out during the
+                  test run.  The output is piped to log files which are then
+                  pulled from the phone.
+                  """),
+    ]
+
+    instrumentation_log = ''.join([name, '_instrumentation.log'])
+    file_prefix = 'wa_test_'
+
+    def __init__(self, device, **kwargs):
+        super(Googlephotos, self).__init__(device, **kwargs)
+        self.output_file = os.path.join(self.device.working_directory, self.instrumentation_log)
+        self.camera_dir = self.device.path.join(self.device.external_storage_directory,
+                                                'DCIM/Camera/')
+
+    def validate(self):
+        super(Googlephotos, self).validate()
+        self.uiauto_params['package'] = self.package
+        self.uiauto_params['output_dir'] = self.device.working_directory
+        self.uiauto_params['output_file'] = self.output_file
+        self.uiauto_params['dumpsys_enabled'] = self.dumpsys_enabled
+
+    def setup(self, context):
+        super(Googlephotos, self).setup(context)
+
+        for entry in os.listdir(self.dependencies_directory):
+            wa_file = ''.join([self.file_prefix, entry])
+            if entry.endswith(".jpg"):
+                self.device.push_file(os.path.join(self.dependencies_directory, entry),
+                                      os.path.join(self.camera_dir, wa_file),
+                                      timeout=300)
+
+        # Force a re-index of the mediaserver cache to pick up new files
+        self.device.execute('am broadcast -a android.intent.action.MEDIA_MOUNTED -d file:///sdcard')
+
+    def update_result(self, context):
+        super(Googlephotos, self).update_result(context)
+
+        if self.dumpsys_enabled:
+            self.device.pull_file(self.output_file, context.output_directory)
+            result_file = os.path.join(context.output_directory, self.instrumentation_log)
+
+            with open(result_file, 'r') as wfh:
+                pattern = r'(?P<key>\w+)\s+(?P<value1>\d+)\s+(?P<value2>\d+)\s+(?P<value3>\d+)'
+                regex = re.compile(pattern)
+                for line in wfh:
+                    match = regex.search(line)
+                    if match:
+                        context.result.add_metric((match.group('key') + "_start"),
+                                                  match.group('value1'))
+                        context.result.add_metric((match.group('key') + "_finish"),
+                                                  match.group('value2'))
+                        context.result.add_metric((match.group('key') + "_duration"),
+                                                  match.group('value3'))
+
+    def teardown(self, context):
+        super(Googlephotos, self).teardown(context)
+
+        for entry in self.device.listdir(self.device.working_directory):
+            if entry.startswith(self.name) and entry.endswith(".log"):
+                self.device.pull_file(os.path.join(self.device.working_directory, entry),
+                                      context.output_directory)
+                self.device.delete_file(os.path.join(self.device.working_directory, entry))
+
+        for entry in self.device.listdir(self.camera_dir):
+            if entry.startswith(self.file_prefix) and entry.endswith(".jpg"):
+                self.device.delete_file(os.path.join(self.camera_dir, entry))
+
+        # Force a re-index of the mediaserver cache to removed cached files
+        self.device.execute('am broadcast -a android.intent.action.MEDIA_MOUNTED -d file:///sdcard')
diff --git a/wlauto/workloads/googlephotos/com.arm.wlauto.uiauto.googlephotos.jar b/wlauto/workloads/googlephotos/com.arm.wlauto.uiauto.googlephotos.jar
new file mode 100644
index 00000000..2a4fdd3d
Binary files /dev/null and b/wlauto/workloads/googlephotos/com.arm.wlauto.uiauto.googlephotos.jar differ
diff --git a/wlauto/workloads/googlephotos/uiauto/build.sh b/wlauto/workloads/googlephotos/uiauto/build.sh
new file mode 100755
index 00000000..f60c0b43
--- /dev/null
+++ b/wlauto/workloads/googlephotos/uiauto/build.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+class_dir=bin/classes/com/arm/wlauto/uiauto
+base_class=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', '*.class')"`
+mkdir -p $class_dir
+cp $base_class $class_dir
+
+ant build
+
+if [[ -f bin/com.arm.wlauto.uiauto.googlephotos.jar ]]; then
+    cp bin/com.arm.wlauto.uiauto.googlephotos.jar ..
+fi
diff --git a/wlauto/workloads/googlephotos/uiauto/build.xml b/wlauto/workloads/googlephotos/uiauto/build.xml
new file mode 100644
index 00000000..648161d0
--- /dev/null
+++ b/wlauto/workloads/googlephotos/uiauto/build.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="com.arm.wlauto.uiauto.googlephotos" default="help">
+
+    <!-- The local.properties file is created and updated by the 'android' tool.
+         It contains the path to the SDK. It should *NOT* be checked into
+         Version Control Systems. -->
+    <property file="local.properties" />
+
+    <!-- The ant.properties file can be created by you. It is only edited by the
+         'android' tool to add properties to it.
+         This is the place to change some Ant specific build properties.
+         Here are some properties you may want to change/update:
+
+         source.dir
+             The name of the source directory. Default is 'src'.
+         out.dir
+             The name of the output directory. Default is 'bin'.
+
+         For other overridable properties, look at the beginning of the rules
+         files in the SDK, at tools/ant/build.xml
+
+         Properties related to the SDK location or the project target should
+         be updated using the 'android' tool with the 'update' action.
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems.
+
+         -->
+    <property file="ant.properties" />
+
+    <!-- if sdk.dir was not set from one of the property file, then
+         get it from the ANDROID_HOME env var.
+         This must be done before we load project.properties since
+         the proguard config can use sdk.dir -->
+    <property environment="env" />
+    <condition property="sdk.dir" value="${env.ANDROID_HOME}">
+        <isset property="env.ANDROID_HOME" />
+    </condition>
+
+    <!-- The project.properties file is created and updated by the 'android'
+         tool, as well as ADT.
+
+         This contains project specific properties such as project target, and library
+         dependencies. Lower level build properties are stored in ant.properties
+         (or in .classpath for Eclipse projects).
+
+         This file is an integral part of the build system for your
+         application and should be checked into Version Control Systems. -->
+    <loadproperties srcFile="project.properties" />
+
+    <!-- quick check on sdk.dir -->
+    <fail
+            message="sdk.dir is missing. Make sure to generate local.properties using 'android update project' or to inject it through the ANDROID_HOME environment variable."
+            unless="sdk.dir"
+    />
+
+    <!--
+        Import per project custom build rules if present at the root of the project.
+        This is the place to put custom intermediary targets such as:
+            -pre-build
+            -pre-compile
+            -post-compile (This is typically used for code obfuscation.
+                           Compiled code location: ${out.classes.absolute.dir}
+                           If this is not done in place, override ${out.dex.input.absolute.dir})
+            -post-package
+            -post-build
+            -pre-clean
+    -->
+    <import file="custom_rules.xml" optional="true" />
+
+    <!-- Import the actual build file.
+
+         To customize existing targets, there are two options:
+         - Customize only one target:
+             - copy/paste the target into this file, *before* the
+               <import> task.
+             - customize it to your needs.
+         - Customize the whole content of build.xml
+             - copy/paste the content of the rules files (minus the top node)
+               into this file, replacing the <import> task.
+             - customize to your needs.
+
+         ***********************
+         ****** IMPORTANT ******
+         ***********************
+         In all cases you must update the value of version-tag below to read 'custom' instead of an integer,
+         in order to avoid having your file be overridden by tools such as "android update project"
+    -->
+    <!-- version-tag: VERSION_TAG -->
+    <import file="${sdk.dir}/tools/ant/uibuild.xml" />
+
+</project>
diff --git a/wlauto/workloads/googlephotos/uiauto/project.properties b/wlauto/workloads/googlephotos/uiauto/project.properties
new file mode 100644
index 00000000..916037e3
--- /dev/null
+++ b/wlauto/workloads/googlephotos/uiauto/project.properties
@@ -0,0 +1,14 @@
+# This file is automatically generated by Android Tools.
+# Do not modify this file -- YOUR CHANGES WILL BE ERASED!
+#
+# This file must be checked in Version Control Systems.
+#
+# To customize properties used by the Ant build system edit
+# "ant.properties", and override values to adapt the script to your
+# project structure.
+#
+# To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home):
+#proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt
+
+# Project target.
+target=android-23
diff --git a/wlauto/workloads/googlephotos/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/googlephotos/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java
new file mode 100644
index 00000000..1c7a8c22
--- /dev/null
+++ b/wlauto/workloads/googlephotos/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java
@@ -0,0 +1,291 @@
+package com.arm.wlauto.uiauto.googlephotos;
+
+import android.os.Bundle;
+import android.graphics.Point;
+import android.graphics.Rect;
+
+// Import the uiautomator libraries
+import com.android.uiautomator.core.UiObject;
+import com.android.uiautomator.core.UiObjectNotFoundException;
+import com.android.uiautomator.core.UiSelector;
+
+import com.arm.wlauto.uiauto.UxPerfUiAutomation;
+
+import java.io.File;
+import java.io.FileWriter;
+import java.io.BufferedWriter;
+import java.util.concurrent.TimeUnit;
+import java.util.LinkedHashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Map.Entry;
+
+public class UiAutomation extends UxPerfUiAutomation {
+
+    public static String TAG = "uxperf_googlephotos";
+
+    public Bundle parameters;
+    private long viewTimeout =  TimeUnit.SECONDS.toMillis(20);
+    private LinkedHashMap<String, Timer> timingResults = new LinkedHashMap<String, Timer>();
+
+    public void runUiAutomation() throws Exception {
+        Timer result = new Timer();
+        result.start();
+        parameters = getParams();
+
+        dismissWelcomeView();
+        gesturesTest();
+        editPhotoTest();
+
+        result.end();
+        timingResults.put("total", result);
+
+        writeResultsToFile(timingResults, parameters.getString("output_file"));
+    }
+
+    private void dismissWelcomeView() throws Exception {
+        // Click through the first two pages and make sure that we don't sign
+        // in to our google account. This ensures the same set of photographs
+        // are placed in the camera directory for each run.
+
+        sleep(3); // Pause while splash screen loads
+
+        UiObject getStarteddButton =
+            getUiObjectByResourceId("com.google.android.apps.photos:id/get_started",
+                                    "android.widget.Button");
+        getStarteddButton.clickAndWaitForNewWindow();
+
+        UiObject welcomeButton =
+            getUiObjectByResourceId("com.google.android.apps.photos:id/name",
+                                    "android.widget.TextView");
+        welcomeButton.clickAndWaitForNewWindow();
+
+        UiObject useWithoutAccount =
+            getUiObjectByText("Use without an account", "android.widget.TextView");
+        useWithoutAccount.clickAndWaitForNewWindow();
+
+        // Dismiss welcome views promoting app features
+        sleep(1);
+        uiDeviceSwipeLeft(10);
+        sleep(1);
+        uiDeviceSwipeLeft(10);
+        sleep(1);
+        uiDeviceSwipeLeft(10);
+        sleep(1);
+
+        UiObject nextButton =
+            getUiObjectByResourceId("com.google.android.apps.photos:id/next_button",
+                                    "android.widget.ImageView");
+        nextButton.clickAndWaitForNewWindow();
+    }
+
+    private void gesturesTest () throws Exception {
+        String testTag = "gestures";
+
+        // Perform a range of swipe tests while browsing photo gallery
+        LinkedHashMap<String, GestureTestParams> testParams = new LinkedHashMap<String, GestureTestParams>();
+        testParams.put("swipe_left", new GestureTestParams(GestureType.UIDEVICE_SWIPE, Direction.LEFT, 10));
+        testParams.put("pinch_out", new GestureTestParams(GestureType.PINCH, PinchType.OUT, 100, 50));
+        testParams.put("pinch_in", new GestureTestParams(GestureType.PINCH, PinchType.IN, 100, 50));
+        testParams.put("swipe_right", new GestureTestParams(GestureType.UIDEVICE_SWIPE, Direction.RIGHT, 10));
+        testParams.put("swipe_up", new GestureTestParams(GestureType.UIDEVICE_SWIPE, Direction.UP, 10));
+
+        Iterator<Entry<String, GestureTestParams>> it = testParams.entrySet().iterator();
+
+        // Select third photograph
+        selectPhoto(2);
+
+        while (it.hasNext()) {
+            Map.Entry<String, GestureTestParams> pair = it.next();
+            GestureType type = pair.getValue().gestureType;
+            Direction dir = pair.getValue().gestureDirection;
+            PinchType pinch = pair.getValue().pinchType;
+            int steps = pair.getValue().steps;
+            int percent = pair.getValue().percent;
+
+            String runName = String.format(testTag + "_" + pair.getKey());
+            String gfxInfologName =  String.format(TAG + "_" + runName + "_gfxInfo.log");
+            String surfFlingerlogName =  String.format(runName + "_surfFlinger.log");
+            String viewName = new String("com.google.android.apps.photos.home.HomeActivity");
+
+            UiObject view = new UiObject(new UiSelector().enabled(true));
+
+            if (!view.waitForExists(viewTimeout)) {
+                throw new UiObjectNotFoundException("Could not find \"photo view\".");
+            };
+
+            startDumpsysGfxInfo();
+            startDumpsysSurfaceFlinger(viewName);
+
+            Timer results = new Timer();
+
+            switch (type) {
+                case UIDEVICE_SWIPE:
+                    results = uiDeviceSwipeTest(dir, steps);
+                    break;
+                case UIOBJECT_SWIPE:
+                    results = uiObjectSwipeTest(view, dir, steps);
+                    break;
+                case PINCH:
+                    results = uiObjectVertPinchTest(view, pinch, steps, percent);
+                    break;
+                default:
+                    break;
+            }
+
+            stopDumpsysSurfaceFlinger(viewName, surfFlingerlogName);
+            stopDumpsysGfxInfo(gfxInfologName);
+
+            timingResults.put(runName, results);
+        }
+    }
+
+    private void editPhotoTest() throws Exception {
+        String testTag = "edit_photo";
+
+        Timer result = new Timer();
+        result.start();
+
+        // Select first photograph
+        selectPhoto(0);
+        UiObject editView = getUiObjectByResourceId("com.google.android.apps.photos:id/edit",
+                                                    "android.widget.ImageView");
+        editView.click();
+
+        UiObject editColor = getUiObjectByText("Colour", "android.widget.RadioButton");
+        editColor.click();
+
+        UiObject seekBar = getUiObjectByResourceId("com.google.android.apps.photos:id/cpe_strength_seek_bar",
+                                                   "android.widget.SeekBar");
+        seekBar.swipeLeft(10);
+
+        UiObject accept = getUiObjectByDescription("Accept", "android.widget.ImageView");
+        accept.click();
+
+        UiObject save = getUiObjectByText("SAVE", "android.widget.TextView");
+        save.click();
+
+        // Return to application home screen
+        getUiDevice().pressBack();
+
+        result.end();
+        timingResults.put(testTag, result);
+    }
+
+    // Helper to click on an individual photographs based on index in Camera gallery.
+    private void selectPhoto(int index) throws Exception {
+        UiObject cameraHeading = new UiObject(new UiSelector().text("Camera"));
+        cameraHeading.clickAndWaitForNewWindow();
+
+        UiObject photo =
+            new UiObject(new UiSelector().resourceId("com.google.android.apps.photos:id/recycler_view")
+                                         .childSelector(new UiSelector()
+                                         .index(index)));
+        photo.click();
+    }
+
+    // Helper for testing zoom facility. NOTE: the built in UiObject methods
+    // pinchIn() and pinchOut() do not zoom appropriately for this application.
+    private Timer uiObjectVertPinchTest(
+            UiObject view, PinchType direction,
+            int steps, int percent) throws Exception {
+
+        Timer results = new Timer();
+        results.start();
+
+        final int FINGER_TOUCH_HALF_WIDTH = 20;
+
+        // make value between 1 and 100
+        percent = (percent < 0) ? 1 : (percent > 100) ? 100 : percent;
+        float percentage = percent / 100f;
+
+        Rect rect = view.getVisibleBounds();
+        if (rect.width() <= FINGER_TOUCH_HALF_WIDTH * 2)
+            throw new IllegalStateException("Object width is too small for operation");
+
+        // start from the same point at the center of the control
+        Point startPoint1 = new Point(rect.centerX(), rect.centerY() + FINGER_TOUCH_HALF_WIDTH);
+        Point startPoint2 = new Point(rect.centerX(), rect.centerY() - FINGER_TOUCH_HALF_WIDTH);
+
+        // End at the top-center and bottom-center of the control
+        Point endPoint1 = new Point(rect.centerX(), rect.centerY() + (int) ((rect.height() / 2) * percentage));
+        Point endPoint2 = new Point(rect.centerX(), rect.centerY() - (int) ((rect.height() / 2) * percentage));
+
+        if (direction.equals(PinchType.IN)) {
+            view.performTwoPointerGesture(endPoint1, endPoint2, startPoint1, startPoint2, steps);
+        } else if (direction.equals(PinchType.OUT)) {
+            view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps);
+        }
+
+        results.end();
+
+        return results;
+    }
+
+    private class GestureTestParams {
+        GestureType gestureType;
+        Direction gestureDirection;
+        PinchType pinchType;
+        private int percent;
+        private int steps;
+
+        GestureTestParams(GestureType gesture, Direction direction, int steps) {
+            this.gestureType = gesture;
+            this.gestureDirection = direction;
+            this.pinchType = PinchType.NULL;
+            this.steps = steps;
+            this.percent = 0;
+        }
+
+        GestureTestParams(GestureType gesture, PinchType pinchType, int steps, int percent) {
+            this.gestureType = gesture;
+            this.gestureDirection = Direction.NULL;
+            this.pinchType = pinchType;
+            this.steps = steps;
+            this.percent = percent;
+        }
+    }
+
+    private void writeResultsToFile(LinkedHashMap timingResults, String file) throws Exception {
+        // Write out the key/value pairs to the instrumentation log file
+        FileWriter fstream = new FileWriter(file);
+        BufferedWriter out = new BufferedWriter(fstream);
+        Iterator<Entry<String, Timer>> it = timingResults.entrySet().iterator();
+
+        while (it.hasNext()) {
+            Map.Entry<String, Timer> pairs = it.next();
+            Timer results = pairs.getValue();
+            long start = results.getStart();
+            long finish = results.getFinish();
+            long duration = results.getDuration();
+            out.write(String.format(pairs .getKey() + " " + start + " " + finish + " " + duration + "\n"));
+        }
+        out.close();
+    }
+
+    private void startDumpsysSurfaceFlinger(String view) {
+        if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) {
+            initDumpsysSurfaceFlinger(parameters.getString("package"), view);
+        }
+    }
+
+    private void stopDumpsysSurfaceFlinger(String view, String filename) throws Exception {
+        if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) {
+            File out_file = new File(parameters.getString("output_dir"), filename);
+            exitDumpsysSurfaceFlinger(parameters.getString("package"), view, out_file);
+          }
+    }
+
+    private void startDumpsysGfxInfo() {
+        if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) {
+            initDumpsysGfxInfo(parameters.getString("package"));
+          }
+    }
+
+    private void stopDumpsysGfxInfo(String filename) throws Exception {
+      if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) {
+            File out_file = new File(parameters.getString("output_dir"), filename);
+            exitDumpsysGfxInfo(parameters.getString("package"), out_file);
+          }
+    }
+}