1/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.monkeyrunner.easy;
18
19import com.google.common.base.Preconditions;
20
21import com.android.chimpchat.core.TouchPressType;
22import com.android.chimpchat.hierarchyviewer.HierarchyViewer;
23import com.android.hierarchyviewerlib.device.ViewNode;
24import com.android.monkeyrunner.JythonUtils;
25import com.android.monkeyrunner.MonkeyDevice;
26import com.android.monkeyrunner.doc.MonkeyRunnerExported;
27
28import org.eclipse.swt.graphics.Point;
29import org.python.core.ArgParser;
30import org.python.core.ClassDictInit;
31import org.python.core.Py;
32import org.python.core.PyException;
33import org.python.core.PyInteger;
34import org.python.core.PyObject;
35import org.python.core.PyTuple;
36
37import java.util.Set;
38
39/**
40 * Extends {@link MonkeyDevice} to support looking up views using a 'selector'.
41 * Currently, only identifiers can be used as a selector. All methods on
42 * MonkeyDevice can be used on this class in Python.
43 *
44 * WARNING: This API is under development, expect the interface to change
45 * without notice.
46 */
47@MonkeyRunnerExported(doc = "MonkeyDevice with easier methods to refer to objects.")
48public class EasyMonkeyDevice extends PyObject implements ClassDictInit {
49    public static void classDictInit(PyObject dict) {
50        JythonUtils.convertDocAnnotationsForClass(EasyMonkeyDevice.class, dict);
51    }
52
53    private MonkeyDevice mDevice;
54    private HierarchyViewer mHierarchyViewer;
55
56    private static final Set<String> EXPORTED_METHODS =
57        JythonUtils.getMethodNames(EasyMonkeyDevice.class);
58
59    @MonkeyRunnerExported(doc = "Creates EasyMonkeyDevice with an underlying MonkeyDevice.",
60            args = { "device" },
61            argDocs = { "MonkeyDevice to extend." })
62    public EasyMonkeyDevice(MonkeyDevice device) {
63        this.mDevice = device;
64        this.mHierarchyViewer = device.getImpl().getHierarchyViewer();
65    }
66
67    @MonkeyRunnerExported(doc = "Sends a touch event to the selected object.",
68            args = { "selector", "type" },
69            argDocs = {
70                    "The selector identifying the object.",
71                    "The event type as returned by TouchPressType()." })
72    public void touch(PyObject[] args, String[] kws) {
73        ArgParser ap = JythonUtils.createArgParser(args, kws);
74        Preconditions.checkNotNull(ap);
75
76        By selector = getSelector(ap, 0);
77        String tmpType = ap.getString(1);
78        TouchPressType type = TouchPressType.fromIdentifier(tmpType);
79        Preconditions.checkNotNull(type, "Invalid touch type: " + tmpType);
80        // TODO: try catch rethrow PyExc
81        touch(selector, type);
82    }
83
84    public void touch(By selector, TouchPressType type) {
85        Point p = getElementCenter(selector);
86        mDevice.getImpl().touch(p.x, p.y, type);
87    }
88
89    @MonkeyRunnerExported(doc = "Types a string into the specified object.",
90            args = { "selector", "text" },
91            argDocs = {
92                    "The selector identifying the object.",
93                    "The text to type into the object." })
94    public void type(PyObject[] args, String[] kws) {
95        ArgParser ap = JythonUtils.createArgParser(args, kws);
96        Preconditions.checkNotNull(ap);
97
98        By selector = getSelector(ap, 0);
99        String text = ap.getString(1);
100        type(selector, text);
101    }
102
103    public void type(By selector, String text) {
104        Point p = getElementCenter(selector);
105        mDevice.getImpl().touch(p.x, p.y, TouchPressType.DOWN_AND_UP);
106        mDevice.getImpl().type(text);
107    }
108
109    @MonkeyRunnerExported(doc = "Locates the coordinates of the selected object.",
110            args = { "selector" },
111            argDocs = { "The selector identifying the object." },
112            returns = "Tuple containing (x,y,w,h) location and size.")
113    public PyTuple locate(PyObject[] args, String[] kws) {
114        ArgParser ap = JythonUtils.createArgParser(args, kws);
115        Preconditions.checkNotNull(ap);
116
117        By selector = getSelector(ap, 0);
118
119        ViewNode node = selector.findView(mHierarchyViewer);
120        Point p = HierarchyViewer.getAbsolutePositionOfView(node);
121        PyTuple tuple = new PyTuple(
122                new PyInteger(p.x),
123                new PyInteger(p.y),
124                new PyInteger(node.width),
125                new PyInteger(node.height));
126        return tuple;
127    }
128
129    @MonkeyRunnerExported(doc = "Checks if the specified object exists.",
130            args = { "selector" },
131            returns = "True if the object exists.",
132            argDocs = { "The selector identifying the object." })
133    public boolean exists(PyObject[] args, String[] kws) {
134        ArgParser ap = JythonUtils.createArgParser(args, kws);
135        Preconditions.checkNotNull(ap);
136
137        By selector = getSelector(ap, 0);
138        return exists(selector);
139    }
140
141    public boolean exists(By selector) {
142        ViewNode node = selector.findView(mHierarchyViewer);
143        return node != null;
144    }
145
146    @MonkeyRunnerExported(doc = "Checks if the specified object is visible.",
147            args = { "selector" },
148            returns = "True if the object is visible.",
149            argDocs = { "The selector identifying the object." })
150    public boolean visible(PyObject[] args, String[] kws) {
151        ArgParser ap = JythonUtils.createArgParser(args, kws);
152        Preconditions.checkNotNull(ap);
153
154        By selector = getSelector(ap, 0);
155        return visible(selector);
156    }
157
158    public boolean visible(By selector) {
159        ViewNode node = selector.findView(mHierarchyViewer);
160        return mHierarchyViewer.visible(node);
161    }
162
163    @MonkeyRunnerExported(doc = "Obtain the text in the selected input box.",
164            args = { "selector" },
165            argDocs = { "The selector identifying the object." },
166            returns = "Text in the selected input box.")
167    public String getText(PyObject[] args, String[] kws) {
168        ArgParser ap = JythonUtils.createArgParser(args, kws);
169        Preconditions.checkNotNull(ap);
170
171        By selector = getSelector(ap, 0);
172        return getText(selector);
173    }
174
175    public String getText(By selector) {
176        ViewNode node = selector.findView(mHierarchyViewer);
177        return mHierarchyViewer.getText(node);
178    }
179
180    @MonkeyRunnerExported(doc = "Gets the id of the focused window.",
181            returns = "The symbolic id of the focused window or None.")
182    public String getFocusedWindowId(PyObject[] args, String[] kws) {
183        return getFocusedWindowId();
184    }
185
186    public String getFocusedWindowId() {
187        return mHierarchyViewer.getFocusedWindowName();
188    }
189
190    /**
191     * Forwards unknown methods to the original MonkeyDevice object.
192     */
193    @Override
194    public PyObject __findattr_ex__(String name) {
195        if (!EXPORTED_METHODS.contains(name)) {
196            return mDevice.__findattr_ex__(name);
197        }
198        return super.__findattr_ex__(name);
199    }
200
201    /**
202     * Get the selector object from the argument parser.
203     *
204     * @param ap argument parser to get it from.
205     * @param i argument index.
206     * @return selector object.
207     */
208    private By getSelector(ArgParser ap, int i) {
209        return (By)ap.getPyObject(i).__tojava__(By.class);
210    }
211
212    /**
213     * Get the coordinates of the element's center.
214     *
215     * @param selector the element selector
216     * @return the (x,y) coordinates of the center
217     */
218    private Point getElementCenter(By selector) {
219        ViewNode node = selector.findView(mHierarchyViewer);
220        if (node == null) {
221            throw new PyException(Py.ValueError,
222                    String.format("View not found: %s", selector));
223        }
224
225        Point p = HierarchyViewer.getAbsoluteCenterOfView(node);
226        return p;
227    }
228
229}
230