1/*
2 * Copyright (C) 2012 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.uiautomator;
18
19import com.android.uiautomator.tree.BasicTreeNode;
20import com.android.uiautomator.tree.BasicTreeNode.IFindNodeListener;
21import com.android.uiautomator.tree.UiHierarchyXmlLoader;
22import com.android.uiautomator.tree.UiNode;
23
24import org.eclipse.swt.SWTException;
25import org.eclipse.swt.graphics.Image;
26import org.eclipse.swt.graphics.ImageData;
27import org.eclipse.swt.graphics.ImageLoader;
28import org.eclipse.swt.graphics.Rectangle;
29
30import java.io.File;
31import java.util.List;
32
33public class UiAutomatorModel {
34
35    private static UiAutomatorModel inst = null;
36
37    private File mScreenshotFile, mXmlDumpFile;
38    private UiAutomatorViewer mView;
39    private Image mScreenshot;
40    private BasicTreeNode mRootNode;
41    private BasicTreeNode mSelectedNode;
42    private Rectangle mCurrentDrawingRect;
43    private List<Rectangle> mNafNodes;
44
45    // determines whether we lookup the leaf UI node on mouse move of screenshot image
46    private boolean mExploreMode = true;
47
48    private boolean mShowNafNodes = false;
49
50    private UiAutomatorModel(UiAutomatorViewer view) {
51        mView = view;
52    }
53
54    public static UiAutomatorModel createInstance(UiAutomatorViewer view) {
55        if (inst != null) {
56            throw new IllegalStateException("instance already created!");
57        }
58        inst = new UiAutomatorModel(view);
59        return inst;
60    }
61
62    public static UiAutomatorModel getModel() {
63        if (inst == null) {
64            throw new IllegalStateException("instance not created yet!");
65        }
66        return inst;
67    }
68
69    public File getScreenshotFile() {
70        return mScreenshotFile;
71    }
72
73    public File getXmlDumpFile() {
74        return mXmlDumpFile;
75    }
76
77    public boolean loadScreenshotAndXmlDump(File screenshotFile, File xmlDumpFile) {
78        if (screenshotFile != null && xmlDumpFile != null
79                && screenshotFile.isFile() && xmlDumpFile.isFile()) {
80            ImageData[] data = null;
81            Image img = null;
82            try {
83                // use SWT's ImageLoader to read png from path
84                data = new ImageLoader().load(screenshotFile.getAbsolutePath());
85            } catch (SWTException e) {
86                e.printStackTrace();
87                return false;
88            }
89            // "data" is an array, probably used to handle images that has multiple frames
90            // i.e. gifs or icons, we just care if it has at least one here
91            if (data.length < 1) return false;
92            UiHierarchyXmlLoader loader = new UiHierarchyXmlLoader();
93            BasicTreeNode rootNode = loader.parseXml(xmlDumpFile
94                    .getAbsolutePath());
95            if (rootNode == null) {
96                System.err.println("null rootnode after parsing.");
97                return false;
98            }
99            mNafNodes = loader.getNafNodes();
100            try {
101                // Image is tied to ImageData and a Display, so we only need to create once
102                // per new image
103                img = new Image(mView.getShell().getDisplay(), data[0]);
104            } catch (SWTException e) {
105                e.printStackTrace();
106                return false;
107            }
108            // only update screenhot and xml if both are loaded successfully
109            if (mScreenshot != null) {
110                mScreenshot.dispose();
111            }
112            mScreenshot = img;
113            if (mRootNode != null) {
114                mRootNode.clearAllChildren();
115            }
116            // TODO: we should verify here if the coordinates in the XML matches the png
117            // or not: think loading a phone screenshot with a tablet XML dump
118            mRootNode = rootNode;
119            mScreenshotFile = screenshotFile;
120            mXmlDumpFile = xmlDumpFile;
121            mExploreMode = true;
122            mView.loadScreenshotAndXml();
123            return true;
124        }
125        return false;
126    }
127
128    public BasicTreeNode getXmlRootNode() {
129        return mRootNode;
130    }
131
132    public Image getScreenshot() {
133        return mScreenshot;
134    }
135
136    public BasicTreeNode getSelectedNode() {
137        return mSelectedNode;
138    }
139
140    /**
141     * change node selection in the Model recalculate the rect to highlight,
142     * also notifies the View to refresh accordingly
143     *
144     * @param node
145     */
146    public void setSelectedNode(BasicTreeNode node) {
147        mSelectedNode = node;
148        if (mSelectedNode != null && mSelectedNode instanceof UiNode) {
149            UiNode uiNode = (UiNode) mSelectedNode;
150            mCurrentDrawingRect = new Rectangle(uiNode.x, uiNode.y, uiNode.width, uiNode.height);
151        } else {
152            mCurrentDrawingRect = null;
153        }
154        mView.updateScreenshot();
155        if (mSelectedNode != null) {
156            mView.loadAttributeTable();
157        }
158    }
159
160    public Rectangle getCurrentDrawingRect() {
161        return mCurrentDrawingRect;
162    }
163
164    /**
165     * Do a search in tree to find a leaf node or deepest parent node containing the coordinate
166     *
167     * @param x
168     * @param y
169     */
170    public void updateSelectionForCoordinates(int x, int y) {
171        if (mRootNode == null)
172            return;
173        MinAreaFindNodeListener listener = new MinAreaFindNodeListener();
174        boolean found = mRootNode.findLeafMostNodesAtPoint(x, y, listener);
175        if (found && listener.mNode != null && !listener.mNode.equals(mSelectedNode)) {
176            mView.updateTreeSelection(listener.mNode);
177        }
178    }
179
180    public boolean isExploreMode() {
181        return mExploreMode;
182    }
183
184    public void toggleExploreMode() {
185        mExploreMode = !mExploreMode;
186        mView.updateScreenshot();
187    }
188
189    public void setExploreMode(boolean exploreMode) {
190        mExploreMode = exploreMode;
191    }
192
193    private static class MinAreaFindNodeListener implements IFindNodeListener {
194        BasicTreeNode mNode = null;
195        @Override
196        public void onFoundNode(BasicTreeNode node) {
197            if (mNode == null) {
198                mNode = node;
199            } else {
200                if ((node.height * node.width) < (mNode.height * mNode.width)) {
201                    mNode = node;
202                }
203            }
204        }
205    }
206
207    public List<Rectangle> getNafNodes() {
208        return mNafNodes;
209    }
210
211    public void toggleShowNaf() {
212        mShowNafNodes = !mShowNafNodes;
213        mView.updateScreenshot();
214    }
215
216    public boolean shouldShowNafNodes() {
217        return mShowNafNodes;
218    }
219}
220