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.actions.ExpandAllAction;
     20 import com.android.uiautomator.actions.OpenFilesAction;
     21 import com.android.uiautomator.actions.ScreenshotAction;
     22 import com.android.uiautomator.actions.ToggleNafAction;
     23 import com.android.uiautomator.tree.AttributePair;
     24 import com.android.uiautomator.tree.BasicTreeNode;
     25 import com.android.uiautomator.tree.BasicTreeNodeContentProvider;
     26 
     27 import org.eclipse.jface.action.Action;
     28 import org.eclipse.jface.action.ToolBarManager;
     29 import org.eclipse.jface.layout.TableColumnLayout;
     30 import org.eclipse.jface.viewers.ArrayContentProvider;
     31 import org.eclipse.jface.viewers.CellEditor;
     32 import org.eclipse.jface.viewers.ColumnLabelProvider;
     33 import org.eclipse.jface.viewers.ColumnWeightData;
     34 import org.eclipse.jface.viewers.EditingSupport;
     35 import org.eclipse.jface.viewers.ISelectionChangedListener;
     36 import org.eclipse.jface.viewers.IStructuredSelection;
     37 import org.eclipse.jface.viewers.LabelProvider;
     38 import org.eclipse.jface.viewers.SelectionChangedEvent;
     39 import org.eclipse.jface.viewers.StructuredSelection;
     40 import org.eclipse.jface.viewers.TableViewer;
     41 import org.eclipse.jface.viewers.TableViewerColumn;
     42 import org.eclipse.jface.viewers.TextCellEditor;
     43 import org.eclipse.jface.viewers.TreeViewer;
     44 import org.eclipse.jface.window.ApplicationWindow;
     45 import org.eclipse.swt.SWT;
     46 import org.eclipse.swt.custom.SashForm;
     47 import org.eclipse.swt.events.MouseAdapter;
     48 import org.eclipse.swt.events.MouseEvent;
     49 import org.eclipse.swt.events.MouseMoveListener;
     50 import org.eclipse.swt.events.PaintEvent;
     51 import org.eclipse.swt.events.PaintListener;
     52 import org.eclipse.swt.graphics.Image;
     53 import org.eclipse.swt.graphics.Point;
     54 import org.eclipse.swt.graphics.Rectangle;
     55 import org.eclipse.swt.graphics.Transform;
     56 import org.eclipse.swt.layout.FillLayout;
     57 import org.eclipse.swt.layout.GridData;
     58 import org.eclipse.swt.layout.GridLayout;
     59 import org.eclipse.swt.widgets.Canvas;
     60 import org.eclipse.swt.widgets.Composite;
     61 import org.eclipse.swt.widgets.Control;
     62 import org.eclipse.swt.widgets.Group;
     63 import org.eclipse.swt.widgets.Shell;
     64 import org.eclipse.swt.widgets.Table;
     65 import org.eclipse.swt.widgets.TableColumn;
     66 import org.eclipse.swt.widgets.Tree;
     67 
     68 public class UiAutomatorViewer extends ApplicationWindow {
     69 
     70     private static final int IMG_BORDER = 2;
     71 
     72     private Canvas mScreenshotCanvas;
     73     private TreeViewer mTreeViewer;
     74 
     75     private Action mOpenFilesAction;
     76     private Action mExpandAllAction;
     77     private Action mScreenshotAction;
     78     private Action mToggleNafAction;
     79     private TableViewer mTableViewer;
     80 
     81     private float mScale = 1.0f;
     82     private int mDx, mDy;
     83 
     84     /**
     85      * Create the application window.
     86      */
     87     public UiAutomatorViewer() {
     88         super(null);
     89         UiAutomatorModel.createInstance(this);
     90         createActions();
     91     }
     92 
     93     /**
     94      * Create contents of the application window.
     95      *
     96      * @param parent
     97      */
     98     @Override
     99     protected Control createContents(Composite parent) {
    100         SashForm baseSash = new SashForm(parent, SWT.HORIZONTAL | SWT.NONE);
    101         // draw the canvas with border, so the divider area for sash form can be highlighted
    102         mScreenshotCanvas = new Canvas(baseSash, SWT.BORDER);
    103         mScreenshotCanvas.addMouseListener(new MouseAdapter() {
    104             @Override
    105             public void mouseUp(MouseEvent e) {
    106                 UiAutomatorModel.getModel().toggleExploreMode();
    107             }
    108         });
    109         mScreenshotCanvas.setBackground(
    110                 getShell().getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND));
    111         mScreenshotCanvas.addPaintListener(new PaintListener() {
    112             @Override
    113             public void paintControl(PaintEvent e) {
    114                 Image image = UiAutomatorModel.getModel().getScreenshot();
    115                 if (image != null) {
    116                     updateScreenshotTransformation();
    117                     // shifting the image here, so that there's a border around screen shot
    118                     // this makes highlighting red rectangles on the screen shot edges more visible
    119                     Transform t = new Transform(e.gc.getDevice());
    120                     t.translate(mDx, mDy);
    121                     t.scale(mScale, mScale);
    122                     e.gc.setTransform(t);
    123                     e.gc.drawImage(image, 0, 0);
    124                     // this resets the transformation to identity transform, i.e. no change
    125                     // we don't use transformation here because it will cause the line pattern
    126                     // and line width of highlight rect to be scaled, causing to appear to be blurry
    127                     e.gc.setTransform(null);
    128                     if (UiAutomatorModel.getModel().shouldShowNafNodes()) {
    129                         // highlight the "Not Accessibility Friendly" nodes
    130                         e.gc.setForeground(e.gc.getDevice().getSystemColor(SWT.COLOR_YELLOW));
    131                         e.gc.setBackground(e.gc.getDevice().getSystemColor(SWT.COLOR_YELLOW));
    132                         for (Rectangle r : UiAutomatorModel.getModel().getNafNodes()) {
    133                             e.gc.setAlpha(50);
    134                             e.gc.fillRectangle(mDx + getScaledSize(r.x), mDy + getScaledSize(r.y),
    135                                     getScaledSize(r.width), getScaledSize(r.height));
    136                             e.gc.setAlpha(255);
    137                             e.gc.setLineStyle(SWT.LINE_SOLID);
    138                             e.gc.setLineWidth(2);
    139                             e.gc.drawRectangle(mDx + getScaledSize(r.x), mDy + getScaledSize(r.y),
    140                                     getScaledSize(r.width), getScaledSize(r.height));
    141                         }
    142                     }
    143                     // draw the mouseover rects
    144                     Rectangle rect = UiAutomatorModel.getModel().getCurrentDrawingRect();
    145                     if (rect != null) {
    146                         e.gc.setForeground(e.gc.getDevice().getSystemColor(SWT.COLOR_RED));
    147                         if (UiAutomatorModel.getModel().isExploreMode()) {
    148                             // when we highlight nodes dynamically on mouse move,
    149                             // use dashed borders
    150                             e.gc.setLineStyle(SWT.LINE_DASH);
    151                             e.gc.setLineWidth(1);
    152                         } else {
    153                             // when highlighting nodes on tree node selection,
    154                             // use solid borders
    155                             e.gc.setLineStyle(SWT.LINE_SOLID);
    156                             e.gc.setLineWidth(2);
    157                         }
    158                         e.gc.drawRectangle(mDx + getScaledSize(rect.x), mDy + getScaledSize(rect.y),
    159                                 getScaledSize(rect.width), getScaledSize(rect.height));
    160                     }
    161                 }
    162             }
    163         });
    164         mScreenshotCanvas.addMouseMoveListener(new MouseMoveListener() {
    165             @Override
    166             public void mouseMove(MouseEvent e) {
    167                 if (UiAutomatorModel.getModel().isExploreMode()) {
    168                     UiAutomatorModel.getModel().updateSelectionForCoordinates(
    169                             getInverseScaledSize(e.x - mDx),
    170                             getInverseScaledSize(e.y - mDy));
    171                 }
    172             }
    173         });
    174 
    175         // right sash is split into 2 parts: upper-right and lower-right
    176         // both are composites with borders, so that the horizontal divider can be highlighted by
    177         // the borders
    178         SashForm rightSash = new SashForm(baseSash, SWT.VERTICAL);
    179 
    180         // upper-right base contains the toolbar and the tree
    181         Composite upperRightBase = new Composite(rightSash, SWT.BORDER);
    182         upperRightBase.setLayout(new GridLayout(1, false));
    183         ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT);
    184         toolBarManager.add(mOpenFilesAction);
    185         toolBarManager.add(mExpandAllAction);
    186         toolBarManager.add(mScreenshotAction);
    187         toolBarManager.add(mToggleNafAction);
    188         toolBarManager.createControl(upperRightBase);
    189 
    190         mTreeViewer = new TreeViewer(upperRightBase, SWT.NONE);
    191         mTreeViewer.setContentProvider(new BasicTreeNodeContentProvider());
    192         // default LabelProvider uses toString() to generate text to display
    193         mTreeViewer.setLabelProvider(new LabelProvider());
    194         mTreeViewer.addSelectionChangedListener(new ISelectionChangedListener() {
    195             @Override
    196             public void selectionChanged(SelectionChangedEvent event) {
    197                 if (event.getSelection().isEmpty()) {
    198                     UiAutomatorModel.getModel().setSelectedNode(null);
    199                 } else if (event.getSelection() instanceof IStructuredSelection) {
    200                     IStructuredSelection selection = (IStructuredSelection) event.getSelection();
    201                     Object o = selection.toArray()[0];
    202                     if (o instanceof BasicTreeNode) {
    203                         UiAutomatorModel.getModel().setSelectedNode((BasicTreeNode)o);
    204                     }
    205                 }
    206             }
    207         });
    208         Tree tree = mTreeViewer.getTree();
    209         tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
    210         // move focus so that it's not on tool bar (looks weird)
    211         tree.setFocus();
    212 
    213         // lower-right base contains the detail group
    214         Composite lowerRightBase = new Composite(rightSash, SWT.BORDER);
    215         lowerRightBase.setLayout(new FillLayout());
    216         Group grpNodeDetail = new Group(lowerRightBase, SWT.NONE);
    217         grpNodeDetail.setLayout(new FillLayout(SWT.HORIZONTAL));
    218         grpNodeDetail.setText("Node Detail");
    219 
    220         Composite tableContainer = new Composite(grpNodeDetail, SWT.NONE);
    221 
    222         TableColumnLayout columnLayout = new TableColumnLayout();
    223         tableContainer.setLayout(columnLayout);
    224 
    225         mTableViewer = new TableViewer(tableContainer, SWT.NONE | SWT.FULL_SELECTION);
    226         Table table = mTableViewer.getTable();
    227         table.setLinesVisible(true);
    228         // use ArrayContentProvider here, it assumes the input to the TableViewer
    229         // is an array, where each element represents a row in the table
    230         mTableViewer.setContentProvider(new ArrayContentProvider());
    231 
    232         TableViewerColumn tableViewerColumnKey = new TableViewerColumn(mTableViewer, SWT.NONE);
    233         TableColumn tblclmnKey = tableViewerColumnKey.getColumn();
    234         tableViewerColumnKey.setLabelProvider(new ColumnLabelProvider() {
    235             @Override
    236             public String getText(Object element) {
    237                 if (element instanceof AttributePair) {
    238                     // first column, shows the attribute name
    239                     return ((AttributePair)element).key;
    240                 }
    241                 return super.getText(element);
    242             }
    243         });
    244         columnLayout.setColumnData(tblclmnKey,
    245                 new ColumnWeightData(1, ColumnWeightData.MINIMUM_WIDTH, true));
    246 
    247         TableViewerColumn tableViewerColumnValue = new TableViewerColumn(mTableViewer, SWT.NONE);
    248         tableViewerColumnValue.setEditingSupport(new AttributeTableEditingSupport(mTableViewer));
    249         TableColumn tblclmnValue = tableViewerColumnValue.getColumn();
    250         columnLayout.setColumnData(tblclmnValue,
    251                 new ColumnWeightData(2, ColumnWeightData.MINIMUM_WIDTH, true));
    252         tableViewerColumnValue.setLabelProvider(new ColumnLabelProvider() {
    253             @Override
    254             public String getText(Object element) {
    255                 if (element instanceof AttributePair) {
    256                     // second column, shows the attribute value
    257                     return ((AttributePair)element).value;
    258                 }
    259                 return super.getText(element);
    260             }
    261         });
    262         // sets the ratio of the vertical split: left 5 vs right 3
    263         baseSash.setWeights(new int[]{5, 3});
    264         return baseSash;
    265     }
    266 
    267     /**
    268      * Create the actions.
    269      */
    270     private void createActions() {
    271         mOpenFilesAction = new OpenFilesAction(this);
    272         mExpandAllAction = new ExpandAllAction(this);
    273         mScreenshotAction = new ScreenshotAction(this);
    274         mToggleNafAction = new ToggleNafAction();
    275     }
    276 
    277     /**
    278      * Launch the application.
    279      *
    280      * @param args
    281      */
    282     public static void main(String args[]) {
    283         try {
    284             UiAutomatorViewer window = new UiAutomatorViewer();
    285             window.setBlockOnOpen(true);
    286             window.open();
    287             UiAutomatorModel.getModel().cleanUp();
    288         } catch (Exception e) {
    289             e.printStackTrace();
    290         }
    291     }
    292 
    293     /**
    294      * Configure the shell.
    295      *
    296      * @param newShell
    297      */
    298     @Override
    299     protected void configureShell(Shell newShell) {
    300         super.configureShell(newShell);
    301         newShell.setText("UI Automator Viewer");
    302     }
    303 
    304 
    305     /**
    306      * Asks the Model for screenshot and xml tree data, then populates the screenshot
    307      * area and tree view accordingly
    308      */
    309     public void loadScreenshotAndXml() {
    310         mScreenshotCanvas.redraw();
    311         // load xml into tree
    312         BasicTreeNode wrapper = new BasicTreeNode();
    313         // putting another root node on top of existing root node
    314         // because Tree seems to like to hide the root node
    315         wrapper.addChild(UiAutomatorModel.getModel().getXmlRootNode());
    316         mTreeViewer.setInput(wrapper);
    317         mTreeViewer.getTree().setFocus();
    318     }
    319 
    320     /*
    321      * Causes a redraw of the canvas.
    322      *
    323      * The drawing code of canvas will handle highlighted nodes and etc based on data
    324      * retrieved from Model
    325      */
    326     public void updateScreenshot() {
    327         mScreenshotCanvas.redraw();
    328     }
    329 
    330     public void expandAll() {
    331         mTreeViewer.expandAll();
    332     }
    333 
    334     public void updateTreeSelection(BasicTreeNode node) {
    335         mTreeViewer.setSelection(new StructuredSelection(node), true);
    336     }
    337 
    338     public void loadAttributeTable() {
    339         // udpate the lower right corner table to show the attributes of the node
    340         mTableViewer.setInput(
    341                 UiAutomatorModel.getModel().getSelectedNode().getAttributesArray());
    342     }
    343 
    344     @Override
    345     protected Point getInitialSize() {
    346         return new Point(800, 600);
    347     }
    348 
    349     private void updateScreenshotTransformation() {
    350         Rectangle canvas = mScreenshotCanvas.getBounds();
    351         Rectangle image = UiAutomatorModel.getModel().getScreenshot().getBounds();
    352         float scaleX = (canvas.width - 2 * IMG_BORDER - 1) / (float)image.width;
    353         float scaleY = (canvas.height - 2 * IMG_BORDER - 1) / (float)image.height;
    354         // use the smaller scale here so that we can fit the entire screenshot
    355         mScale = Math.min(scaleX, scaleY);
    356         // calculate translation values to center the image on the canvas
    357         mDx = (canvas.width - getScaledSize(image.width) - IMG_BORDER * 2) / 2 + IMG_BORDER;
    358         mDy = (canvas.height - getScaledSize(image.height) - IMG_BORDER * 2) / 2 + IMG_BORDER;
    359     }
    360 
    361     private int getScaledSize(int size) {
    362         if (mScale == 1.0f) {
    363             return size;
    364         } else {
    365             return new Double(Math.floor((size * mScale))).intValue();
    366         }
    367     }
    368 
    369     private int getInverseScaledSize(int size) {
    370         if (mScale == 1.0f) {
    371             return size;
    372         } else {
    373             return new Double(Math.floor((size / mScale))).intValue();
    374         }
    375     }
    376 
    377     private class AttributeTableEditingSupport extends EditingSupport {
    378 
    379         private TableViewer mViewer;
    380 
    381         public AttributeTableEditingSupport(TableViewer viewer) {
    382             super(viewer);
    383             mViewer = viewer;
    384         }
    385 
    386         @Override
    387         protected boolean canEdit(Object arg0) {
    388             return true;
    389         }
    390 
    391         @Override
    392         protected CellEditor getCellEditor(Object arg0) {
    393             return new TextCellEditor(mViewer.getTable());
    394         }
    395 
    396         @Override
    397         protected Object getValue(Object o) {
    398             return ((AttributePair)o).value;
    399         }
    400 
    401         @Override
    402         protected void setValue(Object arg0, Object arg1) {
    403         }
    404 
    405     }
    406 }
    407