From 675b718ebfb196dc10d2961fa013356ee6505b77 Mon Sep 17 00:00:00 2001 From: Bill Napier Date: Tue, 8 Jun 2010 17:15:18 -0700 Subject: [PATCH] Add in some more useful MonkeyRunner commands: - Add in device "drag" command to simulate a finger moving across the touch screen - Add in some pixel operation on MonkeyImage to scripts can do pixel level tests and comparisons. - Fix up the help command to no longer loop forever. - Start MonkeyRunner in interactive mode if no script file is specified. Change-Id: Ifd2655d3291dcc9dc355d4c2efa9019dc38bee5d --- .../src/com/android/monkeyrunner/JythonUtils.java | 20 +++ .../src/com/android/monkeyrunner/MonkeyDevice.java | 39 +++++ .../src/com/android/monkeyrunner/MonkeyImage.java | 173 ++++++++++++++++++++- .../com/android/monkeyrunner/MonkeyManager.java | 12 ++ .../com/android/monkeyrunner/MonkeyRunnerHelp.java | 4 +- .../android/monkeyrunner/MonkeyRunnerOptions.java | 9 +- .../android/monkeyrunner/MonkeyRunnerStarter.java | 15 +- .../src/com/android/monkeyrunner/ScriptRunner.java | 32 +--- .../android/monkeyrunner/adb/AdbMonkeyDevice.java | 51 +++++- .../monkeyrunner/adb/LinearInterpolator.java | 128 +++++++++++++++ .../test/com/android/monkeyrunner/AllTests.java | 4 +- .../monkeyrunner/adb/LinearInterpolatorTest.java | 141 +++++++++++++++++ 12 files changed, 578 insertions(+), 50 deletions(-) create mode 100644 tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java create mode 100644 tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java b/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java index 5ef7c36d..258261b0 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/JythonUtils.java @@ -124,6 +124,26 @@ public final class JythonUtils { } /** + * Get a python floating point value from an ArgParser. + * + * @param ap the ArgParser to get the value from. + * @param position the position in the parser + * @param defaultValue the default value to return if the arg isn't specified. + * @return the double value + */ + public static double getFloat(ArgParser ap, int position, double defaultValue) { + PyObject arg = ap.getPyObject(position, new PyFloat(defaultValue)); + + if (Py.isInstance(arg, PyFloat.TYPE)) { + return ((PyFloat) arg).asDouble(); + } + if (Py.isInstance(arg, PyInteger.TYPE)) { + return ((PyInteger) arg).asDouble(); + } + throw Py.TypeError("Unable to parse argument: " + position); + } + + /** * Get a list of arguments from an ArgParser. * * @param ap the ArgParser diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java index f8cecc65..87c54c29 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyDevice.java @@ -22,9 +22,11 @@ import com.google.common.collect.Collections2; import com.android.monkeyrunner.doc.MonkeyRunnerExported; import org.python.core.ArgParser; +import org.python.core.Py; import org.python.core.PyDictionary; import org.python.core.PyException; import org.python.core.PyObject; +import org.python.core.PyTuple; import java.util.Collection; import java.util.Collections; @@ -112,6 +114,41 @@ public abstract class MonkeyDevice { touch(x, y, type); } + @MonkeyRunnerExported(doc = "Simulate a drag on the screen.", + args = { "start", "end", "duration", "steps"}, + argDocs = { "The starting point for the drag (a tuple of x,y)", + "The end point for the drag (a tuple of x,y)", + "How long (in seconds) should the drag take (default is 1.0 seconds)", + "The number of steps to take when interpolating points. (default is 10)"}) + public void drag(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + PyObject startObject = ap.getPyObject(0); + if (!(startObject instanceof PyTuple)) { + throw Py.TypeError("Agrument 0 is not a tuple"); + } + PyObject endObject = ap.getPyObject(1); + if (!(endObject instanceof PyTuple)) { + throw Py.TypeError("Agrument 1 is not a tuple"); + } + + PyTuple start = (PyTuple) startObject; + PyTuple end = (PyTuple) endObject; + + int startx = (Integer) start.__getitem__(0).__tojava__(Integer.class); + int starty = (Integer) start.__getitem__(1).__tojava__(Integer.class); + int endx = (Integer) end.__getitem__(0).__tojava__(Integer.class); + int endy = (Integer) end.__getitem__(1).__tojava__(Integer.class); + + double seconds = JythonUtils.getFloat(ap, 2, 1.0); + long ms = (long) (seconds * 1000.0); + + int steps = ap.getInt(3, 10); + + drag(startx, starty, endx, endy, steps, ms); + } + @MonkeyRunnerExported(doc = "Send a key press event to the specified button", args = { "name", "type" }, argDocs = { "the name of the key to press", "the type of touch event to send"}) @@ -270,6 +307,7 @@ public abstract class MonkeyDevice { return JythonUtils.convertMapToDict(result); } + @MonkeyRunnerExported(doc = "Wake up the screen on the device") public void wake(PyObject[] args, String[] kws) { ArgParser ap = JythonUtils.createArgParser(args, kws); Preconditions.checkNotNull(ap); @@ -288,6 +326,7 @@ public abstract class MonkeyDevice { protected abstract String getSystemProperty(String key); protected abstract void touch(int x, int y, TouchPressType type); protected abstract void press(String keyName, TouchPressType type); + protected abstract void drag(int startx, int starty, int endx, int endy, int steps, long ms); protected abstract void type(String string); protected abstract String shell(String cmd); protected abstract boolean installPackage(String path); diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java index aa962346..7475407c 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyImage.java @@ -20,12 +20,16 @@ import com.google.common.base.Preconditions; import com.android.monkeyrunner.doc.MonkeyRunnerExported; import org.python.core.ArgParser; +import org.python.core.PyInteger; import org.python.core.PyObject; +import org.python.core.PyTuple; import java.awt.Graphics; import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.lang.ref.WeakReference; import java.util.Iterator; import javax.imageio.ImageIO; @@ -36,13 +40,63 @@ import javax.imageio.stream.ImageOutputStream; * Jython object to encapsulate images that have been taken. */ public abstract class MonkeyImage { + /** + * Convert the MonkeyImage into a BufferedImage. + * + * @return a BufferedImage for this MonkeyImage. + */ public abstract BufferedImage createBufferedImage(); + // Cache the BufferedImage so we don't have to generate it every time. + private WeakReference cachedBufferedImage = null; + + /** + * Utility method to handle getting the BufferedImage and managing the cache. + * + * @return the BufferedImage for this image. + */ + private BufferedImage getBufferedImage() { + // Check the cache first + if (cachedBufferedImage != null) { + BufferedImage img = cachedBufferedImage.get(); + if (img != null) { + return img; + } + } + + // Not in the cache, so create it and cache it. + BufferedImage img = createBufferedImage(); + cachedBufferedImage = new WeakReference(img); + return img; + } + + @MonkeyRunnerExported(doc = "Encode the image into a format and return the bytes.", + args = {"format"}, + argDocs = { "The (optional) format in which to encode the image (PNG for example)" }, + returns = "A String containing the bytes.") + public byte[] convertToBytes(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + String format = ap.getString(0, "png"); + + BufferedImage argb = convertSnapshot(); + + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + ImageIO.write(argb, format, os); + } catch (IOException e) { + return new byte[0]; + } + return os.toByteArray(); + } + @MonkeyRunnerExported(doc = "Write out the file to the specified location. If no " + "format is specified, this function tries to guess at the output format " + "depending on the file extension given. If unable to determine, it uses PNG.", args = {"path", "format"}, - argDocs = {"Where to write out the file"}, + argDocs = {"Where to write out the file", + "The format in which to encode the image (PNG for example)"}, returns = "True if writing succeeded.") public boolean writeToFile(PyObject[] args, String[] kws) { ArgParser ap = JythonUtils.createArgParser(args, kws); @@ -64,7 +118,7 @@ public abstract class MonkeyImage { return writeToFile(path, "png"); } ImageWriter writer = writers.next(); - BufferedImage image = createBufferedImage(); + BufferedImage image = getBufferedImage(); try { File f = new File(path); f.delete(); @@ -84,8 +138,44 @@ public abstract class MonkeyImage { return true; } - public boolean writeToFile(String path, String format) { - BufferedImage image = createBufferedImage(); + @MonkeyRunnerExported(doc = "Get a single ARGB pixel from the image", + args = { "x", "y" }, + argDocs = { "the x offset of the pixel", "the y offset of the pixel" }, + returns = "A tuple of (A, R, G, B) for the pixel") + public PyObject getRawPixel(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + int x = ap.getInt(0); + int y = ap.getInt(1); + int pixel = getPixel(x, y); + PyInteger a = new PyInteger((pixel & 0xFF000000) >> 24); + PyInteger r = new PyInteger((pixel & 0x00FF0000) >> 16); + PyInteger g = new PyInteger((pixel & 0x0000FF00) >> 8); + PyInteger b = new PyInteger((pixel & 0x000000FF) >> 0); + return new PyTuple(a, r, g ,b); + } + + @MonkeyRunnerExported(doc = "Get a single ARGB pixel from the image", + args = { "x", "y" }, + argDocs = { "the x offset of the pixel", "the y offset of the pixel" }, + returns = "An integer for the ARGB pixel") + public int getRawPixelInt(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + int x = ap.getInt(0); + int y = ap.getInt(1); + return getPixel(x, y); + } + + private int getPixel(int x, int y) { + BufferedImage image = getBufferedImage(); + return image.getRGB(x, y); + } + + private BufferedImage convertSnapshot() { + BufferedImage image = getBufferedImage(); // Convert the image to ARGB so ImageIO writes it out nicely BufferedImage argb = new BufferedImage(image.getWidth(), image.getHeight(), @@ -93,6 +183,11 @@ public abstract class MonkeyImage { Graphics g = argb.createGraphics(); g.drawImage(image, 0, 0, null); g.dispose(); + return argb; + } + + public boolean writeToFile(String path, String format) { + BufferedImage argb = convertSnapshot(); try { ImageIO.write(argb, format, new File(path)); @@ -101,4 +196,72 @@ public abstract class MonkeyImage { } return true; } -} + + @MonkeyRunnerExported(doc = "Compare this image to the other image.", + args = {"other"}, + argDocs = {"The other image."}, + returns = "True if they are the same image.") + public boolean sameAs(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + PyObject otherObject = ap.getPyObject(0); + MonkeyImage other = (MonkeyImage) otherObject.__tojava__(MonkeyImage.class); + + BufferedImage otherImage = other.getBufferedImage(); + BufferedImage myImage = getBufferedImage(); + + // Easy size check + if (otherImage.getWidth() != myImage.getWidth()) { + return false; + } + if (otherImage.getHeight() != myImage.getHeight()) { + return false; + } + + int[] otherPixel = new int[1]; + int[] myPixel = new int[1]; + + // Now, go through pixel-by-pixel and check that the images are the same; + for (int y = 0; y < myImage.getHeight(); y++) { + for (int x = 0; x < myImage.getWidth(); x++) { + if (myImage.getRGB(x, y) != otherImage.getRGB(x, y)) { + return false; + } + } + } + return true; + } + + private static class BufferedImageMonkeyImage extends MonkeyImage { + private final BufferedImage image; + + public BufferedImageMonkeyImage(BufferedImage image) { + this.image = image; + } + + @Override + public BufferedImage createBufferedImage() { + return image; + } + + } + + @MonkeyRunnerExported(doc = "Get a sub-image of this image.", + args = {"rect"}, + argDocs = {"A Tuple of (x, y, w, h) representing the area of the image to extract."}, + returns = "The newly extracted image.") + public MonkeyImage getSubImage(PyObject[] args, String[] kws) { + ArgParser ap = JythonUtils.createArgParser(args, kws); + Preconditions.checkNotNull(ap); + + PyTuple rect = (PyTuple) ap.getPyObjectByType(0, PyTuple.TYPE); + int x = rect.__getitem__(0).asInt(); + int y = rect.__getitem__(1).asInt(); + int w = rect.__getitem__(2).asInt(); + int h = rect.__getitem__(3).asInt(); + + BufferedImage image = getBufferedImage(); + return new BufferedImageMonkeyImage(image.getSubimage(x, y, w, h)); + } +} \ No newline at end of file diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java index 65a5bcd5..11a2dd4b 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyManager.java @@ -82,6 +82,18 @@ public class MonkeyManager { } /** + * Send a touch move event at the specified location. + * + * @param x the x coordinate of where to click + * @param y the y coordinate of where to click + * @return success or not + * @throws IOException on error communicating with the device + */ + public boolean touchMove(int x, int y) throws IOException { + return sendMonkeyEvent("touch move " + x + " " + y); + } + + /** * Send a touch (down and then up) event at the specified location. * * @param x the x coordinate of where to click diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerHelp.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerHelp.java index ffef5ed4..bda45515 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerHelp.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerHelp.java @@ -97,7 +97,9 @@ public final class MonkeyRunnerHelp { // Containing classes for (Class toAdd : clz.getClasses()) { - newClasses.add(toAdd); + if (haventSeen.apply(toAdd)) { + newClasses.add(toAdd); + } } } diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java index 05860454..cf193c29 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerOptions.java @@ -95,7 +95,7 @@ public class MonkeyRunnerOptions { ImmutableList.Builder pluginListBuilder = ImmutableList.builder(); ImmutableList.Builder argumentBuilder = ImmutableList.builder(); - do { + while (index < args.length) { String argument = args[index++]; if ("-s".equals(argument)) { @@ -172,12 +172,7 @@ public class MonkeyRunnerOptions { argumentBuilder.add(argument); } } - } while (index < args.length); - - if (scriptFile == null) { - printUsage("Missing required parameter"); - return null; - } + }; return new MonkeyRunnerOptions(hostname, port, scriptFile, backend, pluginListBuilder.build(), argumentBuilder.build()); diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java index 8c324088..1f539ba4 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/MonkeyRunnerStarter.java @@ -81,11 +81,16 @@ public class MonkeyRunnerStarter { private int run() { MonkeyRunner.setBackend(backend); Map> plugins = handlePlugins(); - int error = ScriptRunner.run(options.getScriptFile().getAbsolutePath(), - options.getArguments(), plugins); - backend.shutdown(); - MonkeyRunner.setBackend(null); - return error; + if (options.getScriptFile() == null) { + ScriptRunner.console(); + return 0; + } else { + int error = ScriptRunner.run(options.getScriptFile().getAbsolutePath(), + options.getArguments(), plugins); + backend.shutdown(); + MonkeyRunner.setBackend(null); + return error; + } } private Predicate handlePlugin(File f) { diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java index 616ba852..7b2edb07 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/ScriptRunner.java @@ -27,7 +27,6 @@ import org.python.util.InteractiveConsole; import org.python.util.PythonInterpreter; import java.io.File; -import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; @@ -167,38 +166,11 @@ public class ScriptRunner { } /** - * Create and run a console using a new python interpreter for the test - * associated with this instance. + * Start an interactive python interpreter. */ - public void console() throws IOException { + public static void console() { initPython(); InteractiveConsole python = new InteractiveConsole(); - initInterpreter(python, scope, variable); python.interact(); } - - /** - * Start an interactive python interpreter using the specified set of local - * variables. Use this to interrupt a running test script with a prompt: - * - * @param locals - */ - public static void console(PyObject locals) { - initPython(); - InteractiveConsole python = new InteractiveConsole(locals); - python.interact(); - } - - /** - * Initialize a python interpreter. - * - * @param python - * @param scope - * @throws IOException - */ - public static void initInterpreter(PythonInterpreter python, Object scope, String variable) - throws IOException { - // Store the current test case as the this variable - python.set(variable, scope); - } } diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java index f71b1606..78ee601a 100644 --- a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/AdbMonkeyDevice.java @@ -23,6 +23,7 @@ import com.android.ddmlib.IDevice; import com.android.monkeyrunner.MonkeyDevice; import com.android.monkeyrunner.MonkeyImage; import com.android.monkeyrunner.MonkeyManager; +import com.android.monkeyrunner.adb.LinearInterpolator.Point; import java.io.IOException; import java.net.InetAddress; @@ -37,7 +38,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.logging.Level; import java.util.logging.Logger; -import java.util.regex.Pattern; import javax.annotation.Nullable; @@ -421,4 +421,53 @@ public class AdbMonkeyDevice extends MonkeyDevice { } return map; } + + @Override + protected void drag(int startx, int starty, int endx, int endy, int steps, long ms) { + final long iterationTime = ms / steps; + + LinearInterpolator lerp = new LinearInterpolator(steps); + LinearInterpolator.Point start = new LinearInterpolator.Point(startx, starty); + LinearInterpolator.Point end = new LinearInterpolator.Point(endx, endy); + lerp.interpolate(start, end, new LinearInterpolator.Callback() { + @Override + public void step(Point point) { + try { + manager.touchMove(point.getX(), point.getY()); + } catch (IOException e) { + LOG.log(Level.SEVERE, "Error sending drag start event", e); + } + + try { + Thread.sleep(iterationTime); + } catch (InterruptedException e) { + LOG.log(Level.SEVERE, "Error sleeping", e); + } + } + + @Override + public void start(Point point) { + try { + manager.touchDown(point.getX(), point.getY()); + } catch (IOException e) { + LOG.log(Level.SEVERE, "Error sending drag start event", e); + } + + try { + Thread.sleep(iterationTime); + } catch (InterruptedException e) { + LOG.log(Level.SEVERE, "Error sleeping", e); + } + } + + @Override + public void end(Point point) { + try { + manager.touchUp(point.getX(), point.getY()); + } catch (IOException e) { + LOG.log(Level.SEVERE, "Error sending drag end event", e); + } + } + }); + } } diff --git a/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java new file mode 100644 index 00000000..e39fefdd --- /dev/null +++ b/tools/monkeyrunner/src/com/android/monkeyrunner/adb/LinearInterpolator.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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.android.monkeyrunner.adb; + + + +/** + * Linear Interpolation class. + */ +public class LinearInterpolator { + private final int steps; + + /** + * Use our own Point class so we don't pull in java.awt.* just for this simple class. + */ + public static class Point { + private final int x; + private final int y; + + public Point(int x, int y) { + this.x = x; + this.y = y; + } + + @Override + public String toString() { + return new StringBuilder(). + append("("). + append(x). + append(","). + append(y). + append(")").toString(); + } + + + @Override + public boolean equals(Object obj) { + if (obj instanceof Point) { + Point that = (Point) obj; + return this.x == that.x && this.y == that.y; + } + return false; + } + + @Override + public int hashCode() { + return 0x43125315 + x + y; + } + + public int getX() { + return x; + } + + public int getY() { + return y; + } + } + + /** + * Callback interface to recieve interpolated points. + */ + public interface Callback { + /** + * Called once to inform of the start point. + */ + void start(Point point); + /** + * Called once to inform of the end point. + */ + void end(Point point); + /** + * Called at every step in-between start and end. + */ + void step(Point point); + } + + /** + * Create a new linear Interpolator. + * + * @param steps How many steps should be in a single run. This counts the intervals + * in-between points, so the actual number of points generated will be steps + 1. + */ + public LinearInterpolator(int steps) { + this.steps = steps; + } + + // Copied from android.util.MathUtils since we couldn't link it in on the host. + private static float lerp(float start, float stop, float amount) { + return start + (stop - start) * amount; + } + + /** + * Calculate the interpolated points. + * + * @param start The starting point + * @param end The ending point + * @param callback the callback to call with each calculated points. + */ + public void interpolate(Point start, Point end, Callback callback) { + int xDistance = Math.abs(end.getX() - start.getX()); + int yDistance = Math.abs(end.getY() - start.getY()); + float amount = (float) (1.0 / steps); + + + callback.start(start); + for (int i = 1; i < steps; i++) { + float newX = lerp(start.getX(), end.getX(), amount * i); + float newY = lerp(start.getY(), end.getY(), amount * i); + + callback.step(new Point(Math.round(newX), Math.round(newY))); + } + // Generate final point + callback.end(end); + } +} diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java b/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java index 645360e3..f5c571d8 100644 --- a/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java +++ b/tools/monkeyrunner/test/com/android/monkeyrunner/AllTests.java @@ -15,6 +15,8 @@ */ package com.android.monkeyrunner; +import com.android.monkeyrunner.adb.LinearInterpolatorTest; + import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestResult; @@ -36,7 +38,7 @@ public class AllTests { public static void main(String args[]) { TestRunner tr = new TestRunner(); TestResult result = tr.doRun(AllTests.suite(ImageUtilsTest.class, JythonUtilsTest.class, - MonkeyRunnerOptionsTest.class)); + MonkeyRunnerOptionsTest.class, LinearInterpolatorTest.class)); if (result.wasSuccessful()) { System.exit(0); } else { diff --git a/tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java b/tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java new file mode 100644 index 00000000..235550a3 --- /dev/null +++ b/tools/monkeyrunner/test/com/android/monkeyrunner/adb/LinearInterpolatorTest.java @@ -0,0 +1,141 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * 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.android.monkeyrunner.adb; + +import com.google.common.collect.Lists; + +import com.android.monkeyrunner.adb.LinearInterpolator.Point; + +import junit.framework.TestCase; + +import java.util.List; + +/** + * Unit tests for the LinerInterpolator class.S + */ +public class LinearInterpolatorTest extends TestCase { + private static class Collector implements LinearInterpolator.Callback { + private final List points = Lists.newArrayList(); + + public List getPoints() { + return points; + } + + @Override + public void end(Point input) { + points.add(input); + } + + @Override + public void start(Point input) { + points.add(input); + } + + @Override + public void step(Point input) { + points.add(input); + } + } + + List STEP_POINTS = Lists.newArrayList(0, 100, 200, 300, 400, 500, 600, 700, 800, 900, + 1000); + List REVERSE_STEP_POINTS = Lists.newArrayList(1000, 900, 800, 700, 600, 500, 400, 300, + 200, 100, 0); + + public void testLerpRight() { + LinearInterpolator lerp = new LinearInterpolator(10); + Collector collector = new Collector(); + lerp.interpolate(new LinearInterpolator.Point(0, 100), + new LinearInterpolator.Point(1000, 100), + collector); + + List points = collector.getPoints(); + assertEquals(11, points.size()); + for (int x = 0; x < points.size(); x++) { + assertEquals(new Point(STEP_POINTS.get(x), 100), points.get(x)); + } + } + + public void testLerpLeft() { + LinearInterpolator lerp = new LinearInterpolator(10); + Collector collector = new Collector(); + lerp.interpolate(new LinearInterpolator.Point(1000, 100), + new LinearInterpolator.Point(0, 100), + collector); + + List points = collector.getPoints(); + assertEquals(11, points.size()); + for (int x = 0; x < points.size(); x++) { + assertEquals(new Point(REVERSE_STEP_POINTS.get(x), 100), points.get(x)); + } + } + + public void testLerpUp() { + LinearInterpolator lerp = new LinearInterpolator(10); + Collector collector = new Collector(); + lerp.interpolate(new LinearInterpolator.Point(100, 1000), + new LinearInterpolator.Point(100, 0), + collector); + + List points = collector.getPoints(); + assertEquals(11, points.size()); + for (int x = 0; x < points.size(); x++) { + assertEquals(new Point(100, REVERSE_STEP_POINTS.get(x)), points.get(x)); + } + } + + public void testLerpDown() { + LinearInterpolator lerp = new LinearInterpolator(10); + Collector collector = new Collector(); + lerp.interpolate(new LinearInterpolator.Point(100, 0), + new LinearInterpolator.Point(100, 1000), + collector); + + List points = collector.getPoints(); + assertEquals(11, points.size()); + for (int x = 0; x < points.size(); x++) { + assertEquals(new Point(100, STEP_POINTS.get(x)), points.get(x)); + } + } + + public void testLerpNW() { + LinearInterpolator lerp = new LinearInterpolator(10); + Collector collector = new Collector(); + lerp.interpolate(new LinearInterpolator.Point(0, 0), + new LinearInterpolator.Point(1000, 1000), + collector); + + List points = collector.getPoints(); + assertEquals(11, points.size()); + for (int x = 0; x < points.size(); x++) { + assertEquals(new Point(STEP_POINTS.get(x), STEP_POINTS.get(x)), points.get(x)); + } + } + + public void testLerpNE() { + LinearInterpolator lerp = new LinearInterpolator(10); + Collector collector = new Collector(); + lerp.interpolate(new LinearInterpolator.Point(1000, 1000), + new LinearInterpolator.Point(0, 0), + collector); + + List points = collector.getPoints(); + assertEquals(11, points.size()); + for (int x = 0; x < points.size(); x++) { + assertEquals(new Point(REVERSE_STEP_POINTS.get(x), REVERSE_STEP_POINTS.get(x)), points.get(x)); + } + } +} -- 2.11.0