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