From 31cf06b62abb05e45d1083f26bfbd473022c232f Mon Sep 17 00:00:00 2001 From: John Richardson Date: Wed, 4 May 2016 11:10:07 +0100 Subject: [PATCH] Extended BaseUiAutomation and UxPerfUiAutomation Moved common functionality to base classes for code reuse. Refactored googlephotos, reader and gmail workloads to take advantage of the new base class methods and removed total test time metric. New methods for BaseUiAutomation: uiDeviceVertPinchIn, uiDeviceVertPinchOut New methods for UxPerfUiAutomation: uiObjectVertPinchTest, writeResultsToFile, startDumpsysSurfaceFlinger, startDumpsysSurfaceFlinger, startDumpsysGfxInfo, stopDumpsysGfxInfo New class for UxPerfUiAutomation: GestureTestParams --- .../arm/wlauto/uiauto/BaseUiAutomation.java | 48 +++++- .../arm/wlauto/uiauto/UxPerfUiAutomation.java | 87 +++++++++++ .../com/arm/wlauto/uiauto/UiAutomation.java | 31 +--- wlauto/workloads/googlephotos/__init__.py | 7 +- .../com.arm.wlauto.uiauto.googlephotos.jar | Bin 11019 -> 11215 bytes wlauto/workloads/googlephotos/uiauto/build.sh | 4 +- .../googlephotos/uiauto/project.properties | 2 +- .../com/arm/wlauto/uiauto/UiAutomation.java | 139 ++---------------- .../com/arm/wlauto/uiauto/UiAutomation.java | 106 ++----------- 9 files changed, 167 insertions(+), 257 deletions(-) diff --git a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java index c31a835c..22266ca6 100644 --- a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java +++ b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/BaseUiAutomation.java @@ -22,13 +22,13 @@ import java.io.InputStreamReader; import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeUnit; -import android.app.Activity; 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.UiScrollable; import com.android.uiautomator.core.UiSelector; import com.android.uiautomator.testrunner.UiAutomatorTestCase; @@ -207,4 +207,48 @@ public class BaseUiAutomation extends UiAutomatorTestCase { getDisplayCentreHeight(), steps); } + + public void uiDeviceVertPinchIn(UiObject view, int steps, int percent) throws Exception { + 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 at the top-center and bottom-center of the control + Point startPoint1 = new Point(rect.centerX(), rect.centerY() + (int) ((rect.height() / 2) * percentage)); + Point startPoint2 = new Point(rect.centerX(), rect.centerY() - (int) ((rect.height() / 2) * percentage)); + + // End at the same point at the center of the control + Point endPoint1 = new Point(rect.centerX(), rect.centerY() + FINGER_TOUCH_HALF_WIDTH); + Point endPoint2 = new Point(rect.centerX(), rect.centerY() - FINGER_TOUCH_HALF_WIDTH); + + view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); + } + + public void uiDeviceVertPinchOut(UiObject view, int steps, int percent) throws Exception { + 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)); + + view.performTwoPointerGesture(startPoint1, startPoint2, endPoint1, endPoint2, steps); + } } diff --git a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java index 756d0ea0..379908c3 100644 --- a/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java +++ b/wlauto/external/uiauto/src/com/arm/wlauto/uiauto/UxPerfUiAutomation.java @@ -17,6 +17,7 @@ package com.arm.wlauto.uiauto; import android.os.Build; import android.os.SystemClock; +import android.os.Bundle; import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; @@ -26,6 +27,7 @@ import com.android.uiautomator.core.UiSelector; import com.arm.wlauto.uiauto.BaseUiAutomation; import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.InputStreamReader; @@ -34,6 +36,10 @@ import java.util.logging.Level; import java.util.logging.Logger; import java.util.Arrays; import java.util.List; +import java.util.LinkedHashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.Map.Entry; public class UxPerfUiAutomation extends BaseUiAutomation { @@ -209,4 +215,85 @@ public class UxPerfUiAutomation extends BaseUiAutomation { results.end(); return results; } + + public Timer uiObjectVertPinchTest(UiObject view, PinchType direction, + int steps, int percent) throws Exception { + Timer results = new Timer(); + results.start(); + if (direction.equals(PinchType.IN)) { + uiDeviceVertPinchIn(view, steps, percent); + } else if (direction.equals(PinchType.OUT)) { + uiDeviceVertPinchOut(view, steps, percent); + } + results.end(); + return results; + } + + public class GestureTestParams { + public GestureType gestureType; + public Direction gestureDirection; + public PinchType pinchType; + public int percent; + public int steps; + + public GestureTestParams(GestureType gesture, Direction direction, int steps) { + this.gestureType = gesture; + this.gestureDirection = direction; + this.pinchType = PinchType.NULL; + this.steps = steps; + this.percent = 0; + } + + public 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; + } + } + + public 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> it = timingResults.entrySet().iterator(); + + while (it.hasNext()) { + Map.Entry 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(); + } + + public void startDumpsysSurfaceFlinger(Bundle parameters, String view) { + if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { + initDumpsysSurfaceFlinger(parameters.getString("package"), view); + } + } + + public void stopDumpsysSurfaceFlinger(Bundle parameters, 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); + } + } + + public void startDumpsysGfxInfo(Bundle parameters) { + if (Boolean.parseBoolean(parameters.getString("dumpsys_enabled"))) { + initDumpsysGfxInfo(parameters.getString("package")); + } + } + + public void stopDumpsysGfxInfo(Bundle parameters, 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); + } + } } diff --git a/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java index b2ab4dfe..58a0b516 100644 --- a/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java +++ b/wlauto/workloads/gmail/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java @@ -1,24 +1,16 @@ package com.arm.wlauto.uiauto.gmail; import android.os.Bundle; -import android.os.SystemClock; // Import the uiautomator libraries import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiScrollable; import com.android.uiautomator.core.UiSelector; -import com.android.uiautomator.testrunner.UiAutomatorTestCase; import com.arm.wlauto.uiauto.UxPerfUiAutomation; -import java.io.BufferedWriter; -import java.io.FileWriter; import java.util.concurrent.TimeUnit; -import java.util.Iterator; import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Map.Entry; public class UiAutomation extends UxPerfUiAutomation { @@ -55,7 +47,7 @@ public class UiAutomation extends UxPerfUiAutomation { .className("android.widget.ListView")); if (!converationView.waitForExists(networkTimeout)) { throw new UiObjectNotFoundException("Could not find \"converationView\"."); - }; + } } public void clickNewMail() throws Exception { @@ -112,9 +104,9 @@ public class UiAutomation extends UxPerfUiAutomation { UiObject attachIcon = getUiObjectByResourceId("com.google.android.gm:id/add_attachment", "android.widget.TextView"); - String [] imageFiles = {"1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"}; + String[] imageFiles = {"1.jpg", "2.jpg", "3.jpg", "4.jpg", "5.jpg"}; - for ( int i=0; i < imageFiles.length; i++) { + for ( int i = 0; i < imageFiles.length; i++) { result.start(); clickUiObject(attachIcon, timeout); @@ -174,21 +166,4 @@ public class UiAutomation extends UxPerfUiAutomation { timingResults.put(String.format("AttachFiles" + "_" + file), result); } } - - 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> it = timingResults.entrySet().iterator(); - - while (it.hasNext()) { - Map.Entry 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(); - } } diff --git a/wlauto/workloads/googlephotos/__init__.py b/wlauto/workloads/googlephotos/__init__.py index db0107b3..f041defe 100644 --- a/wlauto/workloads/googlephotos/__init__.py +++ b/wlauto/workloads/googlephotos/__init__.py @@ -39,8 +39,6 @@ class Googlephotos(AndroidUiAutoBenchmark): 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() @@ -56,7 +54,7 @@ class Googlephotos(AndroidUiAutoBenchmark): 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), + os.path.join(self.device.working_directory, wa_file), timeout=300) # Force a re-index of the mediaserver cache to pick up new files @@ -91,9 +89,8 @@ class Googlephotos(AndroidUiAutoBenchmark): 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)) + self.device.delete_file(os.path.join(self.device.working_directory, 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 index 2a4fdd3dfe20bdec746f079613637275d236be80..cf790ce49740a4abeb93a8a4001c0b1ac8abf7c2 100644 GIT binary patch delta 10968 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 G00014kDNyU delta 10770 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 timingResults = new LinkedHashMap(); 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")); } @@ -50,10 +40,10 @@ public class UiAutomation extends UxPerfUiAutomation { sleep(3); // Pause while splash screen loads - UiObject getStarteddButton = + UiObject getStartedButton = getUiObjectByResourceId("com.google.android.apps.photos:id/get_started", "android.widget.Button"); - getStarteddButton.clickAndWaitForNewWindow(); + getStartedButton.clickAndWaitForNewWindow(); UiObject welcomeButton = getUiObjectByResourceId("com.google.android.apps.photos:id/name", @@ -79,7 +69,7 @@ public class UiAutomation extends UxPerfUiAutomation { nextButton.clickAndWaitForNewWindow(); } - private void gesturesTest () throws Exception { + private void gesturesTest() throws Exception { String testTag = "gestures"; // Perform a range of swipe tests while browsing photo gallery @@ -112,10 +102,10 @@ public class UiAutomation extends UxPerfUiAutomation { if (!view.waitForExists(viewTimeout)) { throw new UiObjectNotFoundException("Could not find \"photo view\"."); - }; + } - startDumpsysGfxInfo(); - startDumpsysSurfaceFlinger(viewName); + startDumpsysGfxInfo(parameters); + startDumpsysSurfaceFlinger(parameters, viewName); Timer results = new Timer(); @@ -133,8 +123,8 @@ public class UiAutomation extends UxPerfUiAutomation { break; } - stopDumpsysSurfaceFlinger(viewName, surfFlingerlogName); - stopDumpsysGfxInfo(gfxInfologName); + stopDumpsysSurfaceFlinger(parameters, viewName, surfFlingerlogName); + stopDumpsysGfxInfo(parameters, gfxInfologName); timingResults.put(runName, results); } @@ -172,9 +162,9 @@ public class UiAutomation extends UxPerfUiAutomation { 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")); + // Helper to click on an individual photographs based on index in wa-working gallery. + private void selectPhoto(final int index) throws Exception { + UiObject cameraHeading = new UiObject(new UiSelector().text("wa-working")); cameraHeading.clickAndWaitForNewWindow(); UiObject photo = @@ -183,109 +173,4 @@ public class UiAutomation extends UxPerfUiAutomation { .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> it = timingResults.entrySet().iterator(); - - while (it.hasNext()) { - Map.Entry 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); - } - } } diff --git a/wlauto/workloads/reader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java b/wlauto/workloads/reader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java index 41a3ceac..f2334102 100755 --- a/wlauto/workloads/reader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java +++ b/wlauto/workloads/reader/uiauto/src/com/arm/wlauto/uiauto/UiAutomation.java @@ -7,16 +7,10 @@ import android.os.SystemClock; // Import the uiautomator libraries import com.android.uiautomator.core.UiObject; import com.android.uiautomator.core.UiObjectNotFoundException; -import com.android.uiautomator.core.UiScrollable; import com.android.uiautomator.core.UiSelector; -import com.android.uiautomator.core.UiCollection; -import com.android.uiautomator.testrunner.UiAutomatorTestCase; 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.Iterator; import java.util.LinkedHashMap; @@ -33,8 +27,6 @@ public class UiAutomation extends UxPerfUiAutomation { private LinkedHashMap timingResults = new LinkedHashMap(); public void runUiAutomation() throws Exception { - Timer result = new Timer(); - result.start(); parameters = getParams(); dismissWelcomeView(); @@ -45,12 +37,9 @@ public class UiAutomation extends UxPerfUiAutomation { gesturesTest("Getting Started.pdf"); - String [] searchStrings = {"Glossary", "cortex"}; + String[] searchStrings = {"Glossary", "cortex"}; searchPdfTest("cortex_m4", searchStrings); - result.end(); - timingResults.put("Total", result); - writeResultsToFile(timingResults, parameters.getString("output_file")); } @@ -73,7 +62,7 @@ public class UiAutomation extends UxPerfUiAutomation { do { i += 10; tapDisplay(webViewCoords.centerX(), webViewCoords.centerY() + i); - } while ( welcomeView.exists() || i < webViewCoords.top ); + } while (welcomeView.exists() || i < webViewCoords.top); } private void signInOnline(Bundle parameters) throws Exception { @@ -139,10 +128,10 @@ public class UiAutomation extends UxPerfUiAutomation { .className("android.widget.Button")); if (allowButton.waitForExists(timeout)) { allowButton.clickAndWaitForNewWindow(timeout); - }; + } } - private void openFile(String filename) throws Exception { + private void openFile(final String filename) throws Exception { String TestTag = "openfile"; @@ -184,7 +173,7 @@ public class UiAutomation extends UxPerfUiAutomation { return result; } - private Timer searchFileList(String searchText) throws Exception { + private Timer searchFileList(final String searchText) throws Exception { // Enter search text into the file searchBox. This will automatically filter the list. UiObject searchBox = getUiObjectByResourceId("android:id/search_src_text", "android.widget.EditText"); @@ -196,7 +185,7 @@ public class UiAutomation extends UxPerfUiAutomation { return result; } - private Timer openFileFromList(String file) throws Exception { + private Timer openFileFromList(final String file) throws Exception { // Open a file from a file list view by searching for UiObjects containing the doc title. UiObject fileObject = getUiObjectByText(file, "android.widget.TextView"); Timer result = new Timer(); @@ -212,31 +201,7 @@ public class UiAutomation extends UxPerfUiAutomation { return result; } - private class GestureTestParams { - GestureType gestureType; - Direction gestureDirection; - PinchType pinchType; - int percent; - 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 gesturesTest (String filename) throws Exception { + private void gesturesTest(final String filename) throws Exception { String TestTag = "gestures"; @@ -268,8 +233,8 @@ public class UiAutomation extends UxPerfUiAutomation { UiObject view = new UiObject(new UiSelector().resourceId("com.adobe.reader:id/viewPager")); - startDumpsysGfxInfo(); - startDumpsysSurfaceFlinger(viewName); + startDumpsysGfxInfo(parameters); + startDumpsysSurfaceFlinger(parameters, viewName); Timer results = new Timer(); @@ -287,8 +252,8 @@ public class UiAutomation extends UxPerfUiAutomation { break; } - stopDumpsysSurfaceFlinger(viewName, surfFlingerlogName); - stopDumpsysGfxInfo(gfxInfologName); + stopDumpsysSurfaceFlinger(parameters, viewName, surfFlingerlogName); + stopDumpsysGfxInfo(parameters, gfxInfologName); timingResults.put(runName, results); } @@ -296,7 +261,7 @@ public class UiAutomation extends UxPerfUiAutomation { exitDocument(); } - private void searchPdfTest (String filename, String [] searchStrings) throws Exception { + private void searchPdfTest(final String filename, final String[] searchStrings) throws Exception { String TestTag = "search"; @@ -305,7 +270,7 @@ public class UiAutomation extends UxPerfUiAutomation { // Get the page view for the opened document which we can use for pinch actions UiObject pageView = getUiObjectByResourceId("com.adobe.reader:id/pageView", "android.widget.RelativeLayout"); - for ( int i=0; i < searchStrings.length; i++) { + for (int i = 0; i < searchStrings.length; i++) { timingResults.put(String.format(TestTag + "_" + i), searchTest(searchStrings[i])); } @@ -313,7 +278,7 @@ public class UiAutomation extends UxPerfUiAutomation { exitDocument(); } - private Timer searchTest(String searchText) throws Exception { + private Timer searchTest(final String searchText) throws Exception { // Click on the search button icon and enter text in the box. This closes the keyboad // so click the box again and press Enter to start the search. UiObject searchButton = getUiObjectByResourceId("com.adobe.reader:id/document_view_search_icon", @@ -356,47 +321,4 @@ public class UiAutomation extends UxPerfUiAutomation { UiObject upButton = getUiObjectByResourceId("android:id/up", "android.widget.ImageView" ); upButton.clickAndWaitForNewWindow(); } - - 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> it = timingResults.entrySet().iterator(); - - while (it.hasNext()) { - Map.Entry 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); - } - } }