diff --git a/wlauto/workloads/googleplaybooks/__init__.py b/wlauto/workloads/googleplaybooks/__init__.py
new file mode 100755
index 00000000..2f9581e3
--- /dev/null
+++ b/wlauto/workloads/googleplaybooks/__init__.py
@@ -0,0 +1,94 @@
+#    Copyright 2014-2016 ARM Limited
+#
+# 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.
+#
+
+import os
+import re
+
+from wlauto import AndroidUiAutoBenchmark, Parameter
+from wlauto.exceptions import DeviceError
+
+
+class Googleplaybooks(AndroidUiAutoBenchmark):
+
+    name = 'googleplaybooks'
+    package = 'com.google.android.apps.books'
+    version = '3.8.15'  # apk version
+    activity = 'com.google.android.apps.books.app.BooksActivity'
+    view = [package + '/com.google.android.apps.books.app.HomeActivity',
+            package + '/com.android.vending/com.google.android.finsky.activities.MainActivity',
+            package + '/com.google.android.apps.books.app.ReadingActivity',
+            package + '/com.google.android.apps.books.app.TableOfContentsActivityLight']
+    description = """
+    A workload to perform standard productivity tasks with googleplaybooks.
+    This workload performs various tasks, such as searching for a book title
+    online, browsing through a book, adding and removing notes, word searching,
+    and querying information about the book.
+
+    Test description:
+     1. Open Google Play Books application
+     2. Dismisses sync operation (if applicable)
+     3. Searches for a book title
+     4. Adds books to library if not already present
+     5. Opens 'My Library' contents
+     6. Opens selected  book
+     7. Gestures are performed to swipe between pages and pinch zoom in and out of a page
+     8. Selects a specified chapter based on page number from the navigation view
+     9. Selects a word in the centre of screen and adds a test note to the page
+    10. Removes the test note from the page (clean up)
+    11. Searches for the number of occurrences of a common word throughout the book
+    12. Switches page styles from 'Day' to 'Night' to 'Sepia' and back to 'Day'
+    13. Uses the 'About this book' facility on the currently selected book
+
+    NOTE: This workload requires a network connection (ideally, wifi) to run
+          and a Google account to be setup on the device.
+    """
+
+    parameters = [
+        Parameter('search_book_title', kind=str, mandatory=False, default="Hamlet",
+                  description="""
+                  The book title to search for within Google Play Books archive.
+                  The book must either be already in the account's library, or free to purchase.
+                  """),
+        Parameter('select_chapter_page_number', kind=int, mandatory=False, default=22,
+                  description="""
+                  The Page Number to search for within a selected book's Chapter list.
+                  Note: Accepts integers only.
+                  """),
+        Parameter('search_word', kind=str, mandatory=False, default='the',
+                  description="""
+                  The word to search for within a selected book.
+                  Note: Accepts single words only.
+                  """),
+        Parameter('markers_enabled', kind=bool, default=True,
+                  description="""
+                  If ``True``, UX_PERF action markers will be emitted to logcat during
+                  the test run.
+                  """),
+    ]
+
+    def validate(self):
+        super(Googleplaybooks, self).validate()
+        self.uiauto_params['package'] = self.package
+        self.uiauto_params['markers_enabled'] = self.markers_enabled
+        self.uiauto_params['book_title'] = self.search_book_title.replace(" ", "_")
+        self.uiauto_params['chapter_page_number'] = self.select_chapter_page_number
+        self.uiauto_params['search_word'] = self.search_word
+
+    def initialize(self, context):
+        super(Googleplaybooks, self).initialize(context)
+
+        # This workload relies on the internet so check that there is a working internet connection
+        if not self.device.is_network_connected():
+            raise DeviceError('Network is not connected for device {}'.format(self.device.name))
diff --git a/wlauto/workloads/googleplaybooks/com.arm.wlauto.uiauto.googleplaybooks.jar b/wlauto/workloads/googleplaybooks/com.arm.wlauto.uiauto.googleplaybooks.jar
new file mode 100644
index 00000000..13a616c1
Binary files /dev/null and b/wlauto/workloads/googleplaybooks/com.arm.wlauto.uiauto.googleplaybooks.jar differ
diff --git a/wlauto/workloads/googleplaybooks/uiauto/build.sh b/wlauto/workloads/googleplaybooks/uiauto/build.sh
new file mode 100755
index 00000000..468663cf
--- /dev/null
+++ b/wlauto/workloads/googleplaybooks/uiauto/build.sh
@@ -0,0 +1,39 @@
+#!/bin/bash
+
+# CD into build dir if possible - allows building from any directory
+script_path='.'
+if `readlink -f $0 &>/dev/null`; then
+    script_path=`readlink -f $0 2>/dev/null`
+fi
+script_dir=`dirname $script_path`
+cd $script_dir
+
+# Ensure build.xml exists before starting
+if [[ ! -f build.xml ]]; then
+    echo 'Ant build.xml file not found! Check that you are in the right directory.'
+    exit 9
+fi
+
+# Copy base classes from wlauto dist
+class_dir=bin/classes/com/arm/wlauto/uiauto
+base_classes=`python -c "import os, wlauto; print os.path.join(os.path.dirname(wlauto.__file__), 'common', 'android', '*.class')"`
+mkdir -p $class_dir
+cp $base_classes $class_dir
+
+# Build and return appropriate exit code if failed
+ant build
+exit_code=$?
+if [[ $exit_code -ne 0 ]]; then
+    echo "ERROR: 'ant build' exited with code $exit_code"
+    exit $exit_code
+fi
+
+# If successful move JAR file to workload folder (overwrite previous)
+package=com.arm.wlauto.uiauto.googleplaybooks.jar
+rm -f ../$package
+if [[ -f bin/$package ]]; then
+    cp bin/$package ..
+else
+    echo 'ERROR: UiAutomator JAR could not be found!'
+    exit 9
+fi
diff --git a/wlauto/workloads/googleplaybooks/uiauto/build.xml b/wlauto/workloads/googleplaybooks/uiauto/build.xml
new file mode 100644
index 00000000..8771d5ab
--- /dev/null
+++ b/wlauto/workloads/googleplaybooks/uiauto/build.xml
@@ -0,0 +1,92 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project name="com.arm.wlauto.uiauto.googleplaybooks" 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/googleplaybooks/uiauto/project.properties b/wlauto/workloads/googleplaybooks/uiauto/project.properties
new file mode 100644
index 00000000..ce39f2d0
--- /dev/null
+++ b/wlauto/workloads/googleplaybooks/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-18
diff --git a/wlauto/workloads/googleplaybooks/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/googleplaybooks/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java
new file mode 100755
index 00000000..edba383f
--- /dev/null
+++ b/wlauto/workloads/googleplaybooks/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java
@@ -0,0 +1,546 @@
+/*    Copyright 2014-2016 ARM Limited
+ *
+ * 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.
+ */
+
+package com.arm.wlauto.uiauto.googleplaybooks;
+
+import android.os.Bundle;
+
+// Import the uiautomator libraries
+import com.android.uiautomator.core.UiObject;
+import com.android.uiautomator.core.UiObjectNotFoundException;
+import com.android.uiautomator.core.UiSelector;
+import com.android.uiautomator.core.UiWatcher;
+import com.android.uiautomator.core.UiScrollable;
+
+import com.arm.wlauto.uiauto.UxPerfUiAutomation;
+
+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_googleplaybooks";
+
+    public Bundle parameters;
+    private int viewTimeoutSecs = 10;
+    private long viewTimeout =  TimeUnit.SECONDS.toMillis(viewTimeoutSecs);
+
+    public void runUiAutomation() throws Exception {
+        this.uiAutoTimeout = TimeUnit.SECONDS.toMillis(8);
+
+        parameters = getParams();
+
+        String bookTitle = parameters.getString("book_title").replace("_", " ");
+        String chapterPageNumber = parameters.getString("chapter_page_number");
+        String searchWord = parameters.getString("search_word");
+        String noteText = "This is a test note";
+
+        setScreenOrientation(ScreenOrientation.NATURAL);
+        clearFirstRunDialogues();
+        dismissSync();
+
+        searchForBook(bookTitle);
+        addToLibrary();
+        openMyLibrary();
+        openBook(bookTitle);
+
+        UiWatcher pageSyncPopUpWatcher = createPopUpWatcher();
+        registerWatcher("pageSyncPopUp", pageSyncPopUpWatcher);
+        runWatchers();
+
+        gesturesTest();
+        selectChapter(chapterPageNumber);
+        addNote(noteText);
+        removeNote();
+        searchForWord(searchWord);
+        switchPageStyles();
+        aboutBook();
+
+        removeWatcher("pageSyncPop");
+        pressBack();
+        unsetScreenOrientation();
+    }
+
+    // Creates a watcher for when a pop up warning appears when pages are out
+    // of sync across multiple devices.
+    private UiWatcher createPopUpWatcher() throws Exception {
+        UiWatcher pageSyncPopUpWatcher = new UiWatcher() {
+
+            @Override
+            public boolean checkForCondition() {
+                UiObject popUpDialogue =
+                    new UiObject(new UiSelector().resourceId("android:id/message")
+                                                 .textStartsWith("You're on page"));
+
+                // Don't sync and stay on the current page
+                if (popUpDialogue.exists()) {
+                    try {
+                        UiObject stayOnPage = new UiObject(new UiSelector()
+                                .className("android.widget.Button")
+                                .text("Yes"));
+                        stayOnPage.click();
+                    } catch (UiObjectNotFoundException e) {
+                        e.printStackTrace();
+                    }
+                    return popUpDialogue.waitUntilGone(viewTimeout);
+                }
+                return false;
+            }
+        };
+
+        return pageSyncPopUpWatcher;
+    }
+
+    private void dismissSync() throws Exception {
+        UiObject keepSyncOff =
+            new UiObject(new UiSelector().textContains("Keep sync off")
+                                         .className("android.widget.Button"));
+        if (keepSyncOff.exists()) {
+            keepSyncOff.click();
+        }
+    }
+
+    // If there is no sample book in My library we are prompted to choose a
+    // book the first time application is run. Try to skip the screen or
+    // pick a random sample book.
+    private void clearFirstRunDialogues() throws Exception {
+        UiObject startButton =
+            new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/start_button"));
+
+        // First try and skip the sample book selection
+        if (startButton.exists()) {
+            startButton.click();
+        }
+
+        UiObject endButton =
+            new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/end_button"));
+
+        // Click next button if it exists
+        if (endButton.exists()) {
+            endButton.click();
+
+            // Select a random sample book to add to My library
+            sleep(1);
+            tapDisplayCentre();
+            sleep(1);
+
+            // Click done button (uses same resource-id)
+            endButton.click();
+        }
+    }
+
+    // Searches for a "free" or "purchased" book title in Google play
+    private void searchForBook(final String bookTitle) throws Exception {
+        UiObject search =
+            new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/menu_search"));
+        search.click();
+
+        UiObject searchText = new UiObject(new UiSelector().textContains("Search")
+                                                           .className("android.widget.EditText"));
+        searchText.setText(bookTitle);
+        pressEnter();
+
+        UiObject resultList =
+            new UiObject(new UiSelector().resourceId("com.android.vending:id/search_results_list"));
+
+        if (!resultList.waitForExists(viewTimeout)) {
+            throw new UiObjectNotFoundException("Could not find \"search results list view\".");
+        }
+
+        String desc = String.format("Book: " + bookTitle);
+
+        // Create a selector so that we can search for siblings of the desired
+        // book that contains a "free" or "purchased" book identifier
+        UiSelector bookSelector = new UiSelector().description(desc).className("android.widget.TextView");
+
+        UiObject freeLabel =
+            new UiObject(new UiSelector().fromParent(bookSelector)
+                                         .resourceId("com.android.vending:id/li_label")
+                                         .description("Free"));
+
+        UiObject purchasedLabel =
+            new UiObject(new UiSelector().fromParent(bookSelector)
+                                         .resourceId("com.android.vending:id/li_label")
+                                         .description("Purchased"));
+
+        UiScrollable searchResultsList =
+            new UiScrollable(new UiSelector().resourceId("com.android.vending:id/search_results_list"));
+
+        int maxSwipes = 10;
+        while (!freeLabel.exists() && !purchasedLabel.exists()) {
+            if (maxSwipes <= 0) {
+                throw new UiObjectNotFoundException("Could not find free or purchased book \"" + bookTitle + "\"");
+            } else {
+                searchResultsList.swipeUp(10);
+                maxSwipes--;
+            }
+        }
+
+        // Click on either the first "free" or "purchased" book found that
+        // matches the book title
+        try {
+            freeLabel.click();
+        } catch (UiObjectNotFoundException e) {
+            purchasedLabel.click();
+        }
+    }
+
+    private void addToLibrary() throws Exception {
+        UiObject add = new UiObject(new UiSelector().textContains("ADD TO LIBRARY")
+                                                    .className("android.widget.Button"));
+        if (add.exists()) {
+            add.click(); // add to My Library and opens book by default
+        } else {
+            UiObject read = getUiObjectByText("READ", "android.widget.Button");
+            read.click(); // opens book
+        }
+
+        waitForPage();
+
+        UiObject navigationButton = new UiObject(new UiSelector().description("Navigate up"));
+
+        // Return to main app window
+        pressBack();
+
+        // On some devices screen ordering is not preserved so check for
+        // navigation button to determine current screen
+        if (navigationButton.exists()) {
+            pressBack();
+            pressBack();
+        }
+    }
+
+    private void openMyLibrary() throws Exception {
+        String testTag = "open_library";
+        ActionLogger logger = new ActionLogger(testTag, parameters);
+        logger.start();
+
+        UiObject openDrawer = getUiObjectByDescription("Show navigation drawer");
+        openDrawer.click();
+
+        // To correctly find the UiObject we need to specify the index also here
+        UiObject myLibrary =
+            new UiObject(new UiSelector().className("android.widget.TextView")
+                                         .text("My library").index(3));
+        myLibrary.clickAndWaitForNewWindow(uiAutoTimeout);
+        logger.stop();
+    }
+
+    private void openBook(final String bookTitle) throws Exception {
+
+        UiScrollable cardsGrid =
+            new UiScrollable(new UiSelector().resourceId("com.google.android.apps.books:id/cards_grid"));
+
+        UiSelector bookSelector = new UiSelector().text(bookTitle).className("android.widget.TextView");
+        UiObject book = new UiObject(bookSelector);
+
+        // Check that books are sorted by time added to library. This way we
+        // can assume any newly downloaded books will be visible on the first
+        // screen.
+        UiObject menuSort =
+            getUiObjectByResourceId("com.google.android.apps.books:id/menu_sort", "android.widget.TextView");
+        menuSort.click();
+
+        UiObject sortByRecent = getUiObjectByText("Recent", "android.widget.TextView");
+        sortByRecent.click();
+
+        // When the book is first added to library it may not appear in
+        // cardsGrid until it has been fully downloaded. Wait for fully
+        // downloaded books
+        UiObject downloadComplete =
+            new UiObject(new UiSelector().fromParent(bookSelector).description("100% downloaded"));
+
+        int maxDownloadTime = 120; // seconds
+
+        while (!downloadComplete.exists()) {
+            if (maxDownloadTime <= 0) {
+                throw new UiObjectNotFoundException(
+                        "Exceeded maximum wait time (" + maxDownloadTime  + " seconds) to download book \"" + bookTitle + "\"");
+            } else {
+                sleep(1);
+                maxDownloadTime--;
+            }
+        }
+
+        book.click();
+        waitForPage();
+    }
+
+    private void gesturesTest() throws Exception {
+        String testTag = "gestures";
+
+        // Perform a range of swipe tests while browsing home photoplaybooks gallery
+        LinkedHashMap<String, GestureTestParams> testParams = new LinkedHashMap<String, GestureTestParams>();
+        testParams.put("swipe_left", new GestureTestParams(GestureType.UIDEVICE_SWIPE, Direction.LEFT, 20));
+        testParams.put("swipe_right", new GestureTestParams(GestureType.UIDEVICE_SWIPE, Direction.RIGHT, 20));
+        testParams.put("pinch_out", new GestureTestParams(GestureType.PINCH, PinchType.OUT, 100, 50));
+        testParams.put("pinch_in", new GestureTestParams(GestureType.PINCH, PinchType.IN, 100, 50));
+
+        Iterator<Entry<String, GestureTestParams>> it = testParams.entrySet().iterator();
+
+        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());
+            ActionLogger logger = new ActionLogger(runName, parameters);
+
+            UiObject pageView = waitForPage();
+
+            logger.start();
+
+            switch (type) {
+                case UIDEVICE_SWIPE:
+                    uiDeviceSwipe(dir, steps);
+                    break;
+                case UIOBJECT_SWIPE:
+                    uiObjectSwipe(pageView, dir, steps);
+                    break;
+                case PINCH:
+                    uiObjectVertPinch(pageView, pinch, steps, percent);
+                    break;
+                default:
+                    break;
+            }
+
+            logger.stop();
+        }
+
+        waitForPage();
+    }
+
+    private UiObject searchPage(final UiObject view, final String pagenum, final Direction updown,
+                                final int attempts) throws Exception {
+        if (attempts <= 0) {
+            throw new UiObjectNotFoundException("Could not find \"page number\" after several attempts.");
+        }
+
+        String search = String.format("page " + pagenum);
+        UiObject page = new UiObject(new UiSelector().description(search)
+                                                    .className("android.widget.TextView"));
+        if (!page.exists()) {
+            // Scroll up by swiping down
+            if (updown == Direction.UP) {
+                view.swipeDown(200);
+            // Default case is to scroll down (swipe up)
+            } else {
+                view.swipeUp(200);
+            }
+            searchPage(view, pagenum, updown, attempts - 1);
+        }
+        return page;
+    }
+
+    private void selectChapter(final String chapterPageNumber) throws Exception {
+        getDropdownMenu();
+
+        UiObject contents = getUiObjectByResourceId("com.google.android.apps.books:id/menu_reader_toc",
+                                                    "android.widget.TextView");
+        contents.clickAndWaitForNewWindow(uiAutoTimeout);
+
+        UiObject toChapterView = getUiObjectByResourceId("com.google.android.apps.books:id/toc_list_view",
+                                                         "android.widget.ExpandableListView");
+
+        // Navigate to top of chapter view
+        searchPage(toChapterView, "1", Direction.UP, 10);
+
+        UiObject page = searchPage(toChapterView, chapterPageNumber, Direction.DOWN, 10);
+
+        page.clickAndWaitForNewWindow(viewTimeout);
+
+        waitForPage();
+    }
+
+    private void addNote(final String text) throws Exception {
+        String testTag = "add_note";
+        ActionLogger logger = new ActionLogger(testTag, parameters);
+        logger.start();
+
+        UiObject clickable = new UiObject(new UiSelector().longClickable(true));
+        uiObjectPerformLongClick(clickable, 100);
+
+        UiObject addNoteButton = new UiObject(
+                new UiSelector().resourceId("com.google.android.apps.books:id/add_note_button"));
+        addNoteButton.click();
+
+        UiObject noteEditText = getUiObjectByResourceId("com.google.android.apps.books:id/note_edit_text",
+                                                        "android.widget.EditText");
+        noteEditText.setText(text);
+
+        UiObject noteMenuButton = getUiObjectByResourceId("com.google.android.apps.books:id/note_menu_button",
+                                                          "android.widget.ImageButton");
+        noteMenuButton.click();
+
+        UiObject saveButton = getUiObjectByText("Save", "android.widget.TextView");
+        saveButton.click();
+
+        logger.stop();
+
+        waitForPage();
+    }
+
+    private void removeNote() throws Exception {
+        String testTag = "remove_note";
+        ActionLogger logger = new ActionLogger(testTag, parameters);
+        logger.start();
+
+        UiObject clickable = new UiObject(new UiSelector().longClickable(true));
+        uiObjectPerformLongClick(clickable, 100);
+
+        UiObject removeButton = new UiObject(
+                new UiSelector().resourceId("com.google.android.apps.books:id/remove_highlight_button"));
+        removeButton.click();
+
+        UiObject confirmRemove = getUiObjectByText("Remove", "android.widget.Button");
+        confirmRemove.click();
+
+        logger.stop();
+
+        waitForPage();
+    }
+
+    private void searchForWord(final String text) throws Exception {
+        String testTag = "search_for_word";
+        ActionLogger logger = new ActionLogger(testTag, parameters);
+
+        getDropdownMenu();
+        logger.start();
+        UiObject search = new UiObject(
+                new UiSelector().resourceId("com.google.android.apps.books:id/menu_search"));
+        search.click();
+
+        UiObject searchText = new UiObject(
+                new UiSelector().resourceId("com.google.android.apps.books:id/search_src_text"));
+        searchText.setText(text);
+        pressEnter();
+
+        UiObject resultList = new UiObject(
+                new UiSelector().resourceId("com.google.android.apps.books:id/search_results_list"));
+
+        // Allow extra time for search queries involing high freqency words
+        final long searchTimeout =  TimeUnit.SECONDS.toMillis(20);
+
+        if (!resultList.waitForExists(searchTimeout)) {
+            throw new UiObjectNotFoundException("Could not find \"search results list view\".");
+        }
+
+        UiObject searchWeb =
+            new UiObject(new UiSelector().text("Search web")
+                                         .className("android.widget.TextView"));
+
+        if (!searchWeb.waitForExists(searchTimeout)) {
+            throw new UiObjectNotFoundException("Could not find \"Search web view\".");
+        }
+
+        logger.stop();
+        pressBack();
+    }
+
+    private void switchPageStyles() throws Exception {
+        String testTag = "switch_page_style";
+
+        getDropdownMenu();
+        UiObject readerSettings = getUiObjectByResourceId("com.google.android.apps.books:id/menu_reader_settings",
+                                                          "android.widget.TextView");
+        readerSettings.click();
+
+        // Check for lighting option button on newer versions
+        UiObject lightingOptionsButton =
+            new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/lighting_options_button"));
+
+        if (lightingOptionsButton.exists()) {
+            lightingOptionsButton.click();
+        }
+
+        String[] styles = {"Night", "Sepia", "Day"};
+
+        for (String style : styles) {
+            ActionLogger logger = new ActionLogger(testTag + "_" + style, parameters);
+            logger.start();
+            UiObject pageStyle = new UiObject(new UiSelector().description(style));
+            pageStyle.clickAndWaitForNewWindow(viewTimeout);
+            logger.stop();
+        }
+
+        sleep(2);
+        tapDisplayCentre();
+        waitForPage();
+    }
+
+    private void aboutBook() throws Exception {
+        String testTag = "about_book";
+        ActionLogger logger = new ActionLogger(testTag, parameters);
+
+        getDropdownMenu();
+        logger.start();
+
+        UiObject moreOptions = getUiObjectByDescription("More options", "android.widget.ImageView");
+        moreOptions.click();
+
+        UiObject bookInfo = getUiObjectByText("About this book", "android.widget.TextView");
+        bookInfo.clickAndWaitForNewWindow(uiAutoTimeout);
+
+        UiObject detailsPanel =
+            new UiObject(new UiSelector().resourceId("com.android.vending:id/item_details_panel"));
+        waitObject(detailsPanel, viewTimeoutSecs);
+        logger.stop();
+
+        pressBack();
+    }
+
+    // Helper for accessing the drop down menu
+    private void getDropdownMenu() throws Exception {
+        sleep(1); // Allow previous views to settle
+        int height = getDisplayHeight();
+        int width = getDisplayCentreWidth();
+        getUiDevice().swipe(width, 20, width, height / 10, 50);
+
+        // selecting the drop down menu can be unreliable so check for its
+        // existence and if not present try for a second time using a different
+        // start point and step size
+        UiObject actionBar =
+            new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/action_bar"));
+
+        long actionBarTimeout =  TimeUnit.SECONDS.toMillis(3);
+
+        if (!actionBar.waitForExists(actionBarTimeout)) {
+            getUiDevice().swipe(width, 5, width, height / 10, 20);
+        }
+    }
+
+    // Helper for waiting on a page between actions
+    private UiObject waitForPage() throws Exception {
+        UiObject activityReader =
+            new UiObject(new UiSelector().resourceId("com.google.android.apps.books:id/activity_reader")
+                                         .childSelector(new UiSelector().focusable(true)));
+
+        // On some devices the object in the view hierarchy is found before it
+        // becomes visible on the screen. Therefore add pause instead.
+        sleep(3);
+
+        if (!activityReader.waitForExists(viewTimeout)) {
+            throw new UiObjectNotFoundException("Could not find \"activity reader view\".");
+        }
+
+        return activityReader;
+    }
+}